/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include #include #include #include "cborattr/cborattr.h" #include "mgmt/mgmt.h" #include "img_mgmt/image.h" #include "img_mgmt/img_mgmt.h" #include "img_mgmt/img_mgmt_impl.h" #include "img_mgmt_priv.h" #include "img_mgmt/img_mgmt_config.h" static mgmt_handler_fn img_mgmt_upload; static mgmt_handler_fn img_mgmt_erase; static img_mgmt_upload_fn *img_mgmt_upload_cb; static void *img_mgmt_upload_arg; const img_mgmt_dfu_callbacks_t *img_mgmt_dfu_callbacks_fn; struct img_mgmt_state g_img_mgmt_state; static const struct mgmt_handler img_mgmt_handlers[] = { [IMG_MGMT_ID_STATE] = { .mh_read = img_mgmt_state_read, .mh_write = img_mgmt_state_write, }, [IMG_MGMT_ID_UPLOAD] = { .mh_read = NULL, .mh_write = img_mgmt_upload }, [IMG_MGMT_ID_ERASE] = { .mh_read = NULL, .mh_write = img_mgmt_erase }, }; #define IMG_MGMT_HANDLER_CNT \ sizeof(img_mgmt_handlers) / sizeof(img_mgmt_handlers[0]) static struct mgmt_group img_mgmt_group = { .mg_handlers = (struct mgmt_handler *)img_mgmt_handlers, .mg_handlers_count = IMG_MGMT_HANDLER_CNT, .mg_group_id = MGMT_GROUP_ID_IMAGE, }; #if IMG_MGMT_VERBOSE_ERR const char *img_mgmt_err_str_app_reject = "app reject"; const char *img_mgmt_err_str_hdr_malformed = "header malformed"; const char *img_mgmt_err_str_magic_mismatch = "magic mismatch"; const char *img_mgmt_err_str_no_slot = "no slot"; const char *img_mgmt_err_str_flash_open_failed = "fa open fail"; const char *img_mgmt_err_str_flash_erase_failed = "fa erase fail"; const char *img_mgmt_err_str_flash_write_failed = "fa write fail"; const char *img_mgmt_err_str_downgrade = "downgrade"; const char *img_mgmt_err_str_image_bad_flash_addr = "img addr mismatch"; #endif /** * Finds the TLVs in the specified image slot, if any. */ static int img_mgmt_find_tlvs(int slot, size_t *start_off, size_t *end_off, uint16_t magic) { struct image_tlv_info tlv_info; int rc; rc = img_mgmt_impl_read(slot, *start_off, &tlv_info, sizeof tlv_info); if (rc != 0) { /* Read error. */ return MGMT_ERR_EUNKNOWN; } if (tlv_info.it_magic != magic) { /* No TLVs. */ return MGMT_ERR_ENOENT; } *start_off += sizeof tlv_info; *end_off = *start_off + tlv_info.it_tlv_tot; return 0; } /* * Reads the version and build hash from the specified image slot. */ int img_mgmt_read_info(int image_slot, struct image_version *ver, uint8_t *hash, uint32_t *flags) { #if IMG_MGMT_DUMMY_HDR uint8_t dummy_hash[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; if (!hash && !ver && !flags) { return 0; } if (hash) { memcpy(hash, dummy_hash, IMG_MGMT_HASH_LEN); } if (ver) { memset(ver, 0xff, sizeof *ver); } if (flags) { *flags = 0; } return 0; #endif struct image_header hdr; struct image_tlv tlv; size_t data_off; size_t data_end; bool hash_found; uint8_t erased_val; uint32_t erased_val_32; int rc; rc = img_mgmt_impl_erased_val(image_slot, &erased_val); if (rc != 0) { return MGMT_ERR_EUNKNOWN; } rc = img_mgmt_impl_read(image_slot, 0, &hdr, sizeof hdr); if (rc != 0) { return MGMT_ERR_EUNKNOWN; } if (ver != NULL) { memset(ver, erased_val, sizeof(*ver)); } erased_val_32 = ERASED_VAL_32(erased_val); if (hdr.ih_magic == IMAGE_MAGIC) { if (ver != NULL) { memcpy(ver, &hdr.ih_ver, sizeof(*ver)); } } else if (hdr.ih_magic == erased_val_32) { return MGMT_ERR_ENOENT; } else { return MGMT_ERR_EUNKNOWN; } if (flags != NULL) { *flags = hdr.ih_flags; } /* Read the image's TLVs. We first try to find the protected TLVs, if the protected * TLV does not exist, we try to find non-protected TLV which also contains the hash * TLV. All images are required to have a hash TLV. If the hash is missing, the image * is considered invalid. */ data_off = hdr.ih_hdr_size + hdr.ih_img_size; rc = img_mgmt_find_tlvs(image_slot, &data_off, &data_end, IMAGE_TLV_PROT_INFO_MAGIC); if (!rc) { /* The data offset should start after the header bytes after the end of the protected TLV, * if one exists. */ data_off = data_end - sizeof(struct image_tlv_info); } rc = img_mgmt_find_tlvs(image_slot, &data_off, &data_end, IMAGE_TLV_INFO_MAGIC); if (rc != 0) { return MGMT_ERR_EUNKNOWN; } hash_found = false; while (data_off + sizeof tlv <= data_end) { rc = img_mgmt_impl_read(image_slot, data_off, &tlv, sizeof tlv); if (rc != 0) { return MGMT_ERR_EUNKNOWN; } if (tlv.it_type == 0xff && tlv.it_len == 0xffff) { return MGMT_ERR_EUNKNOWN; } if (tlv.it_type != IMAGE_TLV_SHA256 || tlv.it_len != IMAGE_HASH_LEN) { /* Non-hash TLV. Skip it. */ data_off += sizeof tlv + tlv.it_len; continue; } if (hash_found) { /* More than one hash. */ return MGMT_ERR_EUNKNOWN; } hash_found = true; data_off += sizeof tlv; if (hash != NULL) { if (data_off + IMAGE_HASH_LEN > data_end) { return MGMT_ERR_EUNKNOWN; } rc = img_mgmt_impl_read(image_slot, data_off, hash, IMAGE_HASH_LEN); if (rc != 0) { return MGMT_ERR_EUNKNOWN; } } } if (!hash_found) { return MGMT_ERR_EUNKNOWN; } return 0; } /* * Finds image given version number. Returns the slot number image is in, * or -1 if not found. */ int img_mgmt_find_by_ver(struct image_version *find, uint8_t *hash) { int i; struct image_version ver; for (i = 0; i < 2 * IMG_MGMT_UPDATABLE_IMAGE_NUMBER; i++) { if (img_mgmt_read_info(i, &ver, hash, NULL) != 0) { continue; } if (!memcmp(find, &ver, sizeof(ver))) { return i; } } return -1; } /* * Finds image given hash of the image. Returns the slot number image is in, * or -1 if not found. */ int img_mgmt_find_by_hash(uint8_t *find, struct image_version *ver) { int i; uint8_t hash[IMAGE_HASH_LEN]; for (i = 0; i < 2 * IMG_MGMT_UPDATABLE_IMAGE_NUMBER; i++) { if (img_mgmt_read_info(i, ver, hash, NULL) != 0) { continue; } if (!memcmp(hash, find, IMAGE_HASH_LEN)) { return i; } } return -1; } #if IMG_MGMT_VERBOSE_ERR int img_mgmt_error_rsp(struct mgmt_ctxt *ctxt, int rc, const char *rsn) { /* * This is an error response so returning a different error when failed to * encode other error probably does not make much sense - just ignore errors * here. */ cbor_encode_text_stringz(&ctxt->encoder, "rsn"); cbor_encode_text_stringz(&ctxt->encoder, rsn); return rc; } #endif /** * Command handler: image erase */ static int img_mgmt_erase(struct mgmt_ctxt *ctxt) { struct image_version ver; CborError err; int rc; /* * First check if image info is valid. * This check is done incase the flash area has a corrupted image. */ rc = img_mgmt_read_info(1, &ver, NULL, NULL); if (rc == 0) { /* Image info is valid. */ if (img_mgmt_slot_in_use(1)) { /* No free slot. */ return MGMT_ERR_EBADSTATE; } } rc = img_mgmt_impl_erase_slot(); if (!rc) { img_mgmt_dfu_stopped(); } err = 0; err |= cbor_encode_text_stringz(&ctxt->encoder, "rc"); err |= cbor_encode_int(&ctxt->encoder, rc); if (err != 0) { return MGMT_ERR_ENOMEM; } return 0; } static int img_mgmt_upload_good_rsp(struct mgmt_ctxt *ctxt) { CborError err = CborNoError; err |= cbor_encode_text_stringz(&ctxt->encoder, "rc"); err |= cbor_encode_int(&ctxt->encoder, MGMT_ERR_EOK); err |= cbor_encode_text_stringz(&ctxt->encoder, "off"); err |= cbor_encode_int(&ctxt->encoder, g_img_mgmt_state.off); if (err != 0) { return MGMT_ERR_ENOMEM; } return 0; } /** * Logs an upload request if necessary. * * @param is_first Whether the request includes the first chunk of * the image. * @param is_last Whether the request includes the last chunk of * the image. * @param status The result of processing the upload request * (MGMT_ERR code). * * @return 0 on success; nonzero on failure. */ static int img_mgmt_upload_log(bool is_first, bool is_last, int status) { uint8_t hash[IMAGE_HASH_LEN]; const uint8_t *hashp; int rc; if (is_first) { return img_mgmt_impl_log_upload_start(status); } if (is_last || status != 0) { /* Log the image hash if we know it. */ rc = img_mgmt_read_info(1, NULL, hash, NULL); if (rc != 0) { hashp = NULL; } else { hashp = hash; } return img_mgmt_impl_log_upload_done(status, hashp); } /* Nothing to log. */ return 0; } /** * Command handler: image upload */ static int img_mgmt_upload(struct mgmt_ctxt *ctxt) { struct mgmt_evt_op_cmd_status_arg cmd_status_arg; struct img_mgmt_upload_req req = { .off = -1, .size = -1, .data_len = 0, .data_sha_len = 0, .upgrade = false, .image = 0, }; const struct cbor_attr_t off_attr[] = { [0] = { .attribute = "image", .type = CborAttrUnsignedIntegerType, .addr.uinteger = &req.image, .nodefault = true }, [1] = { .attribute = "data", .type = CborAttrByteStringType, .addr.bytestring.data = req.img_data, .addr.bytestring.len = &req.data_len, .len = sizeof(req.img_data) }, [2] = { .attribute = "len", .type = CborAttrUnsignedIntegerType, .addr.uinteger = &req.size, .nodefault = true }, [3] = { .attribute = "off", .type = CborAttrUnsignedIntegerType, .addr.uinteger = &req.off, .nodefault = true }, [4] = { .attribute = "sha", .type = CborAttrByteStringType, .addr.bytestring.data = req.data_sha, .addr.bytestring.len = &req.data_sha_len, .len = sizeof(req.data_sha) }, [5] = { .attribute = "upgrade", .type = CborAttrBooleanType, .addr.boolean = &req.upgrade, .dflt.boolean = false, }, [6] = { 0 }, }; int rc; const char *errstr = NULL; struct img_mgmt_upload_action action; bool last = false; rc = cbor_read_object(&ctxt->it, off_attr); if (rc != 0) { return MGMT_ERR_EINVAL; } /* Determine what actions to take as a result of this request. */ rc = img_mgmt_impl_upload_inspect(&req, &action, &errstr); if (rc != 0) { img_mgmt_dfu_stopped(); return rc; } if (!action.proceed) { /* Request specifies incorrect offset. Respond with a success code and * the correct offset. */ return img_mgmt_upload_good_rsp(ctxt); } /* Request is valid. Give the application a chance to reject this upload * request. */ if (img_mgmt_upload_cb != NULL) { rc = img_mgmt_upload_cb(req.off, action.size, img_mgmt_upload_arg); if (rc != 0) { errstr = img_mgmt_err_str_app_reject; goto end; } } /* Remember flash area ID and image size for subsequent upload requests. */ g_img_mgmt_state.area_id = action.area_id; g_img_mgmt_state.size = action.size; if (req.off == 0) { /* * New upload. */ g_img_mgmt_state.off = 0; img_mgmt_dfu_started(); cmd_status_arg.status = IMG_MGMT_ID_UPLOAD_STATUS_START; /* * We accept SHA trimmed to any length by client since it's up to client * to make sure provided data are good enough to avoid collisions when * resuming upload. */ g_img_mgmt_state.data_sha_len = req.data_sha_len; memcpy(g_img_mgmt_state.data_sha, req.data_sha, req.data_sha_len); memset(&g_img_mgmt_state.data_sha[req.data_sha_len], 0, IMG_MGMT_DATA_SHA_LEN - req.data_sha_len); #if IMG_MGMT_LAZY_ERASE /* setup for lazy sector by sector erase */ g_img_mgmt_state.sector_id = -1; g_img_mgmt_state.sector_end = 0; #else /* erase the entire req.size all at once */ if (action.erase) { rc = img_mgmt_impl_erase_image_data(0, req.size); if (rc != 0) { rc = MGMT_ERR_EUNKNOWN; errstr = img_mgmt_err_str_flash_erase_failed; goto end; } } #endif } else { cmd_status_arg.status = IMG_MGMT_ID_UPLOAD_STATUS_ONGOING; } /* Write the image data to flash. */ if (req.data_len != 0) { #if IMG_MGMT_LAZY_ERASE /* erase as we cross sector boundaries */ if (img_mgmt_impl_erase_if_needed(req.off, action.write_bytes) != 0) { rc = MGMT_ERR_EUNKNOWN; errstr = img_mgmt_err_str_flash_erase_failed; goto end; } #endif /* If this is the last chunk */ if (g_img_mgmt_state.off + req.data_len == g_img_mgmt_state.size) { last = true; } rc = img_mgmt_impl_write_image_data(req.off, req.img_data, action.write_bytes, last); if (rc != 0) { rc = MGMT_ERR_EUNKNOWN; errstr = img_mgmt_err_str_flash_write_failed; goto end; } else { g_img_mgmt_state.off += action.write_bytes; if (g_img_mgmt_state.off == g_img_mgmt_state.size) { /* Done */ img_mgmt_dfu_pending(); cmd_status_arg.status = IMG_MGMT_ID_UPLOAD_STATUS_COMPLETE; g_img_mgmt_state.area_id = -1; } } } end: img_mgmt_upload_log(req.off == 0, g_img_mgmt_state.off == g_img_mgmt_state.size, rc); mgmt_evt(MGMT_EVT_OP_CMD_STATUS, MGMT_GROUP_ID_IMAGE, IMG_MGMT_ID_UPLOAD, &cmd_status_arg); if (rc != 0) { img_mgmt_dfu_stopped(); return img_mgmt_error_rsp(ctxt, rc, errstr); } return img_mgmt_upload_good_rsp(ctxt); } void img_mgmt_dfu_stopped(void) { if (img_mgmt_dfu_callbacks_fn && img_mgmt_dfu_callbacks_fn->dfu_stopped_cb) { img_mgmt_dfu_callbacks_fn->dfu_stopped_cb(); } } void img_mgmt_dfu_started(void) { if (img_mgmt_dfu_callbacks_fn && img_mgmt_dfu_callbacks_fn->dfu_started_cb) { img_mgmt_dfu_callbacks_fn->dfu_started_cb(); } } void img_mgmt_dfu_pending(void) { if (img_mgmt_dfu_callbacks_fn && img_mgmt_dfu_callbacks_fn->dfu_pending_cb) { img_mgmt_dfu_callbacks_fn->dfu_pending_cb(); } } void img_mgmt_dfu_confirmed(void) { if (img_mgmt_dfu_callbacks_fn && img_mgmt_dfu_callbacks_fn->dfu_confirmed_cb) { img_mgmt_dfu_callbacks_fn->dfu_confirmed_cb(); } } void img_mgmt_set_upload_cb(img_mgmt_upload_fn *cb, void *arg) { img_mgmt_upload_cb = cb; img_mgmt_upload_arg = arg; } void img_mgmt_register_callbacks(const img_mgmt_dfu_callbacks_t *cb_struct) { img_mgmt_dfu_callbacks_fn = cb_struct; } int img_mgmt_my_version(struct image_version *ver) { return img_mgmt_read_info(IMG_MGMT_BOOT_CURR_SLOT, ver, NULL, NULL); } void img_mgmt_register_group(void) { mgmt_register_group(&img_mgmt_group); } void img_mgmt_unregister_group(void) { mgmt_unregister_group(&img_mgmt_group); }