1
0
Fork 0
umcumgr/src/image.c

457 lines
14 KiB
C
Raw Permalink Normal View History

//
// Created by nock on 26/03/19.
//
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
2019-05-02 12:54:43 -04:00
#include <fcntl.h>
#include <sys/stat.h>
2019-05-08 11:53:08 -04:00
#include <math.h>
#include "base64.h"
2019-05-02 12:54:43 -04:00
#include "crc.h"
2019-06-19 13:39:53 -04:00
#include "dump.h"
#include "error.h"
#include "fixkey-msg.h"
#include "fixkey-payload.h"
#include "gpio.h"
#include "image.h"
#include "nmgr.h"
2019-05-08 11:53:08 -04:00
#include "timer.h"
#include "serial_util.h"
2019-05-08 10:47:29 -04:00
#include <tinycbor/cbor.h>
#ifdef DO_UPLOAD_SHA
2019-05-03 23:14:14 -04:00
#include "sha256.h"
#endif
/* These values produce output identical to mcumgr *if* DO_UPLOAD_SHA is defined */
#define IMAGE_CHUNK_SIZE 124 // 124 for identical output to newtmgr
#define MACRO_CHUNK_SIZE 302 // 302 ""
/* Fast mode doesn't support original EM bootloader, but is much faster with later bootloaders */
#define IMAGE_CHUNK_SIZE_FAST 216
#define MACRO_CHUNK_SIZE_FAST 432
2019-05-02 12:54:43 -04:00
/*
* Larger values above greatly increase speed, but seem to break older
* versions of mcuboot (like the one shipped with inital EM firmware)
*/
2019-05-02 12:54:43 -04:00
#define CRC16_INITIAL_CRC 0 /* what to seed crc16 with */
#define CRC_CITT_POLYMINAL 0x1021
static int serial_fd = -1;
static bool g_fast_mode = false;
2019-06-19 13:39:53 -04:00
static bool g_no_fix = false;
void image_set_fast_mode(void) {
g_fast_mode = true;
}
static bool image_get_fast_mode(void) {
return g_fast_mode;
}
void image_help(void){
BAIL_OUT(ERR_USAGE);
}
static void read_response(uint8_t *dest, size_t *len) {
static uint8_t buf[BOOT_SERIAL_IN_MAX*2] = {0};
static uint8_t *p = buf;
2019-05-08 11:53:08 -04:00
timer_start(3);
while (true) {
int bytes_read = read(serial_fd, p, BOOT_SERIAL_IN_MAX);
if (bytes_read < 0) {
if (errno == EAGAIN) {
2019-05-08 11:53:08 -04:00
if (timer_expired()) {
2019-06-19 13:39:53 -04:00
dump_raw(buf, p-buf, DUMP_RXI);
BAIL_OUT(ERR_TIMEOUT);
2019-05-08 11:53:08 -04:00
}
usleep(5000);
continue;
}
BAIL_OUT(ERR_SERIAL_PORT);
}
2019-06-19 13:39:53 -04:00
dump_raw(p, bytes_read, DUMP_RXI_BUFFER_UPDATE);
p += bytes_read;
uint8_t *nl_p = memchr(buf, '\n', p-buf);
if (nl_p) {
*len = nl_p - buf;
memcpy(dest, buf, *len);
uint16_t new_buf_len = p - nl_p;
if (new_buf_len) {
p = buf;
return;
}
memcpy(buf, nl_p+1, new_buf_len);
p = buf+new_buf_len;
break;
}
}
}
static bool assert_pkt_start(uint8_t const *buf){
return (buf[0] == SHELL_NLIP_PKT_START1 && buf[1] == SHELL_NLIP_PKT_START2);
}
#define cbor_require_type(it, type) _cbor_require_type(it, type, __FUNCTION__, __FILE__, __LINE__)
static void _cbor_require_type(CborValue *it, CborType type, const char *function, const char *file, unsigned line) {
CborType actual_type = cbor_value_get_type(it);
if (actual_type != type) {
_bail_out(ERR_CBOR_TYPE, function, file, line);
}
}
#define cbor_ensure_success(cbor_err) _cbor_ensure_success(cbor_err, __FUNCTION__, __FILE__, __LINE__)
static inline void _cbor_ensure_success(CborError err, const char *function, const char *file, unsigned line) {
if (err != CborNoError) {
_bail_out(ERR_CBOR, function, file, line);
}
}
static char *get_version(void) {
serial_flush(serial_fd);
int rc = write(serial_fd, IMAGE_LIST_CMD, sizeof(IMAGE_LIST_CMD) - 1);
2019-06-19 13:39:53 -04:00
dump_raw((uint8_t *)IMAGE_LIST_CMD, sizeof(IMAGE_LIST_CMD)-1, DUMP_TXO);
if (rc < sizeof(IMAGE_LIST_CMD) - 1) {
BAIL_OUT(ERR_SERIAL_PORT);
}
uint8_t tmp[BOOT_SERIAL_IN_MAX];
size_t in_len;
read_response(tmp, &in_len);
2019-06-19 13:39:53 -04:00
dump_raw(tmp, in_len, DUMP_RXI);
// Process the response
CborParser parser;
CborValue it;
// Validate packet start marker
2019-06-19 13:39:53 -04:00
if (in_len < 2) {
BAIL_OUT(ERR_INVALID_PACKET);
}
if (!assert_pkt_start(tmp)) {
BAIL_OUT(ERR_INVALID_PACKET);
2019-05-02 12:54:43 -04:00
}
// Decode the payload
size_t out_len;
uint8_t *decoded_buf = base64_decode(tmp+2, in_len-2, &out_len);
2019-05-02 12:54:43 -04:00
if (!decoded_buf) {
BAIL_OUT(ERR_BASE64_DECODE);
2019-05-02 12:54:43 -04:00
}
// Unclear why this would be useful in a line oriented (cannonical mode) program
uint16_t pkt_len = ntohs(*(uint16_t *)decoded_buf);
uint16_t crc = crc16(decoded_buf+sizeof(pkt_len), pkt_len, CRC_CITT_POLYMINAL, CRC16_INITIAL_CRC, true);
if (crc) {
BAIL_OUT(ERR_BAD_CRC);
}
uint8_t *p = decoded_buf + sizeof(pkt_len) + sizeof(struct nmgr_hdr);
cbor_ensure_success(
cbor_parser_init(p, out_len, 0, &parser, &it));
cbor_require_type(&it, CborMapType);
CborValue map;
cbor_ensure_success(cbor_value_enter_container(&it, &map));
cbor_require_type(&map, CborTextStringType);
cbor_ensure_success(cbor_value_advance(&map));
cbor_require_type(&map, CborArrayType);
CborValue img_array;
cbor_ensure_success(cbor_value_enter_container(&map, &img_array));
char *version = NULL;
while (!cbor_value_at_end(&img_array)) {
cbor_require_type(&img_array, CborMapType);
CborValue version_tag;
cbor_ensure_success(
cbor_value_map_find_value(&img_array, "version", &version_tag));
if (cbor_value_get_type(&version_tag) == CborInvalidType) {
cbor_value_advance(&img_array);
continue;
}
size_t len;
cbor_value_dup_text_string(&version_tag, &version, &len, NULL);
break;
}
free(decoded_buf);
return version;
}
static void wrap_and_send_pkt(uint8_t *data, size_t len, bool fast_mode) {
assert(data != NULL);
assert(len != 0);
static uint8_t seq = 67;
uint8_t b64_buf[BOOT_SERIAL_OUT_MAX*2];
uint16_t tot_len = 2 + len + sizeof(struct nmgr_hdr) + 2; // +2 is len +2 is crc
uint16_t net_len = htons(tot_len-2); // tot_len doesn't include the len field
2019-05-03 23:14:14 -04:00
struct nmgr_hdr hdr = {0};
2019-05-02 12:54:43 -04:00
hdr.nh_op = NMGR_OP_WRITE;
hdr.nh_group = htons(MGMT_GROUP_ID_IMAGE);
hdr.nh_id = IMGMGR_NMGR_ID_UPLOAD;
2019-05-03 23:14:14 -04:00
hdr.nh_seq = seq++;
hdr.nh_len = htons(len);
2019-05-02 12:54:43 -04:00
memcpy(b64_buf, &net_len, sizeof(net_len));
2019-05-02 12:54:43 -04:00
#define HDR_OFFSET 2
#define DATA_OFFSET (HDR_OFFSET+sizeof(struct nmgr_hdr))
#define CRC_OFFSET(len) (DATA_OFFSET+len)
2019-05-03 23:14:14 -04:00
memcpy(b64_buf+HDR_OFFSET, &hdr, sizeof(struct nmgr_hdr));
memcpy(b64_buf+DATA_OFFSET, data, len);
2019-05-03 23:14:14 -04:00
uint16_t crc = crc16(b64_buf+HDR_OFFSET, tot_len-4, CRC_CITT_POLYMINAL, CRC16_INITIAL_CRC, true);
crc = htons(crc);
memcpy(b64_buf+CRC_OFFSET(len), &crc, sizeof(crc));
2019-05-02 12:54:43 -04:00
size_t out_len;
uint8_t *out_buf = base64_encode(b64_buf, tot_len, &out_len);
uint8_t *pos = out_buf;
while (pos < out_buf+out_len) {
uint8_t start_pkt[] = {SHELL_NLIP_PKT_START1, SHELL_NLIP_PKT_START2};
uint8_t start_data[] = {SHELL_NLIP_DATA_START1, SHELL_NLIP_DATA_START2};
2019-05-02 12:54:43 -04:00
if (pos == out_buf) {
write(serial_fd, start_pkt, sizeof(start_pkt));
2019-06-19 13:39:53 -04:00
dump_raw(start_pkt, sizeof(start_pkt), DUMP_TXO);
} else {
if (!fast_mode) {
usleep(20000);
}
write(serial_fd, start_data, sizeof(start_data));
2019-06-19 13:39:53 -04:00
dump_raw(start_data, sizeof(start_data), DUMP_TXO);
}
2019-05-02 12:54:43 -04:00
size_t remaining = out_buf + out_len - pos;
size_t to_write;
if (fast_mode) {
to_write = remaining > IMAGE_CHUNK_SIZE_FAST ? IMAGE_CHUNK_SIZE_FAST : remaining;
} else {
to_write = remaining > IMAGE_CHUNK_SIZE ? IMAGE_CHUNK_SIZE : remaining;
}
write(serial_fd, pos, to_write);
2019-06-19 13:39:53 -04:00
dump_raw(pos, to_write, DUMP_TXO);
write(serial_fd, "\n", 1);
2019-06-19 13:39:53 -04:00
dump_raw("\n", 1, DUMP_TXO);
pos += to_write;
}
2019-05-02 12:54:43 -04:00
free(out_buf);
fsync(serial_fd);
2019-05-02 12:54:43 -04:00
}
void do_upload(char *filename, bool fast_mode){
serial_flush(serial_fd);
size_t pos = 0, len;
uint8_t const *payload;
if (filename) {
int fd = open(filename, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Failed to open \"%s\": %s\n", filename, strerror(errno));
exit(ERR_SERIAL_PORT);
}
2019-05-02 12:54:43 -04:00
struct stat st;
fstat(fd, &st);
len = st.st_size;
uint8_t *payload_mut = calloc(1, len);
if (!payload_mut) {
BAIL_OUT(ERR_MEMORY);
}
size_t bytes_read = 0;
while (bytes_read < len) {
errno = 0;
int r = read(fd, &payload_mut[bytes_read], len);
if (r < 0 && errno != EAGAIN) {
BAIL_OUT(ERR_IO);
}
bytes_read += r;
}
payload = payload_mut;
} else {
payload = fixkey_payload_bin;
len = fixkey_payload_len;
}
2019-05-08 11:53:08 -04:00
size_t base_chunk_len;
if (fast_mode) {
base_chunk_len = MACRO_CHUNK_SIZE_FAST;
} else {
base_chunk_len = MACRO_CHUNK_SIZE;
}
2019-05-02 12:54:43 -04:00
while (pos < len){
size_t data_chunk_len;
if (pos == 0) {
data_chunk_len = base_chunk_len;
} else {
data_chunk_len = base_chunk_len + 45;
}
2019-05-02 12:54:43 -04:00
CborEncoder root, map;
uint8_t cbor_buf[BOOT_SERIAL_OUT_MAX*2];
cbor_encoder_init(&root, cbor_buf, BOOT_SERIAL_OUT_MAX*2, 0);
#ifdef DO_UPLOAD_SHA
cbor_ensure_success(cbor_encoder_create_map(&root, &map, pos == 0 ? 4 : 2));
#else
cbor_ensure_success(cbor_encoder_create_map(&root, &map, pos == 0 ? 3 : 2));
#endif
2019-05-02 12:54:43 -04:00
cbor_ensure_success(cbor_encode_text_stringz(&map, "data"));
// Most packets will be `data_chunk_len`, but not the last one
size_t actual_size = len - pos > data_chunk_len ? data_chunk_len : len - pos;
cbor_ensure_success(cbor_encode_byte_string(&map, &payload[pos], actual_size));
if (pos == 0) {
cbor_ensure_success(cbor_encode_text_stringz(&map, "len"));
cbor_ensure_success(cbor_encode_uint(&map, len));
}
cbor_ensure_success(cbor_encode_text_stringz(&map, "off"));
cbor_ensure_success(cbor_encode_uint(&map, pos));
#ifdef DO_UPLOAD_SHA
if (pos == 0) {
cbor_ensure_success(cbor_encode_text_stringz(&map, "sha"));
SHA256_CTX sha_ctx;
sha256_init(&sha_ctx);
sha256_update(&sha_ctx, payload, len);
uint8_t sha[32];
sha256_final(&sha_ctx, (BYTE *)&sha);
cbor_ensure_success(cbor_encode_byte_string(&map, sha, 32));
}
#endif /* DO_UPLOAD_SHA */
2019-05-03 23:14:14 -04:00
cbor_ensure_success(cbor_encoder_close_container(&root, &map));
size_t cbor_size = cbor_encoder_get_buffer_size(&root, cbor_buf);
wrap_and_send_pkt(cbor_buf, cbor_size, fast_mode);
2019-05-02 12:54:43 -04:00
uint8_t resp_buf[BOOT_SERIAL_IN_MAX*2];
size_t in_len;
read_response(resp_buf, &in_len);
2019-05-02 12:54:43 -04:00
size_t out_len;
uint8_t *out = base64_decode(resp_buf+2, in_len-2, &out_len);
if (out == NULL) {
BAIL_OUT(ERR_BASE64_DECODE);
}
uint16_t l;
memcpy(&l, out, sizeof(l));
l = ntohs(l);
if (crc16(out+2, l, CRC_CITT_POLYMINAL, CRC16_INITIAL_CRC, true)) {
BAIL_OUT(ERR_BAD_CRC);
2019-05-02 12:54:43 -04:00
}
CborParser parser;
CborValue it, rc;
cbor_ensure_success(
cbor_parser_init(out+sizeof(struct nmgr_hdr)+2, out_len-sizeof(struct nmgr_hdr)-2, 0, &parser, &it));
2019-05-02 12:54:43 -04:00
cbor_require_type(&it, CborMapType);
if (cbor_value_map_find_value(&it, "rc", &rc)) {
BAIL_OUT(ERR_NO_RESPONSE_CODE);
2019-05-02 12:54:43 -04:00
}
int r;
cbor_ensure_success(cbor_value_get_int(&rc, &r));
if (r) {
BAIL_OUT(ERR_CLIENT_REPORTED);
2019-05-02 12:54:43 -04:00
}
if (cbor_value_map_find_value(&it, "off", &rc)) {
fprintf(stderr, "Failed to find an offset in the response %s:%d\n", __FILE__, __LINE__);
} else {
cbor_ensure_success(cbor_value_get_int(&rc, &r));
2019-05-08 11:53:08 -04:00
uint8_t percent_complete = round(((double)r/len)*100);
printf("\033[80D%d/%lu Written (%d%%)", r, len, percent_complete);
2019-05-08 11:53:08 -04:00
fflush(stdout);
2019-05-02 12:54:43 -04:00
}
pos += actual_size;
free(out);
2019-05-02 12:54:43 -04:00
}
printf("\n");
}
void fix_and_upload(char *filename) {
char *version = get_version();
bool has_new_bootloader = false;
if (version && !strcmp(version, "0.3.2.0") ) {
// Upload bootloader update payload
printf("Bootloader update required, uploading...\n");
do_upload(NULL, false);
// Reset into controller
printf("Starting Controller\n");
gpio_ble_req_controller();
sleep(1);
// Update bootloader
printf("Triggering bootloader upgrade\n");
write(serial_fd, fixkey_msg, sizeof(fixkey_msg));
sleep(3);
// Reset into new bootloader
printf("Reseting into new bootloader\n");
gpio_ble_req_bootloader();
usleep(50000);
has_new_bootloader = true;
}
// Do the update
printf("Writing %s into slot 1\n", filename);
do_upload(filename, image_get_fast_mode() || has_new_bootloader); // If we just updated the BL, it supports fast
}
2019-06-19 13:39:53 -04:00
void image_set_no_fix(void) {
g_no_fix = true;
}
void image_main(int fd, int argc, char **argv){
gpio_ble_req_bootloader();
serial_fd = fd;
if (argc < 1) {
image_help();
return;
}
if (!strcmp(argv[0], "version")) {
char *version = get_version();
if (version) {
printf("%s\n", version);
free(version);
} else {
printf("No application image found.\n");
}
} else if (!strcmp(argv[0], "upload")) {
2019-05-02 12:54:43 -04:00
if (argc < 2) {
fprintf(stderr, "umcumgr image upload <binary file>\n");
}
2019-06-19 13:39:53 -04:00
if (g_no_fix) {
printf("Writing %s into slot 1\n", argv[1]);
do_upload(argv[1], image_get_fast_mode());
} else {
fix_and_upload(argv[1]);
}
} else {
image_help();
}
gpio_ble_req_controller();
}