blob: 9ab8ee02dcd9105d6ccb725c6d78503342fca1f8 [file] [log] [blame]
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zcbor_common.h>
#include <zcbor_decode.h>
#include <zcbor_encode.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/net/buf.h>
#include <zephyr/mgmt/mcumgr/mgmt/mgmt.h>
#include <zephyr/mgmt/mcumgr/smp/smp.h>
#include <zephyr/mgmt/mcumgr/smp/smp_client.h>
#include <zephyr/mgmt/mcumgr/transport/smp.h>
#include <zephyr/mgmt/mcumgr/grp/img_mgmt/img_mgmt.h>
#include <zephyr/mgmt/mcumgr/grp/img_mgmt/img_mgmt_client.h>
#include <mgmt/mcumgr/util/zcbor_bulk.h>
#include <mgmt/mcumgr/transport/smp_internal.h>
#include <zephyr/logging/log.h>
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);
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);
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);
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;
/* 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;
}