/* * Copyright (c) 2023 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(mcumgr_grp_img_client, CONFIG_MCUMGR_GRP_IMG_CLIENT_LOG_LEVEL); #define MCUMGR_UPLOAD_INIT_HEADER_BUF_SIZE 128 /* Pointer for active Client */ static struct img_mgmt_client *active_client; /* Image State read or set response pointer */ static struct mcumgr_image_state *image_info; /* Image upload response pointer */ static struct mcumgr_image_upload *image_upload_buf; static K_SEM_DEFINE(mcumgr_img_client_grp_sem, 0, 1); static K_MUTEX_DEFINE(mcumgr_img_client_grp_mutex); static const char smp_images_str[] = "images"; #define IMAGES_STR_LEN (sizeof(smp_images_str) - 1) static int image_state_res_fn(struct net_buf *nb, void *user_data) { zcbor_state_t zsd[CONFIG_MCUMGR_SMP_CBOR_MAX_DECODING_LEVELS + 2]; struct zcbor_string value = {0}; int buf_len, rc; uint32_t img_num, slot_num; struct zcbor_string hash, version; bool bootable, pending, confirmed, active, permanent, ok; size_t decoded; struct zcbor_map_decode_key_val list_res_decode[] = { /* Mandatory */ ZCBOR_MAP_DECODE_KEY_DECODER("version", zcbor_tstr_decode, &version), ZCBOR_MAP_DECODE_KEY_DECODER("hash", zcbor_bstr_decode, &hash), ZCBOR_MAP_DECODE_KEY_DECODER("slot", zcbor_uint32_decode, &slot_num), /* Optional */ ZCBOR_MAP_DECODE_KEY_DECODER("image", zcbor_uint32_decode, &img_num), ZCBOR_MAP_DECODE_KEY_DECODER("bootable", zcbor_bool_decode, &bootable), ZCBOR_MAP_DECODE_KEY_DECODER("pending", zcbor_bool_decode, &pending), ZCBOR_MAP_DECODE_KEY_DECODER("confirmed", zcbor_bool_decode, &confirmed), ZCBOR_MAP_DECODE_KEY_DECODER("active", zcbor_bool_decode, &active), ZCBOR_MAP_DECODE_KEY_DECODER("permanent", zcbor_bool_decode, &permanent) }; buf_len = active_client->image_list_length; if (!nb) { image_info->status = MGMT_ERR_ETIMEOUT; goto out; } zcbor_new_decode_state(zsd, ARRAY_SIZE(zsd), nb->data, nb->len, 1, NULL, 0); ok = zcbor_map_start_decode(zsd); if (!ok) { image_info->status = MGMT_ERR_ECORRUPT; goto out; } ok = zcbor_tstr_decode(zsd, &value); if (!ok) { image_info->status = MGMT_ERR_ECORRUPT; goto out; } if (value.len != IMAGES_STR_LEN || memcmp(value.value, smp_images_str, IMAGES_STR_LEN)) { image_info->status = MGMT_ERR_EINVAL; goto out; } ok = zcbor_list_start_decode(zsd); if (!ok) { image_info->status = MGMT_ERR_ECORRUPT; goto out; } rc = 0; /* Parse Image map info to configured buffer */ while (rc == 0) { decoded = 0; zcbor_map_decode_bulk_reset(list_res_decode, ARRAY_SIZE(list_res_decode)); /* Init buffer values */ active = false; bootable = false; confirmed = false; pending = false; permanent = false; img_num = 0; slot_num = UINT32_MAX; hash.len = 0; version.len = 0; rc = zcbor_map_decode_bulk(zsd, list_res_decode, ARRAY_SIZE(list_res_decode), &decoded); if (rc) { if (image_info->image_list_length) { /* No more map */ break; } LOG_ERR("Corrupted Image data %d", rc); image_info->status = MGMT_ERR_EINVAL; goto out; } /* Check that mandatory parameters have decoded */ if (hash.len != IMG_MGMT_DATA_SHA_LEN || !version.len || !zcbor_map_decode_bulk_key_found(list_res_decode, ARRAY_SIZE(list_res_decode), "slot")) { LOG_ERR("Missing mandatory parametrs"); image_info->status = MGMT_ERR_EINVAL; goto out; } /* Store parsed values */ if (buf_len) { image_info->image_list[image_info->image_list_length].img_num = img_num; image_info->image_list[image_info->image_list_length].slot_num = slot_num; memcpy(image_info->image_list[image_info->image_list_length].hash, hash.value, IMG_MGMT_DATA_SHA_LEN); if (version.len > IMG_MGMT_VER_MAX_STR_LEN) { LOG_WRN("Version truncated len %d -> %d", version.len, IMG_MGMT_VER_MAX_STR_LEN); version.len = IMG_MGMT_VER_MAX_STR_LEN; } memcpy(image_info->image_list[image_info->image_list_length].version, version.value, version.len); image_info->image_list[image_info->image_list_length].version[version.len] = '\0'; /* Set Image flags */ image_info->image_list[image_info->image_list_length].flags.bootable = bootable; image_info->image_list[image_info->image_list_length].flags.pending = pending; image_info->image_list[image_info->image_list_length].flags.confirmed = confirmed; image_info->image_list[image_info->image_list_length].flags.active = active; image_info->image_list[image_info->image_list_length].flags.permanent = permanent; /* Update valid image count */ image_info->image_list_length++; buf_len--; } else { LOG_INF("User configured image list buffer size %d can't store all info", active_client->image_list_length); } } ok = zcbor_list_end_decode(zsd); if (!ok) { image_info->status = MGMT_ERR_ECORRUPT; } else { image_info->status = MGMT_ERR_EOK; } out: if (image_info->status != MGMT_ERR_EOK) { image_info->image_list_length = 0; } rc = image_info->status; k_sem_give(user_data); return rc; } static int image_upload_res_fn(struct net_buf *nb, void *user_data) { zcbor_state_t zsd[CONFIG_MCUMGR_SMP_CBOR_MAX_DECODING_LEVELS + 2]; size_t decoded; int rc; int32_t res_rc = MGMT_ERR_EOK; struct zcbor_map_decode_key_val upload_res_decode[] = { ZCBOR_MAP_DECODE_KEY_DECODER("off", zcbor_size_decode, &image_upload_buf->image_upload_offset), ZCBOR_MAP_DECODE_KEY_DECODER("rc", zcbor_int32_decode, &res_rc)}; if (!nb) { image_upload_buf->status = MGMT_ERR_ETIMEOUT; goto end; } zcbor_new_decode_state(zsd, ARRAY_SIZE(zsd), nb->data, nb->len, 1, NULL, 0); rc = zcbor_map_decode_bulk(zsd, upload_res_decode, ARRAY_SIZE(upload_res_decode), &decoded); if (rc || image_upload_buf->image_upload_offset == SIZE_MAX) { image_upload_buf->status = MGMT_ERR_EINVAL; goto end; } image_upload_buf->status = res_rc; active_client->upload.offset = image_upload_buf->image_upload_offset; end: /* Set status for Upload request handler */ rc = image_upload_buf->status; k_sem_give(user_data); return rc; } static int erase_res_fn(struct net_buf *nb, void *user_data) { zcbor_state_t zsd[CONFIG_MCUMGR_SMP_CBOR_MAX_DECODING_LEVELS + 2]; size_t decoded; int rc, status = MGMT_ERR_EOK; struct zcbor_map_decode_key_val upload_res_decode[] = { ZCBOR_MAP_DECODE_KEY_DECODER("rc", zcbor_int32_decode, &status) }; if (!nb) { active_client->status = MGMT_ERR_ETIMEOUT; goto end; } zcbor_new_decode_state(zsd, ARRAY_SIZE(zsd), nb->data, nb->len, 1, NULL, 0); rc = zcbor_map_decode_bulk(zsd, upload_res_decode, ARRAY_SIZE(upload_res_decode), &decoded); if (rc) { LOG_ERR("Erase fail %d", rc); active_client->status = MGMT_ERR_EINVAL; goto end; } active_client->status = status; end: rc = active_client->status; k_sem_give(user_data); return rc; } static size_t upload_message_header_size(struct img_gr_upload *upload_state) { bool ok; size_t cbor_length; int map_count; zcbor_state_t zse[CONFIG_MCUMGR_SMP_CBOR_MAX_DECODING_LEVELS + 2]; uint8_t temp_buf[MCUMGR_UPLOAD_INIT_HEADER_BUF_SIZE]; uint8_t temp_data = 0U; /* Calculation of message header with data length of 1 */ zcbor_new_encode_state(zse, ARRAY_SIZE(zse), temp_buf, MCUMGR_UPLOAD_INIT_HEADER_BUF_SIZE, 0); if (upload_state->hash_initialized) { map_count = 12; } else { map_count = 10; } /* Init map start and write image info and data */ ok = zcbor_map_start_encode(zse, map_count) && zcbor_tstr_put_lit(zse, "image") && zcbor_uint32_put(zse, upload_state->image_num) && zcbor_tstr_put_lit(zse, "data") && zcbor_bstr_encode_ptr(zse, &temp_data, 1) && zcbor_tstr_put_lit(zse, "len") && zcbor_size_put(zse, upload_state->image_size) && zcbor_tstr_put_lit(zse, "off") && zcbor_size_put(zse, upload_state->offset); /* Write hash when it defined and offset is 0 */ if (ok && upload_state->hash_initialized) { ok = zcbor_tstr_put_lit(zse, "sha") && zcbor_bstr_encode_ptr(zse, upload_state->sha256, IMG_MGMT_DATA_SHA_LEN); } if (ok) { ok = zcbor_map_end_encode(zse, map_count); } if (!ok) { LOG_ERR("Failed to encode Image Upload packet"); return 0; } cbor_length = zse->payload - temp_buf; /* Return Message header length */ return cbor_length + (CONFIG_MCUMGR_GRP_IMG_UPLOAD_DATA_ALIGNMENT_SIZE - 1); } void img_mgmt_client_init(struct img_mgmt_client *client, struct smp_client_object *smp_client, int image_list_size, struct mcumgr_image_data *image_list) { client->smp_client = smp_client; client->image_list_length = image_list_size; client->image_list = image_list; } int img_mgmt_client_upload_init(struct img_mgmt_client *client, size_t image_size, uint32_t image_num, const char *image_hash) { int rc; k_mutex_lock(&mcumgr_img_client_grp_mutex, K_FOREVER); client->upload.image_size = image_size; client->upload.offset = 0; client->upload.image_num = image_num; if (image_hash) { memcpy(client->upload.sha256, image_hash, IMG_MGMT_DATA_SHA_LEN); client->upload.hash_initialized = true; } else { client->upload.hash_initialized = false; } /* Calculate worst case header size for adapt payload length */ client->upload.upload_header_size = upload_message_header_size(&client->upload); if (client->upload.upload_header_size) { rc = MGMT_ERR_EOK; } else { rc = MGMT_ERR_ENOMEM; } k_mutex_unlock(&mcumgr_img_client_grp_mutex); return rc; } int img_mgmt_client_upload(struct img_mgmt_client *client, const uint8_t *data, size_t length, struct mcumgr_image_upload *res_buf) { struct net_buf *nb; const uint8_t *write_ptr; int rc; uint32_t map_count; bool ok; size_t write_length, max_data_length, offset_before_send, request_length, wrote_length; zcbor_state_t zse[CONFIG_MCUMGR_SMP_CBOR_MAX_DECODING_LEVELS + 2]; k_mutex_lock(&mcumgr_img_client_grp_mutex, K_FOREVER); active_client = client; image_upload_buf = res_buf; request_length = length; wrote_length = 0; /* Calculate max data length based on * net_buf size - (SMP header + CBOR message_len + 16-bit CRC + 16-bit length) */ max_data_length = CONFIG_MCUMGR_TRANSPORT_NETBUF_SIZE - (active_client->upload.upload_header_size + MGMT_HDR_SIZE + 2U + 2U); /* Trim length based on CONFIG_MCUMGR_GRP_IMG_UPLOAD_DATA_ALIGNMENT_SIZE */ if (max_data_length % CONFIG_MCUMGR_GRP_IMG_UPLOAD_DATA_ALIGNMENT_SIZE) { max_data_length -= (max_data_length % CONFIG_MCUMGR_GRP_IMG_UPLOAD_DATA_ALIGNMENT_SIZE); } while (request_length != wrote_length) { write_ptr = data + wrote_length; write_length = request_length - wrote_length; if (write_length > max_data_length) { write_length = max_data_length; } nb = smp_client_buf_allocation(active_client->smp_client, MGMT_GROUP_ID_IMAGE, IMG_MGMT_ID_UPLOAD, MGMT_OP_WRITE, SMP_MCUMGR_VERSION_1); if (!nb) { image_upload_buf->status = MGMT_ERR_ENOMEM; goto end; } zcbor_new_encode_state(zse, ARRAY_SIZE(zse), nb->data + nb->len, net_buf_tailroom(nb), 0); if (active_client->upload.offset) { map_count = 6; } else if (active_client->upload.hash_initialized) { map_count = 12; } else { map_count = 10; } /* Init map start and write image info, data and offset */ ok = zcbor_map_start_encode(zse, map_count) && zcbor_tstr_put_lit(zse, "image") && zcbor_uint32_put(zse, active_client->upload.image_num) && zcbor_tstr_put_lit(zse, "data") && zcbor_bstr_encode_ptr(zse, write_ptr, write_length) && zcbor_tstr_put_lit(zse, "off") && zcbor_size_put(zse, active_client->upload.offset); /* Write Len and configured hash when offset is zero */ if (ok && !active_client->upload.offset) { ok = zcbor_tstr_put_lit(zse, "len") && zcbor_size_put(zse, active_client->upload.image_size); if (ok && active_client->upload.hash_initialized) { ok = zcbor_tstr_put_lit(zse, "sha") && zcbor_bstr_encode_ptr(zse, active_client->upload.sha256, IMG_MGMT_DATA_SHA_LEN); } } if (ok) { ok = zcbor_map_end_encode(zse, map_count); } if (!ok) { LOG_ERR("Failed to encode Image Upload packet"); smp_packet_free(nb); image_upload_buf->status = MGMT_ERR_ENOMEM; goto end; } offset_before_send = active_client->upload.offset; nb->len = zse->payload - nb->data; k_sem_reset(&mcumgr_img_client_grp_sem); image_upload_buf->status = MGMT_ERR_EINVAL; image_upload_buf->image_upload_offset = SIZE_MAX; rc = smp_client_send_cmd(active_client->smp_client, nb, image_upload_res_fn, &mcumgr_img_client_grp_sem, CONFIG_MCUMGR_GRP_IMG_FLASH_OPERATION_TIMEOUT); if (rc) { LOG_ERR("Failed to send SMP Upload init packet, err: %d", rc); smp_packet_free(nb); image_upload_buf->status = rc; goto end; } k_sem_take(&mcumgr_img_client_grp_sem, K_FOREVER); if (image_upload_buf->status) { LOG_ERR("Upload Fail: %d", image_upload_buf->status); goto end; } if (offset_before_send + write_length < active_client->upload.offset) { /* Offset further than expected which indicate upload session resume */ goto end; } wrote_length += write_length; } end: rc = image_upload_buf->status; active_client = NULL; image_upload_buf = NULL; k_mutex_unlock(&mcumgr_img_client_grp_mutex); return rc; } int img_mgmt_client_state_write(struct img_mgmt_client *client, char *hash, bool confirm, struct mcumgr_image_state *res_buf) { struct net_buf *nb; int rc; uint32_t map_count; zcbor_state_t zse[CONFIG_MCUMGR_SMP_CBOR_MAX_DECODING_LEVELS]; bool ok; k_mutex_lock(&mcumgr_img_client_grp_mutex, K_FOREVER); active_client = client; image_info = res_buf; /* Init Response */ res_buf->image_list_length = 0; res_buf->image_list = active_client->image_list; nb = smp_client_buf_allocation(active_client->smp_client, MGMT_GROUP_ID_IMAGE, IMG_MGMT_ID_STATE, MGMT_OP_WRITE, SMP_MCUMGR_VERSION_1); if (!nb) { res_buf->status = MGMT_ERR_ENOMEM; goto end; } zcbor_new_encode_state(zse, ARRAY_SIZE(zse), nb->data + nb->len, net_buf_tailroom(nb), 0); if (hash) { map_count = 4; } else { map_count = 2; } /* Write map start init and confirm params */ ok = zcbor_map_start_encode(zse, map_count) && zcbor_tstr_put_lit(zse, "confirm") && zcbor_bool_put(zse, confirm); /* Write hash data */ if (ok && hash) { ok = zcbor_tstr_put_lit(zse, "hash") && zcbor_bstr_encode_ptr(zse, hash, IMG_MGMT_DATA_SHA_LEN); } /* Close map */ if (ok) { ok = zcbor_map_end_encode(zse, map_count); } if (!ok) { smp_packet_free(nb); res_buf->status = MGMT_ERR_ENOMEM; goto end; } nb->len = zse->payload - nb->data; k_sem_reset(&mcumgr_img_client_grp_sem); rc = smp_client_send_cmd(active_client->smp_client, nb, image_state_res_fn, &mcumgr_img_client_grp_sem, CONFIG_SMP_CMD_DEFAULT_LIFE_TIME); if (rc) { res_buf->status = rc; smp_packet_free(nb); goto end; } k_sem_take(&mcumgr_img_client_grp_sem, K_FOREVER); end: rc = res_buf->status; active_client = NULL; k_mutex_unlock(&mcumgr_img_client_grp_mutex); return rc; } int img_mgmt_client_state_read(struct img_mgmt_client *client, struct mcumgr_image_state *res_buf) { struct net_buf *nb; int rc; zcbor_state_t zse[CONFIG_MCUMGR_SMP_CBOR_MAX_DECODING_LEVELS]; bool ok; k_mutex_lock(&mcumgr_img_client_grp_mutex, K_FOREVER); active_client = client; /* Init Response */ res_buf->image_list_length = 0; res_buf->image_list = active_client->image_list; image_info = res_buf; nb = smp_client_buf_allocation(active_client->smp_client, MGMT_GROUP_ID_IMAGE, IMG_MGMT_ID_STATE, MGMT_OP_READ, SMP_MCUMGR_VERSION_1); if (!nb) { res_buf->status = MGMT_ERR_ENOMEM; goto end; } zcbor_new_encode_state(zse, ARRAY_SIZE(zse), nb->data + nb->len, net_buf_tailroom(nb), 0); ok = zcbor_map_start_encode(zse, 1) && zcbor_map_end_encode(zse, 1); if (!ok) { smp_packet_free(nb); res_buf->status = MGMT_ERR_ENOMEM; goto end; } nb->len = zse->payload - nb->data; k_sem_reset(&mcumgr_img_client_grp_sem); rc = smp_client_send_cmd(active_client->smp_client, nb, image_state_res_fn, &mcumgr_img_client_grp_sem, CONFIG_SMP_CMD_DEFAULT_LIFE_TIME); if (rc) { smp_packet_free(nb); res_buf->status = rc; goto end; } k_sem_take(&mcumgr_img_client_grp_sem, K_FOREVER); end: rc = res_buf->status; image_info = NULL; active_client = NULL; k_mutex_unlock(&mcumgr_img_client_grp_mutex); return rc; } int img_mgmt_client_erase(struct img_mgmt_client *client, uint32_t slot) { struct net_buf *nb; int rc; zcbor_state_t zse[CONFIG_MCUMGR_SMP_CBOR_MAX_DECODING_LEVELS]; bool ok; k_mutex_lock(&mcumgr_img_client_grp_mutex, K_FOREVER); active_client = client; nb = smp_client_buf_allocation(active_client->smp_client, MGMT_GROUP_ID_IMAGE, IMG_MGMT_ID_ERASE, MGMT_OP_WRITE, SMP_MCUMGR_VERSION_1); if (!nb) { active_client->status = MGMT_ERR_ENOMEM; goto end; } zcbor_new_encode_state(zse, ARRAY_SIZE(zse), nb->data + nb->len, net_buf_tailroom(nb), 0); ok = zcbor_map_start_encode(zse, 2) && zcbor_tstr_put_lit(zse, "slot") && zcbor_uint32_put(zse, slot) && zcbor_map_end_encode(zse, 2); if (!ok) { smp_packet_free(nb); active_client->status = MGMT_ERR_ENOMEM; goto end; } nb->len = zse->payload - nb->data; k_sem_reset(&mcumgr_img_client_grp_sem); rc = smp_client_send_cmd(client->smp_client, nb, erase_res_fn, &mcumgr_img_client_grp_sem, CONFIG_MCUMGR_GRP_IMG_FLASH_OPERATION_TIMEOUT); if (rc) { smp_packet_free(nb); active_client->status = rc; goto end; } k_sem_take(&mcumgr_img_client_grp_sem, K_FOREVER); end: rc = active_client->status; active_client = NULL; k_mutex_unlock(&mcumgr_img_client_grp_mutex); return rc; }