diff --git a/components/bt/esp_ble_mesh/v1.1/dfu/dfd.h b/components/bt/esp_ble_mesh/v1.1/dfu/dfd.h new file mode 100644 index 0000000000..3af9af7214 --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/dfu/dfd.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define BT_MESH_DFD_OP_RECEIVERS_ADD BT_MESH_MODEL_OP_2(0x83, 0x11) +#define BT_MESH_DFD_OP_RECEIVERS_DELETE_ALL BT_MESH_MODEL_OP_2(0x83, 0x12) +#define BT_MESH_DFD_OP_RECEIVERS_STATUS BT_MESH_MODEL_OP_2(0x83, 0x13) +#define BT_MESH_DFD_OP_RECEIVERS_GET BT_MESH_MODEL_OP_2(0x83, 0x14) +#define BT_MESH_DFD_OP_RECEIVERS_LIST BT_MESH_MODEL_OP_2(0x83, 0x15) +#define BT_MESH_DFD_OP_CAPABILITIES_GET BT_MESH_MODEL_OP_2(0x83, 0x16) +#define BT_MESH_DFD_OP_CAPABILITIES_STATUS BT_MESH_MODEL_OP_2(0x83, 0x17) +#define BT_MESH_DFD_OP_GET BT_MESH_MODEL_OP_2(0x83, 0x18) +#define BT_MESH_DFD_OP_START BT_MESH_MODEL_OP_2(0x83, 0x19) +#define BT_MESH_DFD_OP_SUSPEND BT_MESH_MODEL_OP_2(0x83, 0x1a) +#define BT_MESH_DFD_OP_CANCEL BT_MESH_MODEL_OP_2(0x83, 0x1b) +#define BT_MESH_DFD_OP_APPLY BT_MESH_MODEL_OP_2(0x83, 0x1c) +#define BT_MESH_DFD_OP_STATUS BT_MESH_MODEL_OP_2(0x83, 0x1d) +#define BT_MESH_DFD_OP_UPLOAD_GET BT_MESH_MODEL_OP_2(0x83, 0x1e) +#define BT_MESH_DFD_OP_UPLOAD_START BT_MESH_MODEL_OP_2(0x83, 0x1f) +#define BT_MESH_DFD_OP_UPLOAD_START_OOB BT_MESH_MODEL_OP_2(0x83, 0x20) +#define BT_MESH_DFD_OP_UPLOAD_CANCEL BT_MESH_MODEL_OP_2(0x83, 0x21) +#define BT_MESH_DFD_OP_UPLOAD_STATUS BT_MESH_MODEL_OP_2(0x83, 0x22) +#define BT_MESH_DFD_OP_FW_GET BT_MESH_MODEL_OP_2(0x83, 0x23) +#define BT_MESH_DFD_OP_FW_GET_BY_INDEX BT_MESH_MODEL_OP_2(0x83, 0x24) +#define BT_MESH_DFD_OP_FW_DELETE BT_MESH_MODEL_OP_2(0x83, 0x25) +#define BT_MESH_DFD_OP_FW_DELETE_ALL BT_MESH_MODEL_OP_2(0x83, 0x26) +#define BT_MESH_DFD_OP_FW_STATUS BT_MESH_MODEL_OP_2(0x83, 0x27) diff --git a/components/bt/esp_ble_mesh/v1.1/dfu/dfd_srv.c b/components/bt/esp_ble_mesh/v1.1/dfu/dfd_srv.c new file mode 100644 index 0000000000..4a395a175b --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/dfu/dfd_srv.c @@ -0,0 +1,1306 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "dfu_slot.h" +#include "dfd.h" +#include "dfu.h" +#include "dfd_srv_internal.h" +#include "net.h" +#include "transport.h" + +#define LOG_LEVEL CONFIG_BT_MESH_DFU_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_dfd_srv); + +#define ERASE_BLOCK_SIZE DT_PROP(DT_CHOSEN(zephyr_flash), erase_block_size) + +#define DFD_UPLOAD_STATUS_MSG_MAXLEN (5 + CONFIG_BT_MESH_DFU_FWID_MAXLEN) + +BUILD_ASSERT((DFD_UPLOAD_STATUS_MSG_MAXLEN + BT_MESH_MODEL_OP_LEN(BT_MESH_DFD_OP_UPLOAD_STATUS) + + BT_MESH_MIC_SHORT) <= BT_MESH_TX_SDU_MAX, + "The Firmware Distribution Upload Status message does not fit into the maximum " + "outgoing SDU size."); + +#define DFD_UPLOAD_START_MSG_MAXLEN (16 + CONFIG_BT_MESH_DFU_FWID_MAXLEN + \ + CONFIG_BT_MESH_DFU_METADATA_MAXLEN) + +BUILD_ASSERT((DFD_UPLOAD_START_MSG_MAXLEN + BT_MESH_MODEL_OP_LEN(BT_MESH_DFD_OP_UPLOAD_START) + + BT_MESH_MIC_SHORT) <= BT_MESH_RX_SDU_MAX, + "The Firmware Distribution Upload Start message does not fit into the maximum " + "incoming SDU size."); + +#define DFD_RECEIVERS_LIST_MSG_MAXLEN (4 + CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX * 5) + +BUILD_ASSERT((DFD_RECEIVERS_LIST_MSG_MAXLEN + BT_MESH_MODEL_OP_LEN(BT_MESH_DFD_OP_RECEIVERS_LIST) + + BT_MESH_MIC_SHORT) <= BT_MESH_TX_SDU_MAX, + "The Firmware Distribution Receivers List message does not fit into the maximum " + "outgoing SDU size."); + +#define DFD_RECEIVERS_ADD_MSG_MAXLEN (CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX * 3) + +BUILD_ASSERT((DFD_RECEIVERS_ADD_MSG_MAXLEN + BT_MESH_MODEL_OP_LEN(BT_MESH_DFD_OP_RECEIVERS_ADD) + + BT_MESH_MIC_SHORT) <= BT_MESH_RX_SDU_MAX, + "The Firmware Distribution Receivers Add message does not fit into the maximum " + "incoming SDU size."); + +struct slot_search_ctx { + off_t offset; + size_t size; + bool failed; +}; + +static void dfd_phase_set(struct bt_mesh_dfd_srv *srv, + enum bt_mesh_dfd_phase new_phase) +{ + srv->phase = new_phase; + + if (srv->cb && srv->cb->phase) { + srv->cb->phase(srv, srv->phase); + } +} + +static struct bt_mesh_dfu_target *target_get(struct bt_mesh_dfd_srv *srv, + uint16_t addr) +{ + for (int i = 0; i < srv->target_cnt; ++i) { + if (addr == srv->targets[i].blob.addr) { + return &srv->targets[i]; + } + } + + return NULL; +} + +static bool is_busy(const struct bt_mesh_dfd_srv *srv) +{ + return srv->phase == BT_MESH_DFD_PHASE_TRANSFER_ACTIVE || + srv->phase == BT_MESH_DFD_PHASE_TRANSFER_SUCCESS || + srv->phase == BT_MESH_DFD_PHASE_APPLYING_UPDATE; +} + +static bool upload_is_busy(const struct bt_mesh_dfd_srv *srv) +{ + return bt_mesh_blob_srv_is_busy(&srv->upload.blob) || + srv->upload.phase == BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ACTIVE; +} + +static int slot_del(struct bt_mesh_dfd_srv *srv, const struct bt_mesh_dfu_slot *slot) +{ + if (srv->cb && srv->cb->del) { + srv->cb->del(srv, slot); + } + + return bt_mesh_dfu_slot_del(slot); +} + +static void receivers_status_rsp(struct bt_mesh_dfd_srv *srv, + struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_dfd_status status) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFD_OP_RECEIVERS_STATUS, 3); + bt_mesh_model_msg_init(&buf, BT_MESH_DFD_OP_RECEIVERS_STATUS); + + net_buf_simple_add_u8(&buf, status); + net_buf_simple_add_le16(&buf, srv->target_cnt); + + bt_mesh_model_send(srv->mod, ctx, &buf, NULL, NULL); +} + +static int handle_receivers_add(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + enum bt_mesh_dfd_status status = BT_MESH_DFD_SUCCESS; + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + if (buf->len % 3) { + return -EINVAL; + } + + if (bt_mesh_dfu_cli_is_busy(&srv->dfu)) { + receivers_status_rsp(srv, ctx, + BT_MESH_DFD_ERR_BUSY_WITH_DISTRIBUTION); + return 0; + } + + while (buf->len >= 3 && status == BT_MESH_DFD_SUCCESS) { + uint8_t img_idx; + uint16_t addr; + + addr = net_buf_simple_pull_le16(buf); + img_idx = net_buf_simple_pull_u8(buf); + + status = bt_mesh_dfd_srv_receiver_add(srv, addr, img_idx); + } + + receivers_status_rsp(srv, ctx, status); + + return 0; +} + +static int handle_receivers_delete_all(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + receivers_status_rsp(srv, ctx, bt_mesh_dfd_srv_receivers_delete_all(srv)); + + return 0; +} + +static int handle_receivers_get(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + uint16_t first, cnt; + uint8_t progress; + int i; + + first = net_buf_simple_pull_le16(buf); + cnt = net_buf_simple_pull_le16(buf); + if (cnt == 0) { + return -EINVAL; + } + + /* Create a buffer that can fit the full target list, maxing out at TX_SDU_MAX: */ + NET_BUF_SIMPLE_DEFINE( + rsp, BT_MESH_MODEL_BUF_LEN(BT_MESH_DFD_OP_RECEIVERS_LIST, + DFD_RECEIVERS_LIST_MSG_MAXLEN)); + bt_mesh_model_msg_init(&rsp, BT_MESH_DFD_OP_RECEIVERS_LIST); + + net_buf_simple_add_le16(&rsp, srv->target_cnt); + net_buf_simple_add_le16(&rsp, first); + + cnt = MIN(cnt, srv->target_cnt - first); + progress = bt_mesh_dfu_cli_progress(&srv->dfu) / 2; + + for (i = 0; i < cnt && net_buf_simple_tailroom(&rsp) >= 5 + BT_MESH_MIC_SHORT; i++) { + const struct bt_mesh_dfu_target *t = &srv->targets[i + first]; + + net_buf_simple_add_le32( + &rsp, ((t->blob.addr & BIT_MASK(15)) | + ((t->phase & BIT_MASK(4)) << 15U) | + ((t->status & BIT_MASK(3)) << 19U) | + ((t->blob.status & BIT_MASK(4)) << 22U) | + ((progress & BIT_MASK(6)) << 26U))); + net_buf_simple_add_u8(&rsp, t->img_idx); + } + + bt_mesh_model_send(srv->mod, ctx, &rsp, NULL, NULL); + + return 0; +} + +static enum bt_mesh_dfu_iter slot_space_cb(const struct bt_mesh_dfu_slot *slot, + void *user_data) +{ + size_t *total = user_data; + + *total += slot->size; + + return BT_MESH_DFU_ITER_CONTINUE; +} + +static int handle_capabilities_get(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + size_t size = 0; + + BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_DFD_OP_CAPABILITIES_STATUS, 17); + bt_mesh_model_msg_init(&rsp, BT_MESH_DFD_OP_CAPABILITIES_STATUS); + + net_buf_simple_add_le16(&rsp, CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX); + net_buf_simple_add_le16(&rsp, CONFIG_BT_MESH_DFU_SLOT_CNT); + net_buf_simple_add_le32(&rsp, CONFIG_BT_MESH_DFD_SRV_SLOT_MAX_SIZE); + net_buf_simple_add_le32(&rsp, CONFIG_BT_MESH_DFD_SRV_SLOT_SPACE); + + /* Remaining size */ + (void)bt_mesh_dfu_slot_foreach(slot_space_cb, &size); + size = MIN(size, CONFIG_BT_MESH_DFD_SRV_SLOT_SPACE); + + net_buf_simple_add_le32(&rsp, CONFIG_BT_MESH_DFD_SRV_SLOT_SPACE - size); + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + if (srv->oob_schemes.count > 0) { + net_buf_simple_add_u8(&rsp, 1); + net_buf_simple_add_mem(&rsp, srv->oob_schemes.schemes, + srv->oob_schemes.count); + } else +#endif + { + net_buf_simple_add_u8(&rsp, 0); + } + + bt_mesh_model_send(mod, ctx, &rsp, NULL, NULL); + + return 0; +} + +static void status_rsp(struct bt_mesh_dfd_srv *srv, struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_dfd_status status) +{ + BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_DFD_OP_STATUS, 12); + bt_mesh_model_msg_init(&rsp, BT_MESH_DFD_OP_STATUS); + + net_buf_simple_add_u8(&rsp, status); + net_buf_simple_add_u8(&rsp, srv->phase); + + if (srv->phase == BT_MESH_DFD_PHASE_IDLE || !srv->dfu.xfer.slot) { + bt_mesh_model_send(srv->mod, ctx, &rsp, NULL, NULL); + return; + } + + net_buf_simple_add_le16(&rsp, srv->inputs.group); + net_buf_simple_add_le16(&rsp, srv->inputs.app_idx); + net_buf_simple_add_u8(&rsp, srv->inputs.ttl); + net_buf_simple_add_le16(&rsp, srv->inputs.timeout_base); + net_buf_simple_add_u8(&rsp, ((srv->dfu.xfer.blob.mode & BIT_MASK(2)) | + ((srv->apply & BIT_MASK(1)) << 2))); + net_buf_simple_add_le16(&rsp, srv->slot_idx); + + bt_mesh_model_send(srv->mod, ctx, &rsp, NULL, NULL); +} + +static int handle_get(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + + return 0; +} + +static int handle_start(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + struct bt_mesh_dfd_start_params params; + uint8_t byte; + + params.app_idx = net_buf_simple_pull_le16(buf); + params.ttl = net_buf_simple_pull_u8(buf); + params.timeout_base = net_buf_simple_pull_le16(buf); + byte = net_buf_simple_pull_u8(buf); + params.xfer_mode = byte & BIT_MASK(2); + params.apply = (byte >> 2U) & BIT_MASK(1); + params.slot_idx = net_buf_simple_pull_le16(buf); + + if (buf->len == 16) { + /* TODO: Virtual addresses not supported. */ + status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + return 0; + } + + if (buf->len != 2) { + return -EINVAL; + } + + params.group = net_buf_simple_pull_le16(buf); + + status_rsp(srv, ctx, bt_mesh_dfd_srv_start(srv, ¶ms)); + + return 0; +} + +static int handle_suspend(const struct bt_mesh_model *mod, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + status_rsp(srv, ctx, bt_mesh_dfd_srv_suspend(srv)); + + return 0; +} + +static int handle_cancel(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + bt_mesh_dfd_srv_cancel(srv, ctx); + + return 0; +} + +static int handle_apply(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + status_rsp(srv, ctx, bt_mesh_dfd_srv_apply(srv)); + + return 0; +} + +static void upload_status_rsp_with_progress(struct bt_mesh_dfd_srv *srv, + struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_dfd_status status, + uint8_t progress) +{ + BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_DFD_OP_UPLOAD_STATUS, + DFD_UPLOAD_STATUS_MSG_MAXLEN); + bt_mesh_model_msg_init(&rsp, BT_MESH_DFD_OP_UPLOAD_STATUS); + + net_buf_simple_add_u8(&rsp, status); + net_buf_simple_add_u8(&rsp, srv->upload.phase); + + if (srv->upload.phase == BT_MESH_DFD_UPLOAD_PHASE_IDLE || + !srv->upload.slot) { + bt_mesh_model_send(srv->mod, ctx, &rsp, NULL, NULL); + return; + } + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + if (srv->upload.is_oob) { + net_buf_simple_add_u8(&rsp, progress | BIT(7)); + net_buf_simple_add_mem(&rsp, srv->upload.oob.current_fwid, + srv->upload.oob.current_fwid_len); + } else +#endif + { + net_buf_simple_add_u8(&rsp, progress); + net_buf_simple_add_mem(&rsp, srv->upload.slot->fwid, + srv->upload.slot->fwid_len); + } + + bt_mesh_model_send(srv->mod, ctx, &rsp, NULL, NULL); +} + +static void upload_status_rsp(struct bt_mesh_dfd_srv *srv, + struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_dfd_status status) +{ + uint8_t progress; + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + if (srv->upload.is_oob) { + progress = srv->cb->oob_progress_get(srv, srv->upload.slot); + } else +#endif + { + progress = bt_mesh_blob_srv_progress(&srv->upload.blob); + } + + upload_status_rsp_with_progress(srv, ctx, status, progress); +} + +static int handle_upload_get(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + upload_status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + + return 0; +} + +static inline int set_upload_fwid(struct bt_mesh_dfd_srv *srv, struct bt_mesh_msg_ctx *ctx, + const uint8_t *fwid, size_t fwid_len) +{ + int err = bt_mesh_dfu_slot_fwid_set(srv->upload.slot, fwid, fwid_len); + + switch (err) { + case -EFBIG: /* Fwid too long */ + case -EALREADY: /* Other server is in progress with this fwid */ + bt_mesh_dfu_slot_release(srv->upload.slot); + srv->upload.slot = NULL; + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + break; + case -EEXIST: /* Img with this fwid already is in list */ + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_SUCCESS; + bt_mesh_dfu_slot_release(srv->upload.slot); + + err = bt_mesh_dfu_slot_get(fwid, fwid_len, &srv->upload.slot); + if (!err) { + upload_status_rsp_with_progress(srv, ctx, BT_MESH_DFD_SUCCESS, 100); + } else { + srv->upload.slot = NULL; + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + } + break; + case 0: + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ACTIVE; + break; + case -EINVAL: /* Slot in wrong state. */ + default: + break; + } + + return err; +} + +static int handle_upload_start(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + size_t meta_len, fwid_len, size; + const uint8_t *meta, *fwid; + uint16_t timeout_base; + uint64_t blob_id; + int err; + uint8_t ttl; + + ttl = net_buf_simple_pull_u8(buf); + timeout_base = net_buf_simple_pull_le16(buf); + blob_id = net_buf_simple_pull_le64(buf); + size = net_buf_simple_pull_le32(buf); + meta_len = net_buf_simple_pull_u8(buf); + if (buf->len < meta_len) { + return -EINVAL; + } + + meta = net_buf_simple_pull_mem(buf, meta_len); + fwid_len = buf->len; + fwid = net_buf_simple_pull_mem(buf, fwid_len); + + LOG_DBG("Upload Start: size: %d, fwid: %s, metadata: %s", size, bt_hex(fwid, fwid_len), + bt_hex(meta, meta_len)); + + if (size > CONFIG_BT_MESH_DFD_SRV_SLOT_MAX_SIZE) { + upload_status_rsp(srv, ctx, + BT_MESH_DFD_ERR_INSUFFICIENT_RESOURCES); + return 0; + } + + if (upload_is_busy(srv)) { + if (!srv->upload.slot) { + LOG_WRN("Busy with no upload slot"); + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + return 0; + } + + if (srv->upload.slot->fwid_len == fwid_len && + !memcmp(srv->upload.slot->fwid, fwid, fwid_len) && + srv->upload.slot->metadata_len == meta_len && + !memcmp(srv->upload.slot->metadata, meta, meta_len) && + srv->upload.blob.state.xfer.id == blob_id && + srv->upload.blob.state.ttl == ttl && + srv->upload.blob.state.timeout_base == timeout_base +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + && !srv->upload.is_oob +#endif + ) { + LOG_DBG("Duplicate upload start"); + upload_status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + return 0; + } + + LOG_WRN("Upload already in progress"); + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_BUSY_WITH_UPLOAD); + return 0; + } + + /* This will be a no-op if the slot state isn't RESERVED, which is + * what we want. + */ + if (srv->upload.slot) { + bt_mesh_dfu_slot_release(srv->upload.slot); + } + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + srv->upload.is_oob = false; +#endif + srv->upload.slot = bt_mesh_dfu_slot_reserve(); + + if (!srv->upload.slot) { + LOG_WRN("No space for slot"); + upload_status_rsp(srv, ctx, + BT_MESH_DFD_ERR_INSUFFICIENT_RESOURCES); + return 0; + } + + err = set_upload_fwid(srv, ctx, fwid, fwid_len); + if (err) { + return err; + } + + err = bt_mesh_dfu_slot_info_set(srv->upload.slot, size, meta, meta_len); + switch (err) { + case -EFBIG: + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + break; + case 0: + break; + default: + return err; + } + + srv->io = NULL; + err = srv->cb->recv(srv, srv->upload.slot, &srv->io); + if (err || !srv->io) { + LOG_ERR("App rejected upload. err: %d io: %p", err, srv->io); + bt_mesh_dfu_slot_release(srv->upload.slot); + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + return 0; + } + + err = bt_mesh_blob_srv_recv(&srv->upload.blob, blob_id, srv->io, ttl, + timeout_base); + if (err) { + LOG_ERR("BLOB Server rejected upload (err: %d)", err); + bt_mesh_dfu_slot_release(srv->upload.slot); + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + return 0; + } + + LOG_DBG("%s", bt_hex(fwid, fwid_len)); + + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ACTIVE; + upload_status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + + return 0; +} + +static int handle_upload_start_oob(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + uint8_t uri_len; + uint8_t *uri; + uint16_t fwid_len; + uint8_t *fwid; + + uri_len = net_buf_simple_pull_u8(buf); + + if (uri_len > buf->len) { + return -EINVAL; + } + + uri = net_buf_simple_pull_mem(buf, uri_len); + fwid_len = buf->len; + fwid = net_buf_simple_pull_mem(buf, fwid_len); + + LOG_DBG("Upload OOB Start"); + LOG_HEXDUMP_DBG(uri, uri_len, "URI"); + LOG_HEXDUMP_DBG(fwid, fwid_len, "FWID"); + + if (upload_is_busy(srv)) { +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + if (srv->upload.is_oob && + uri_len == srv->upload.oob.uri_len && + fwid_len == srv->upload.oob.current_fwid_len && + !memcmp(uri, srv->upload.oob.uri, uri_len) && + !memcmp(fwid, srv->upload.oob.current_fwid, fwid_len)) { + /* Same image, return SUCCESS for idempotency */ + upload_status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + return 0; + } +#endif + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_BUSY_WITH_UPLOAD); + return 0; +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + } else if (srv->upload.is_oob && srv->upload.is_pending_oob_check) { + /* Ignore the request if we didn't confirm the previous one. */ + return 0; +#endif + } + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + if (uri_len > CONFIG_BT_MESH_DFU_URI_MAXLEN || + fwid_len > CONFIG_BT_MESH_DFU_FWID_MAXLEN) { + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + return 0; + } + + struct bt_mesh_dfu_slot *slot = bt_mesh_dfu_slot_reserve(); + + if (slot == NULL) { + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INSUFFICIENT_RESOURCES); + return 0; + } + + /* This will be a no-op if the slot state isn't RESERVED, which is + * what we want. + */ + if (srv->upload.slot) { + bt_mesh_dfu_slot_release(srv->upload.slot); + } + + srv->upload.is_oob = true; + srv->upload.slot = slot; + memcpy(srv->upload.oob.uri, uri, uri_len); + srv->upload.oob.uri_len = uri_len; + memcpy(srv->upload.oob.current_fwid, fwid, fwid_len); + srv->upload.oob.current_fwid_len = fwid_len; + memcpy(&srv->upload.oob.ctx, ctx, sizeof(struct bt_mesh_msg_ctx)); + + int status = srv->cb->start_oob_upload(srv, srv->upload.slot, srv->upload.oob.uri, + srv->upload.oob.uri_len, + srv->upload.oob.current_fwid, + srv->upload.oob.current_fwid_len); + + if (status != BT_MESH_DFD_SUCCESS) { + upload_status_rsp(srv, ctx, status); + bt_mesh_dfu_slot_release(srv->upload.slot); + } else { + srv->upload.is_pending_oob_check = true; + } +#else + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_URI_NOT_SUPPORTED); +#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */ + + return 0; +} + +static int handle_upload_cancel(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_IDLE; +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + if (srv->upload.is_oob) { + srv->cb->cancel_oob_upload(srv, srv->upload.slot); + } else +#endif + { + (void)bt_mesh_blob_srv_cancel(&srv->upload.blob); + } + upload_status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + + return 0; +} + +static void fw_status_rsp(struct bt_mesh_dfd_srv *srv, + struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_dfd_status status, uint16_t idx, + const uint8_t *fwid, size_t fwid_len) +{ + BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_DFD_OP_FW_STATUS, + 7 + CONFIG_BT_MESH_DFU_FWID_MAXLEN); + bt_mesh_model_msg_init(&rsp, BT_MESH_DFD_OP_FW_STATUS); + + net_buf_simple_add_u8(&rsp, status); + net_buf_simple_add_le16(&rsp, bt_mesh_dfu_slot_count()); + + net_buf_simple_add_le16(&rsp, idx); + if (fwid) { + net_buf_simple_add_mem(&rsp, fwid, fwid_len); + } + + bt_mesh_model_send(srv->mod, ctx, &rsp, NULL, NULL); +} + +static int handle_fw_get(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + struct bt_mesh_dfu_slot *slot; + const uint8_t *fwid; + size_t fwid_len; + int idx; + + fwid_len = buf->len; + fwid = net_buf_simple_pull_mem(buf, fwid_len); + + idx = bt_mesh_dfu_slot_get(fwid, fwid_len, &slot); + if (idx >= 0) { + fw_status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS, idx, fwid, + fwid_len); + } else { + fw_status_rsp(srv, ctx, BT_MESH_DFD_ERR_FW_NOT_FOUND, 0xffff, + fwid, fwid_len); + } + + return 0; +} + +static int handle_fw_get_by_index(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + const struct bt_mesh_dfu_slot *slot; + uint16_t idx; + + idx = net_buf_simple_pull_le16(buf); + + slot = bt_mesh_dfu_slot_at(idx); + if (slot) { + fw_status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS, idx, slot->fwid, + slot->fwid_len); + } else { + fw_status_rsp(srv, ctx, BT_MESH_DFD_ERR_FW_NOT_FOUND, idx, + NULL, 0); + } + + return 0; +} + +static int handle_fw_delete(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + const uint8_t *fwid; + size_t fwid_len; + + fwid_len = buf->len; + fwid = net_buf_simple_pull_mem(buf, fwid_len); + + enum bt_mesh_dfd_status status = bt_mesh_dfd_srv_fw_delete(srv, &fwid_len, &fwid); + + fw_status_rsp(srv, ctx, status, 0xffff, fwid, fwid_len); + + return 0; +} + +static enum bt_mesh_dfu_iter slot_del_cb(const struct bt_mesh_dfu_slot *slot, + void *user_data) +{ + struct bt_mesh_dfd_srv *srv = user_data; + + if (srv->cb && srv->cb->del) { + srv->cb->del(srv, slot); + } + + return BT_MESH_DFU_ITER_CONTINUE; +} + +static int handle_fw_delete_all(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + fw_status_rsp(srv, ctx, bt_mesh_dfd_srv_fw_delete_all(srv), 0xffff, NULL, 0); + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_dfd_srv_op[] = { + { BT_MESH_DFD_OP_RECEIVERS_ADD, BT_MESH_LEN_MIN(3), handle_receivers_add }, + { BT_MESH_DFD_OP_RECEIVERS_DELETE_ALL, BT_MESH_LEN_EXACT(0), handle_receivers_delete_all }, + { BT_MESH_DFD_OP_RECEIVERS_GET, BT_MESH_LEN_EXACT(4), handle_receivers_get }, + { BT_MESH_DFD_OP_CAPABILITIES_GET, BT_MESH_LEN_EXACT(0), handle_capabilities_get }, + { BT_MESH_DFD_OP_GET, BT_MESH_LEN_EXACT(0), handle_get }, + { BT_MESH_DFD_OP_START, BT_MESH_LEN_MIN(10), handle_start }, + { BT_MESH_DFD_OP_SUSPEND, BT_MESH_LEN_EXACT(0), handle_suspend }, + { BT_MESH_DFD_OP_CANCEL, BT_MESH_LEN_EXACT(0), handle_cancel }, + { BT_MESH_DFD_OP_APPLY, BT_MESH_LEN_EXACT(0), handle_apply }, + { BT_MESH_DFD_OP_UPLOAD_GET, BT_MESH_LEN_EXACT(0), handle_upload_get }, + { BT_MESH_DFD_OP_UPLOAD_START, BT_MESH_LEN_MIN(16), handle_upload_start }, + { BT_MESH_DFD_OP_UPLOAD_START_OOB, BT_MESH_LEN_MIN(2), handle_upload_start_oob }, + { BT_MESH_DFD_OP_UPLOAD_CANCEL, BT_MESH_LEN_EXACT(0), handle_upload_cancel }, + { BT_MESH_DFD_OP_FW_GET, BT_MESH_LEN_MIN(0), handle_fw_get }, + { BT_MESH_DFD_OP_FW_GET_BY_INDEX, BT_MESH_LEN_EXACT(2), handle_fw_get_by_index }, + { BT_MESH_DFD_OP_FW_DELETE, BT_MESH_LEN_MIN(0), handle_fw_delete }, + { BT_MESH_DFD_OP_FW_DELETE_ALL, BT_MESH_LEN_EXACT(0), handle_fw_delete_all }, + + BT_MESH_MODEL_OP_END +}; + +static void dfu_suspended(struct bt_mesh_dfu_cli *cli) +{ + struct bt_mesh_dfd_srv *srv = + CONTAINER_OF(cli, struct bt_mesh_dfd_srv, dfu); + + dfd_phase_set(srv, BT_MESH_DFD_PHASE_TRANSFER_SUSPENDED); +} + +static void dfu_ended(struct bt_mesh_dfu_cli *cli, + enum bt_mesh_dfu_status reason) +{ + struct bt_mesh_dfd_srv *srv = + CONTAINER_OF(cli, struct bt_mesh_dfd_srv, dfu); + int err; + + LOG_DBG("reason: %u, phase: %u, apply: %u", reason, srv->phase, srv->apply); + + if (srv->phase == BT_MESH_DFD_PHASE_IDLE) { + return; + } + + if (srv->phase == BT_MESH_DFD_PHASE_CANCELING_UPDATE) { + dfd_phase_set(srv, BT_MESH_DFD_PHASE_IDLE); + return; + } + + if (reason != BT_MESH_DFU_SUCCESS) { + dfd_phase_set(srv, BT_MESH_DFD_PHASE_FAILED); + return; + } + + if (!srv->apply) { + dfd_phase_set(srv, BT_MESH_DFD_PHASE_TRANSFER_SUCCESS); + return; + } + + dfd_phase_set(srv, BT_MESH_DFD_PHASE_APPLYING_UPDATE); + + err = bt_mesh_dfu_cli_apply(cli); + if (err) { + LOG_ERR("Apply failed: %d", err); + dfd_phase_set(srv, BT_MESH_DFD_PHASE_FAILED); + } +} + +static void dfu_applied(struct bt_mesh_dfu_cli *cli) +{ + struct bt_mesh_dfd_srv *srv = + CONTAINER_OF(cli, struct bt_mesh_dfd_srv, dfu); + int err; + + if (srv->phase == BT_MESH_DFD_PHASE_CANCELING_UPDATE) { + dfd_phase_set(srv, BT_MESH_DFD_PHASE_FAILED); + return; + } + + if (srv->phase != BT_MESH_DFD_PHASE_APPLYING_UPDATE) { + return; + } + + err = bt_mesh_dfu_cli_confirm(cli); + if (err) { + LOG_ERR("Confirm failed: %d", err); + dfd_phase_set(srv, BT_MESH_DFD_PHASE_FAILED); + } +} + +static void dfu_confirmed(struct bt_mesh_dfu_cli *cli) +{ + struct bt_mesh_dfd_srv *srv = + CONTAINER_OF(cli, struct bt_mesh_dfd_srv, dfu); + + if (srv->phase != BT_MESH_DFD_PHASE_APPLYING_UPDATE && + srv->phase != BT_MESH_DFD_PHASE_CANCELING_UPDATE) { + return; + } + + dfd_phase_set(srv, BT_MESH_DFD_PHASE_COMPLETED); +} + +const struct bt_mesh_dfu_cli_cb _bt_mesh_dfd_srv_dfu_cb = { + .suspended = dfu_suspended, + .ended = dfu_ended, + .applied = dfu_applied, + .confirmed = dfu_confirmed, +}; + +static int upload_start(struct bt_mesh_blob_srv *b, struct bt_mesh_msg_ctx *ctx, + struct bt_mesh_blob_xfer *xfer) +{ + LOG_DBG(""); + return 0; +} + +static void upload_end(struct bt_mesh_blob_srv *b, uint64_t id, bool success) +{ + struct bt_mesh_dfd_srv *srv = + CONTAINER_OF(b, struct bt_mesh_dfd_srv, upload.blob); + + if (srv->upload.phase != BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ACTIVE) { + return; + } + + LOG_DBG("%u", success); + + if (success && (bt_mesh_dfu_slot_commit(srv->upload.slot) == 0)) { + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_SUCCESS; + return; + } + + /* Will delete slot when we start a new upload */ + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ERROR; +} + +static void upload_timeout(struct bt_mesh_blob_srv *b) +{ + LOG_DBG(""); + + upload_end(b, b->state.xfer.id, false); +} + +const struct bt_mesh_blob_srv_cb _bt_mesh_dfd_srv_blob_cb = { + .start = upload_start, + .end = upload_end, + .suspended = upload_timeout, +}; + +static int dfd_srv_init(const struct bt_mesh_model *mod) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + srv->mod = mod; + + if (IS_ENABLED(CONFIG_BT_MESH_MODEL_EXTENSIONS)) { + bt_mesh_model_extend(mod, srv->upload.blob.mod); + } + + return 0; +} + +static void dfd_srv_reset(const struct bt_mesh_model *mod) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + dfd_phase_set(srv, BT_MESH_DFD_PHASE_IDLE); + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_IDLE; + + sys_slist_init(&srv->inputs.targets); + srv->target_cnt = 0; + + bt_mesh_dfu_slot_foreach(slot_del_cb, srv); + bt_mesh_dfu_slot_del_all(); +} + +const struct bt_mesh_model_cb _bt_mesh_dfd_srv_cb = { + .init = dfd_srv_init, + .reset = dfd_srv_reset, +}; + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_receiver_add(struct bt_mesh_dfd_srv *srv, uint16_t addr, + uint8_t img_idx) +{ + struct bt_mesh_dfu_target *t; + struct bt_mesh_blob_target_pull *p; + + if (!BT_MESH_ADDR_IS_UNICAST(addr)) { + return BT_MESH_DFD_SUCCESS; + } + + t = target_get(srv, addr); + if (t) { + t->img_idx = img_idx; + return BT_MESH_DFD_SUCCESS; + } + + /* New target node, add it to the list */ + + if (srv->target_cnt == ARRAY_SIZE(srv->targets)) { + return BT_MESH_DFD_ERR_INSUFFICIENT_RESOURCES; + } + + t = &srv->targets[srv->target_cnt]; + p = &srv->pull_ctxs[srv->target_cnt]; + srv->target_cnt++; + + memset(t, 0, sizeof(*t)); + memset(p, 0, sizeof(*p)); + t->blob.addr = addr; + t->blob.pull = p; + t->img_idx = img_idx; + + LOG_DBG("Added receiver 0x%04x img: %u", t->blob.addr, + t->img_idx); + + return BT_MESH_DFD_SUCCESS; +} + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_receivers_delete_all(struct bt_mesh_dfd_srv *srv) +{ + if (bt_mesh_dfu_cli_is_busy(&srv->dfu)) { + return BT_MESH_DFD_ERR_BUSY_WITH_DISTRIBUTION; + } + + sys_slist_init(&srv->inputs.targets); + srv->target_cnt = 0; + + return BT_MESH_DFD_SUCCESS; +} + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_start(struct bt_mesh_dfd_srv *srv, + struct bt_mesh_dfd_start_params *params) +{ + int err, i; + struct bt_mesh_dfu_cli_xfer xfer = { 0 }; + + if (!srv->target_cnt) { + return BT_MESH_DFD_ERR_RECEIVERS_LIST_EMPTY; + } + + if (!bt_mesh_app_key_exists(params->app_idx)) { + return BT_MESH_DFD_ERR_INVALID_APPKEY_INDEX; + } + + xfer.mode = params->xfer_mode; + xfer.slot = bt_mesh_dfu_slot_at(params->slot_idx); + if (!xfer.slot) { + return BT_MESH_DFD_ERR_FW_NOT_FOUND; + } + + if (srv->inputs.app_idx == params->app_idx && + srv->inputs.timeout_base == params->timeout_base && + srv->inputs.group == params->group && srv->inputs.ttl == params->ttl && + srv->dfu.xfer.blob.mode == xfer.mode && srv->apply == params->apply && + srv->slot_idx == params->slot_idx) { + if (is_busy(srv) || + srv->phase == BT_MESH_DFD_PHASE_COMPLETED) { + LOG_WRN("Already completed or in progress"); + return BT_MESH_DFD_SUCCESS; + } else if (srv->phase == BT_MESH_DFD_PHASE_TRANSFER_SUSPENDED) { + bt_mesh_dfu_cli_resume(&srv->dfu); + dfd_phase_set(srv, BT_MESH_DFD_PHASE_TRANSFER_ACTIVE); + return BT_MESH_DFD_SUCCESS; + } + } else if (is_busy(srv) || + srv->phase == BT_MESH_DFD_PHASE_TRANSFER_SUSPENDED) { + LOG_WRN("Busy with distribution"); + return BT_MESH_DFD_ERR_BUSY_WITH_DISTRIBUTION; + } + + if (srv->phase == BT_MESH_DFD_PHASE_CANCELING_UPDATE) { + LOG_WRN("Canceling distribution"); + return BT_MESH_DFD_ERR_BUSY_WITH_DISTRIBUTION; + } + + srv->io = NULL; + err = srv->cb->send(srv, xfer.slot, &srv->io); + if (err || !srv->io) { + return BT_MESH_DFD_ERR_INTERNAL; + } + + sys_slist_init(&srv->inputs.targets); + for (i = 0; i < srv->target_cnt; i++) { + uint16_t addr = srv->targets[i].blob.addr; + + memset(&srv->targets[i].blob, 0, sizeof(struct bt_mesh_blob_target)); + memset(&srv->pull_ctxs[i], 0, sizeof(struct bt_mesh_blob_target_pull)); + srv->targets[i].blob.addr = addr; + srv->targets[i].blob.pull = &srv->pull_ctxs[i]; + + sys_slist_append(&srv->inputs.targets, &srv->targets[i].blob.n); + } + + srv->slot_idx = params->slot_idx; + srv->inputs.app_idx = params->app_idx; + srv->inputs.timeout_base = params->timeout_base; + srv->inputs.group = params->group; + srv->inputs.ttl = params->ttl; + srv->apply = params->apply; + + LOG_DBG("Distribution Start: slot: %d, appidx: %d, tb: %d, addr: %04X, ttl: %d, apply: %d", + params->slot_idx, params->app_idx, params->timeout_base, params->group, params->ttl, + params->apply); + + /* DFD Server will always retrieve targets' capabilities before distributing a firmware.*/ + xfer.blob_params = NULL; + + dfd_phase_set(srv, BT_MESH_DFD_PHASE_TRANSFER_ACTIVE); + err = bt_mesh_dfu_cli_send(&srv->dfu, &srv->inputs, srv->io, &xfer); + if (err) { + dfd_phase_set(srv, BT_MESH_DFD_PHASE_IDLE); + return BT_MESH_DFD_ERR_INTERNAL; + } + + return BT_MESH_DFD_SUCCESS; +} + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_suspend(struct bt_mesh_dfd_srv *srv) +{ + int err; + + if (srv->phase == BT_MESH_DFD_PHASE_TRANSFER_SUSPENDED) { + return BT_MESH_DFD_SUCCESS; + } + + if (srv->phase != BT_MESH_DFD_PHASE_TRANSFER_ACTIVE) { + return BT_MESH_DFD_ERR_WRONG_PHASE; + } + + err = bt_mesh_dfu_cli_suspend(&srv->dfu); + if (err) { + return BT_MESH_DFD_ERR_SUSPEND_FAILED; + } + + srv->phase = BT_MESH_DFD_PHASE_TRANSFER_SUSPENDED; + return BT_MESH_DFD_SUCCESS; +} + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_cancel(struct bt_mesh_dfd_srv *srv, + struct bt_mesh_msg_ctx *ctx) +{ + enum bt_mesh_dfd_phase prev_phase; + int err; + + if (srv->phase == BT_MESH_DFD_PHASE_CANCELING_UPDATE || + srv->phase == BT_MESH_DFD_PHASE_IDLE) { + if (ctx != NULL) { + status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + } + return BT_MESH_DFD_SUCCESS; + } + + if (srv->phase == BT_MESH_DFD_PHASE_COMPLETED || + srv->phase == BT_MESH_DFD_PHASE_FAILED) { + dfd_phase_set(srv, BT_MESH_DFD_PHASE_IDLE); + if (ctx != NULL) { + status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + } + return BT_MESH_DFD_SUCCESS; + } + + /* Phase TRANSFER_ACTIVE, TRANSFER_SUSPENDED, TRANSFER_SUCCESS, APPLYING_UPDATE: */ + + prev_phase = srv->phase; + dfd_phase_set(srv, BT_MESH_DFD_PHASE_CANCELING_UPDATE); + err = bt_mesh_dfu_cli_cancel(&srv->dfu, NULL); + if (err) { + if (ctx != NULL) { + status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + } + return BT_MESH_DFD_ERR_INTERNAL; + } + + if (prev_phase == BT_MESH_DFD_PHASE_APPLYING_UPDATE && ctx) { + /* Disable randomization for the Firmware Distribution State message to avoid + * reordering when Firmware Distribution Server sends 2 messages in a row when + * cancelling the update (see section 6.2.3.10 of MshDFUv1.0). + */ + ctx->rnd_delay = false; + } + + if (ctx != NULL) { + status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + } + + if (prev_phase == BT_MESH_DFD_PHASE_APPLYING_UPDATE) { + dfd_phase_set(srv, BT_MESH_DFD_PHASE_IDLE); + if (ctx != NULL) { + status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + } + } + + return BT_MESH_DFD_SUCCESS; +} + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_apply(struct bt_mesh_dfd_srv *srv) +{ + int err; + + if (srv->phase == BT_MESH_DFD_PHASE_IDLE || + srv->phase == BT_MESH_DFD_PHASE_CANCELING_UPDATE || + srv->phase == BT_MESH_DFD_PHASE_TRANSFER_ACTIVE || + srv->phase == BT_MESH_DFD_PHASE_TRANSFER_SUSPENDED || + srv->phase == BT_MESH_DFD_PHASE_FAILED) { + return BT_MESH_DFD_ERR_WRONG_PHASE; + } + + if (srv->phase == BT_MESH_DFD_PHASE_APPLYING_UPDATE || + srv->phase == BT_MESH_DFD_PHASE_COMPLETED) { + return BT_MESH_DFD_SUCCESS; + } + + err = bt_mesh_dfu_cli_apply(&srv->dfu); + if (err) { + return BT_MESH_DFD_ERR_INTERNAL; + } + + dfd_phase_set(srv, BT_MESH_DFD_PHASE_APPLYING_UPDATE); + return BT_MESH_DFD_SUCCESS; +} + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_fw_delete(struct bt_mesh_dfd_srv *srv, size_t *fwid_len, + const uint8_t **fwid) +{ + struct bt_mesh_dfu_slot *slot; + int idx, err; + + if (srv->phase != BT_MESH_DFD_PHASE_IDLE) { + *fwid = NULL; + *fwid_len = 0; + return BT_MESH_DFD_ERR_BUSY_WITH_DISTRIBUTION; + } + + idx = bt_mesh_dfu_slot_get(*fwid, *fwid_len, &slot); + if (idx < 0) { + return BT_MESH_DFD_SUCCESS; + } + + err = slot_del(srv, slot); + if (err) { + *fwid = NULL; + *fwid_len = 0; + return BT_MESH_DFD_ERR_INTERNAL; + } else { + return BT_MESH_DFD_SUCCESS; + } +} + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_fw_delete_all(struct bt_mesh_dfd_srv *srv) +{ + if (srv->phase != BT_MESH_DFD_PHASE_IDLE) { + return BT_MESH_DFD_ERR_BUSY_WITH_DISTRIBUTION; + } + + bt_mesh_dfu_slot_foreach(slot_del_cb, srv); + + bt_mesh_dfu_slot_del_all(); + + return BT_MESH_DFD_SUCCESS; +} + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD +int bt_mesh_dfd_srv_oob_check_complete(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot, int status, + uint8_t *fwid, size_t fwid_len) +{ + int err; + + if (slot != srv->upload.slot || !srv->upload.is_oob || + srv->upload.phase == BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ACTIVE || + !srv->upload.is_pending_oob_check) { + /* This should not happen, unless the application calls the function with a + * "wrong" pointer or at a wrong time. + */ + return -EINVAL; + } + + srv->upload.is_pending_oob_check = false; + + if (status != BT_MESH_DFD_SUCCESS) { + bt_mesh_dfu_slot_release(srv->upload.slot); + upload_status_rsp(srv, &srv->upload.oob.ctx, status); + return -ECANCELED; + } + + err = set_upload_fwid(srv, &srv->upload.oob.ctx, fwid, fwid_len); + + if (err) { + return err; + } + + upload_status_rsp(srv, &srv->upload.oob.ctx, BT_MESH_DFD_SUCCESS); + return 0; +} + +int bt_mesh_dfd_srv_oob_store_complete(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot, bool success, + size_t size, const uint8_t *metadata, size_t metadata_len) +{ + int err = 0; + + if (srv->upload.phase != BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ACTIVE || + srv->upload.slot != slot || !srv->upload.is_oob) { + return -EINVAL; + } + + if (!success) { + goto error; + } + + err = bt_mesh_dfu_slot_info_set(srv->upload.slot, size, metadata, metadata_len); + if (err) { + goto error; + } + + err = bt_mesh_dfu_slot_commit(srv->upload.slot); + if (err) { + goto error; + } + + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_SUCCESS; + return 0; + +error: + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ERROR; + bt_mesh_dfu_slot_release(srv->upload.slot); + return err; +} +#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */ diff --git a/components/bt/esp_ble_mesh/v1.1/dfu/dfd_srv_internal.h b/components/bt/esp_ble_mesh/v1.1/dfu/dfd_srv_internal.h new file mode 100644 index 0000000000..12c7d943f6 --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/dfu/dfd_srv_internal.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_SRV_INTERNAL_H__ +#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_SRV_INTERNAL_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct bt_mesh_dfd_start_params { + uint16_t app_idx, timeout_base, slot_idx, group; + enum bt_mesh_blob_xfer_mode xfer_mode; + uint8_t ttl; + bool apply; +}; + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_receiver_add(struct bt_mesh_dfd_srv *srv, uint16_t addr, + uint8_t img_idx); + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_receivers_delete_all(struct bt_mesh_dfd_srv *srv); + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_start(struct bt_mesh_dfd_srv *srv, + struct bt_mesh_dfd_start_params *params); + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_suspend(struct bt_mesh_dfd_srv *srv); + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_cancel(struct bt_mesh_dfd_srv *srv, + struct bt_mesh_msg_ctx *ctx); + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_apply(struct bt_mesh_dfd_srv *srv); + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_fw_delete(struct bt_mesh_dfd_srv *srv, size_t *fwid_len, + const uint8_t **fwid); + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_fw_delete_all(struct bt_mesh_dfd_srv *srv); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_SRV_INTERNAL_H__ */ diff --git a/components/bt/esp_ble_mesh/v1.1/dfu/dfu.h b/components/bt/esp_ble_mesh/v1.1/dfu/dfu.h new file mode 100644 index 0000000000..c1c5aca46c --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/dfu/dfu.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define BT_MESH_DFU_OP_UPDATE_INFO_GET BT_MESH_MODEL_OP_2(0x83, 0x08) +#define BT_MESH_DFU_OP_UPDATE_INFO_STATUS BT_MESH_MODEL_OP_2(0x83, 0x09) + +#define BT_MESH_DFU_OP_UPDATE_METADATA_CHECK BT_MESH_MODEL_OP_2(0x83, 0x0a) +#define BT_MESH_DFU_OP_UPDATE_METADATA_STATUS BT_MESH_MODEL_OP_2(0x83, 0x0b) + +#define BT_MESH_DFU_OP_UPDATE_GET BT_MESH_MODEL_OP_2(0x83, 0x0c) +#define BT_MESH_DFU_OP_UPDATE_START BT_MESH_MODEL_OP_2(0x83, 0x0d) +#define BT_MESH_DFU_OP_UPDATE_CANCEL BT_MESH_MODEL_OP_2(0x83, 0x0e) +#define BT_MESH_DFU_OP_UPDATE_APPLY BT_MESH_MODEL_OP_2(0x83, 0x0f) +#define BT_MESH_DFU_OP_UPDATE_STATUS BT_MESH_MODEL_OP_2(0x83, 0x10) + +#define DFU_UPDATE_INFO_STATUS_MSG_MINLEN (4 + CONFIG_BT_MESH_DFU_FWID_MAXLEN + \ + CONFIG_BT_MESH_DFU_URI_MAXLEN) +#define DFU_UPDATE_START_MSG_MAXLEN (12 + CONFIG_BT_MESH_DFU_METADATA_MAXLEN) + +static inline uint16_t dfu_metadata_checksum(struct net_buf_simple *buf) +{ + /* Simple Fletcher-16 checksum to ensure duplicate start messages don't + * have different metadata. + */ + struct net_buf_simple_state state; + uint8_t sum[2] = {0, 0}; + + net_buf_simple_save(buf, &state); + + + while (buf->len) { + uint8_t byte = net_buf_simple_pull_u8(buf); + + sum[0] += byte; + sum[1] += sum[0]; + } + + net_buf_simple_restore(buf, &state); + + return (sum[0] << 8U) | sum[1]; +} diff --git a/components/bt/esp_ble_mesh/v1.1/dfu/dfu_cli.c b/components/bt/esp_ble_mesh/v1.1/dfu/dfu_cli.c new file mode 100644 index 0000000000..f02b884d22 --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/dfu/dfu_cli.c @@ -0,0 +1,1265 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "access.h" +#include "dfu.h" +#include "blob.h" +#include + +#define LOG_LEVEL CONFIG_BT_MESH_DFU_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_dfu_cli); + +#define TARGETS_FOR_EACH(cli, target) \ + SYS_SLIST_FOR_EACH_CONTAINER( \ + (sys_slist_t *)&((cli)->blob.inputs)->targets, target, blob.n) + +#define MSG_CTX(cli, dst) \ + { \ + .app_idx = (cli)->blob.inputs->app_idx, .addr = dst, \ + .send_ttl = (cli)->blob.inputs->ttl, \ + } + +#define DFU_CLI(blob_cli) CONTAINER_OF(blob_cli, struct bt_mesh_dfu_cli, blob) + +BUILD_ASSERT((DFU_UPDATE_START_MSG_MAXLEN + BT_MESH_MODEL_OP_LEN(BT_MESH_DFU_OP_UPDATE_START) + + BT_MESH_MIC_SHORT) <= BT_MESH_TX_SDU_MAX, + "The Firmware Update Start message does not fit into the maximum outgoing SDU size."); + +BUILD_ASSERT((DFU_UPDATE_INFO_STATUS_MSG_MINLEN + + BT_MESH_MODEL_OP_LEN(BT_MESH_DFU_OP_UPDATE_INFO_STATUS) + BT_MESH_MIC_SHORT) + <= BT_MESH_RX_SDU_MAX, + "The Firmware Update Info Status message does not fit into the maximum incoming SDU " + "size."); + +enum req { + REQ_NONE, + REQ_METADATA, + REQ_IMG, + REQ_STATUS, +}; + +enum { + FLAG_FAILED = BIT(0), + FLAG_CANCELLED = BIT(1), + FLAG_SKIP_CAPS_GET = BIT(2), + FLAG_RESUME = BIT(3), + FLAG_COMPLETED = BIT(4), +}; + +enum { + STATE_IDLE, + STATE_TRANSFER, + STATE_REFRESH, + STATE_VERIFIED, + STATE_APPLY, + STATE_APPLIED, + STATE_CONFIRM, + STATE_CANCEL, + STATE_SUSPENDED, +}; + +static int32_t dfu_cli_timeout = (10 * MSEC_PER_SEC); + +static struct bt_mesh_dfu_target *target_get(struct bt_mesh_dfu_cli *cli, + uint16_t addr) +{ + struct bt_mesh_dfu_target *target; + + TARGETS_FOR_EACH(cli, target) { + if (addr == target->blob.addr) { + return target; + } + } + + return NULL; +} + +static void target_failed(struct bt_mesh_dfu_cli *cli, + struct bt_mesh_dfu_target *target, + enum bt_mesh_dfu_status status) +{ + target->status = status; + + LOG_ERR("Target 0x%04x failed: %u", target->blob.addr, status); + + /* Invalidate blob status to prevent the target from being included in + * future sending: + */ + if (target->blob.status == BT_MESH_BLOB_SUCCESS) { + target->blob.status = BT_MESH_BLOB_ERR_INTERNAL; + } + + if (cli->cb && cli->cb->lost_target) { + cli->cb->lost_target(cli, target); + } +} + +static void dfu_complete(struct bt_mesh_dfu_cli *cli) +{ + LOG_DBG(""); + + if (cli->cb && cli->cb->ended) { + cli->cb->ended(cli, BT_MESH_DFU_SUCCESS); + } +} + +static void dfu_applied(struct bt_mesh_dfu_cli *cli) +{ + LOG_DBG(""); + + cli->xfer.state = STATE_APPLIED; + + if (cli->cb && cli->cb->applied) { + cli->cb->applied(cli); + } +} + +static void dfu_failed(struct bt_mesh_dfu_cli *cli, + enum bt_mesh_dfu_status reason) +{ + LOG_DBG("%u", reason); + + cli->xfer.flags |= FLAG_FAILED; + + if (cli->cb && cli->cb->ended) { + cli->cb->ended(cli, reason); + } +} + +static int req_setup(struct bt_mesh_dfu_cli *cli, enum req type, uint16_t addr, + void *params) +{ + if (cli->req.type != REQ_NONE) { + return -EBUSY; + } + + cli->req.addr = addr; + cli->req.params = params; + cli->req.type = type; + + return 0; +} + +static int req_wait(struct bt_mesh_dfu_cli *cli, k_timeout_t timeout) +{ + int err; + + err = k_sem_take(&cli->req.sem, timeout); + cli->req.type = REQ_NONE; + + return err; +} + +static bool targets_active(struct bt_mesh_dfu_cli *cli) +{ + struct bt_mesh_dfu_target *target; + + TARGETS_FOR_EACH(cli, target) { + if (target->status == BT_MESH_DFU_SUCCESS) { + return true; + } + } + + return false; +} + +/******************************************************************************* + * Blob client + ******************************************************************************/ +static void refresh(struct bt_mesh_dfu_cli *cli); + +static void blob_caps(struct bt_mesh_blob_cli *b, + const struct bt_mesh_blob_cli_caps *caps) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + int err; + + if (!caps) { + dfu_failed(cli, BT_MESH_DFU_ERR_RESOURCES); + return; + } + + cli->xfer.blob.block_size_log = caps->max_block_size_log; + cli->xfer.blob.chunk_size = caps->max_chunk_size; + + /* If mode is not already set and server reported it supports all modes + * default to PUSH, otherwise set value reported by server. If mode + * was set and server supports all modes, keep old value; set + * reported value otherwise. + */ + if (!(cli->xfer.blob.mode & BT_MESH_BLOB_XFER_MODE_ALL)) { + cli->xfer.blob.mode = + caps->modes == BT_MESH_BLOB_XFER_MODE_ALL ? + BT_MESH_BLOB_XFER_MODE_PUSH : caps->modes; + } else { + cli->xfer.blob.mode = + caps->modes == BT_MESH_BLOB_XFER_MODE_ALL ? + cli->xfer.blob.mode : caps->modes; + } + + err = bt_mesh_blob_cli_send(b, b->inputs, &cli->xfer.blob, cli->xfer.io); + if (err) { + LOG_ERR("Starting BLOB xfer failed: %d", err); + dfu_failed(cli, BT_MESH_DFU_ERR_BLOB_XFER_BUSY); + } +} + +static void blob_lost_target(struct bt_mesh_blob_cli *b, + struct bt_mesh_blob_target *blobt, + enum bt_mesh_blob_status reason) +{ + struct bt_mesh_dfu_target *target = + CONTAINER_OF(blobt, struct bt_mesh_dfu_target, blob); + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + + if ((cli->xfer.state == STATE_CONFIRM || cli->xfer.state == STATE_APPLY) && + target->effect == BT_MESH_DFU_EFFECT_UNPROV) { + /* Reset status for such targets to use them in consequent procedures. See sections + * 7.1.2.6 and 7.1.2.9 of the MeshDFU. + */ + target->blob.status = BT_MESH_BLOB_SUCCESS; + return; + } + + target_failed(cli, target, BT_MESH_DFU_ERR_INTERNAL); +} + +static void blob_suspended(struct bt_mesh_blob_cli *b) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + + LOG_DBG("BLOB transfer suspended"); + + cli->xfer.state = STATE_SUSPENDED; + + if (cli->cb && cli->cb->suspended) { + cli->cb->suspended(cli); + } +} + +static void blob_end(struct bt_mesh_blob_cli *b, + const struct bt_mesh_blob_xfer *xfer, bool success) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + + cli->req.img_cb = NULL; + + if (success) { + refresh(cli); + return; + } + + if (cli->xfer.state == STATE_CANCEL) { + /* The user cancelled the transfer, DFU will end when all + * targets have been notified. + */ + return; + } + + if (cli->xfer.state != STATE_TRANSFER) { + LOG_ERR("Blob failed in invalid state %u", cli->xfer.state); + return; + } + + dfu_failed(cli, BT_MESH_DFU_ERR_INTERNAL); +} + +const struct bt_mesh_blob_cli_cb _bt_mesh_dfu_cli_blob_handlers = { + .caps = blob_caps, + .lost_target = blob_lost_target, + .suspended = blob_suspended, + .end = blob_end, +}; + +/******************************************************************************* + * Message sending + ******************************************************************************/ + +static void tx_start(uint16_t dur, int err, void *cb_data); +static void tx_end(int err, void *cb_data); + +static const struct bt_mesh_send_cb send_cb = { + .start = tx_start, + .end = tx_end, +}; + +static void tx_start(uint16_t dur, int err, void *cb_data) +{ + if (err) { + tx_end(err, cb_data); + } +} + +static void tx_end(int err, void *cb_data) +{ + struct bt_mesh_dfu_cli *cli = cb_data; + + blob_cli_broadcast_tx_complete(&cli->blob); +} + +static int tx(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf, const struct bt_mesh_send_cb *cb, + struct bt_mesh_dfu_cli *cli) +{ + int err; + + err = bt_mesh_model_send(mod, ctx, buf, cb, cli); + if (err) { + LOG_ERR("Send err: %d", err); + if (cb) { + cb->end(err, cli); + } + return err; + } + + return 0; +} + +static int info_get(struct bt_mesh_dfu_cli *cli, struct bt_mesh_msg_ctx *ctx, + uint8_t idx, uint8_t max_count, + const struct bt_mesh_send_cb *cb) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_INFO_GET, 2); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_INFO_GET); + net_buf_simple_add_u8(&buf, idx); + net_buf_simple_add_u8(&buf, max_count); + + return tx(cli->mod, ctx, &buf, cb, cli); +} + +static void send_info_get(struct bt_mesh_blob_cli *b, uint16_t dst) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + struct bt_mesh_msg_ctx ctx = MSG_CTX(cli, dst); + + cli->req.img_cnt = 0xff; + + info_get(cli, &ctx, 0, cli->req.img_cnt, &send_cb); +} + +static void send_update_start(struct bt_mesh_blob_cli *b, uint16_t dst) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + struct bt_mesh_msg_ctx ctx = MSG_CTX(cli, dst); + struct bt_mesh_dfu_target *target; + + if (b->tx.ctx.force_unicast) { + target = target_get(cli, dst); + } else { + target = SYS_SLIST_PEEK_HEAD_CONTAINER( + (sys_slist_t *)&((cli)->blob.inputs)->targets, + target, blob.n); + } + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_START, + DFU_UPDATE_START_MSG_MAXLEN); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_START); + + net_buf_simple_add_u8(&buf, cli->blob.inputs->ttl); + net_buf_simple_add_le16(&buf, cli->blob.inputs->timeout_base); + net_buf_simple_add_le64(&buf, cli->xfer.blob.id); + net_buf_simple_add_u8(&buf, target->img_idx); + net_buf_simple_add_mem(&buf, cli->xfer.slot->metadata, + cli->xfer.slot->metadata_len); + + (void)tx(cli->mod, &ctx, &buf, &send_cb, cli); +} + +static void send_update_get(struct bt_mesh_blob_cli *b, uint16_t dst) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + struct bt_mesh_msg_ctx ctx = MSG_CTX(cli, dst); + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_GET, 0); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_GET); + + (void)tx(cli->mod, &ctx, &buf, &send_cb, cli); +} + +static void send_update_cancel(struct bt_mesh_blob_cli *b, uint16_t dst) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + struct bt_mesh_msg_ctx ctx = MSG_CTX(cli, dst); + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_CANCEL, 0); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_CANCEL); + + (void)tx(cli->mod, &ctx, &buf, &send_cb, cli); +} + +static void send_update_apply(struct bt_mesh_blob_cli *b, uint16_t dst) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + struct bt_mesh_msg_ctx ctx = MSG_CTX(cli, dst); + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_APPLY, 0); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_APPLY); + + (void)tx(cli->mod, &ctx, &buf, &send_cb, cli); +} + +/******************************************************************************* + * Distribution procedure + ******************************************************************************/ +static void transfer(struct bt_mesh_blob_cli *b); +static void apply(struct bt_mesh_dfu_cli *cli); +static void applied(struct bt_mesh_blob_cli *b); +static void confirmed(struct bt_mesh_blob_cli *b); +static void cancelled(struct bt_mesh_blob_cli *b); + +static void initiate(struct bt_mesh_dfu_cli *cli) +{ + struct blob_cli_broadcast_ctx tx = { + .send = send_update_start, + .next = transfer, + .acked = true, + }; + struct bt_mesh_dfu_target *target; + int img_idx = -1; + + /** If firmware img index is the same for all targets, we can send Firmware Update Start + * message using multicast address. Otherwise, it has to be send in a unicast way. + */ + TARGETS_FOR_EACH(cli, target) { + if (img_idx == -1) { + img_idx = target->img_idx; + } else if (target->img_idx != img_idx) { + tx.force_unicast = true; + break; + } + } + + LOG_DBG(""); + + cli->op = BT_MESH_DFU_OP_UPDATE_STATUS; + cli->xfer.state = STATE_TRANSFER; + + blob_cli_broadcast(&cli->blob, &tx); +} + +static void skip_targets_from_broadcast(struct bt_mesh_dfu_cli *cli, bool skip) +{ + struct bt_mesh_dfu_target *target; + + TARGETS_FOR_EACH(cli, target) { + /* If distributor is in the targets list, or target is in Verify phase, + * disable it until Retrieve Capabilities and BLOB Transfer procedures + * are completed. + */ + if (bt_mesh_has_addr(target->blob.addr) || + target->phase == BT_MESH_DFU_PHASE_VERIFY) { + target->blob.skip = skip; + break; + } + } +} + +static bool transfer_skip(struct bt_mesh_dfu_cli *cli) +{ + struct bt_mesh_dfu_target *target; + + TARGETS_FOR_EACH(cli, target) { + if (!bt_mesh_has_addr(target->blob.addr) || !target->blob.skip) { + return false; + } + } + + return true; +} + +static void transfer(struct bt_mesh_blob_cli *b) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + int err; + + LOG_DBG(""); + + if (!targets_active(cli)) { + dfu_failed(cli, BT_MESH_DFU_ERR_INTERNAL); + return; + } + + skip_targets_from_broadcast(cli, true); + + if (transfer_skip(cli)) { + /* If distributor only updates itself, or all targets are in Verify phase, + * proceed to the refresh step immediately. + */ + refresh(cli); + return; + } + + if (cli->xfer.flags & FLAG_RESUME) { + cli->xfer.flags ^= FLAG_RESUME; + err = bt_mesh_blob_cli_resume(b); + if (err) { + LOG_ERR("Resuming BLOB xfer failed: %d", err); + dfu_failed(cli, BT_MESH_DFU_ERR_BLOB_XFER_BUSY); + } + } else if (cli->xfer.flags & FLAG_SKIP_CAPS_GET) { + cli->xfer.flags ^= FLAG_SKIP_CAPS_GET; + err = bt_mesh_blob_cli_send(b, b->inputs, &cli->xfer.blob, cli->xfer.io); + if (err) { + LOG_ERR("Starting BLOB xfer failed: %d", err); + dfu_failed(cli, BT_MESH_DFU_ERR_BLOB_XFER_BUSY); + } + } else { + err = bt_mesh_blob_cli_caps_get(&cli->blob, cli->blob.inputs); + if (err) { + LOG_ERR("Failed starting blob xfer: %d", err); + dfu_failed(cli, BT_MESH_DFU_ERR_BLOB_XFER_BUSY); + } + } +} + +static void refreshed(struct bt_mesh_blob_cli *b) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + + if (!targets_active(cli)) { + dfu_failed(cli, BT_MESH_DFU_ERR_INTERNAL); + return; + } + + cli->xfer.state = STATE_VERIFIED; + dfu_complete(cli); +} + +static void refresh(struct bt_mesh_dfu_cli *cli) +{ + const struct blob_cli_broadcast_ctx tx = { + .send = send_update_get, + .next = refreshed, + .acked = true + }; + + LOG_DBG(""); + + cli->xfer.state = STATE_REFRESH; + cli->op = BT_MESH_DFU_OP_UPDATE_STATUS; + + /* If distributor is in the targets list, enable it again so it participates in Distribute + * Firmware procedure. + */ + skip_targets_from_broadcast(cli, false); + + blob_cli_broadcast(&cli->blob, &tx); +} + +static void apply(struct bt_mesh_dfu_cli *cli) +{ + const struct blob_cli_broadcast_ctx tx = { + .send = send_update_apply, + .next = applied, + .acked = true + }; + + LOG_DBG(""); + + cli->xfer.state = STATE_APPLY; + cli->op = BT_MESH_DFU_OP_UPDATE_STATUS; + + blob_cli_broadcast(&cli->blob, &tx); +} + +static void applied(struct bt_mesh_blob_cli *b) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + + if (!targets_active(cli)) { + dfu_failed(cli, BT_MESH_DFU_ERR_INTERNAL); + return; + } + + dfu_applied(cli); +} + +static enum bt_mesh_dfu_iter target_img_cb(struct bt_mesh_dfu_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint8_t idx, uint8_t cnt, + const struct bt_mesh_dfu_img *img, + void *cb_data) +{ + struct bt_mesh_dfu_target *target; + + if ((img->fwid_len != cli->xfer.slot->fwid_len) || + memcmp(cli->xfer.slot->fwid, img->fwid, img->fwid_len)) { + return BT_MESH_DFU_ITER_CONTINUE; + } + + target = target_get(cli, ctx->addr); + if (target) { + LOG_DBG("SUCCESS: 0x%04x applied dfu (as image %u)", ctx->addr, + idx); + target->phase = BT_MESH_DFU_PHASE_APPLY_SUCCESS; + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + } else { + LOG_WRN("Target 0x%04x not found", ctx->addr); + } + + return BT_MESH_DFU_ITER_STOP; +} + +static void confirm(struct bt_mesh_dfu_cli *cli) +{ + const struct blob_cli_broadcast_ctx tx = { + .send = send_info_get, + .next = confirmed, + .acked = true, + .optional = true, + }; + + LOG_DBG(""); + + cli->op = BT_MESH_DFU_OP_UPDATE_INFO_STATUS; + cli->req.img_cb = target_img_cb; + cli->req.ttl = cli->blob.inputs->ttl; + + blob_cli_broadcast(&cli->blob, &tx); +} + +static void confirmed(struct bt_mesh_blob_cli *b) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + struct bt_mesh_dfu_target *target; + bool success = false; + + cli->req.img_cb = NULL; + + TARGETS_FOR_EACH(cli, target) { + if (target->status != BT_MESH_DFU_SUCCESS) { + /* Target either failed at earlier stage or during confirmation. In any + * case, the app is already notified. Don't consider the target here. + */ + continue; + } + + if (target->effect == BT_MESH_DFU_EFFECT_UNPROV) { + if (!target->blob.acked) { + success = true; + continue; + } + + LOG_DBG("Target 0x%04x still provisioned", target->blob.addr); + target->phase = BT_MESH_DFU_PHASE_APPLY_FAIL; + target_failed(cli, target, BT_MESH_DFU_ERR_INTERNAL); + } else if (!target->blob.acked) { + LOG_DBG("Target 0x%04x failed to respond", target->blob.addr); + target->phase = BT_MESH_DFU_PHASE_APPLY_FAIL; + target_failed(cli, target, BT_MESH_DFU_ERR_INTERNAL); + } else if (target->status == BT_MESH_DFU_SUCCESS) { + success = true; + } + } + + if (success) { + cli->xfer.state = STATE_IDLE; + cli->xfer.flags = FLAG_COMPLETED; + + if (cli->cb && cli->cb->confirmed) { + cli->cb->confirmed(cli); + } + } else { + dfu_failed(cli, BT_MESH_DFU_ERR_INTERNAL); + } +} + +static void cancel(struct bt_mesh_dfu_cli *cli) +{ + const struct blob_cli_broadcast_ctx tx = { + .send = send_update_cancel, + .next = cancelled, + .acked = true + }; + + LOG_DBG(""); + + cli->op = BT_MESH_DFU_OP_UPDATE_STATUS; + + blob_cli_broadcast(&cli->blob, &tx); +} + +static void cancelled(struct bt_mesh_blob_cli *b) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + + cli->xfer.flags |= FLAG_CANCELLED; + dfu_failed(cli, BT_MESH_DFU_ERR_INTERNAL); +} + +/******************************************************************************* + * Message handlers + ******************************************************************************/ + +static int handle_status(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_cli *cli = mod->rt->user_data; + enum bt_mesh_dfu_status status; + enum bt_mesh_dfu_phase phase; + struct bt_mesh_dfu_target *target; + uint8_t byte; + + byte = net_buf_simple_pull_u8(buf); + status = byte & BIT_MASK(3); + phase = byte >> 5; + + if (cli->req.type == REQ_STATUS && cli->req.addr == ctx->addr) { + if (cli->req.params) { + struct bt_mesh_dfu_target_status *rsp = cli->req.params; + + rsp->status = status; + rsp->phase = phase; + if (buf->len == 13) { + rsp->ttl = net_buf_simple_pull_u8(buf); + rsp->effect = net_buf_simple_pull_u8(buf) & BIT_MASK(5); + rsp->timeout_base = net_buf_simple_pull_le16(buf); + rsp->blob_id = net_buf_simple_pull_le64(buf); + rsp->img_idx = net_buf_simple_pull_u8(buf); + } else if (buf->len) { + return -EINVAL; + } + + rsp->ttl = 0U; + rsp->effect = BT_MESH_DFU_EFFECT_NONE; + rsp->timeout_base = 0U; + rsp->blob_id = 0U; + rsp->img_idx = 0U; + } + k_sem_give(&cli->req.sem); + } + if (cli->op != BT_MESH_DFU_OP_UPDATE_STATUS) { + return 0; + } + + target = target_get(cli, ctx->addr); + if (!target) { + LOG_WRN("Unknown target 0x%04x", ctx->addr); + return -ENOENT; + } + + LOG_DBG("%u phase: %u, cur state: %u", status, phase, cli->xfer.state); + + target->phase = phase; + + if (cli->xfer.state == STATE_APPLY && phase == BT_MESH_DFU_PHASE_IDLE && + status == BT_MESH_DFU_ERR_WRONG_PHASE) { + LOG_DBG("Response received with Idle phase"); + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + return 0; + } + + if (status != BT_MESH_DFU_SUCCESS) { + target_failed(cli, target, status); + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + return 0; + } + + if (buf->len == 13) { + net_buf_simple_pull_u8(buf); /* ttl */ + target->effect = net_buf_simple_pull_u8(buf) & BIT_MASK(5); + net_buf_simple_pull_le16(buf); /* timeout */ + + if (net_buf_simple_pull_le64(buf) != cli->xfer.blob.id) { + LOG_WRN("Invalid BLOB ID"); + target_failed(cli, target, BT_MESH_DFU_ERR_BLOB_XFER_BUSY); + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + return 0; + } + + target->img_idx = net_buf_simple_pull_u8(buf); + + LOG_DBG("Target 0x%04x receiving transfer", ctx->addr); + } else if (buf->len) { + return -EINVAL; + } + + if (cli->xfer.state == STATE_REFRESH) { + if (phase == BT_MESH_DFU_PHASE_VERIFY) { + LOG_DBG("Still pending..."); + return 0; + } else if (phase == BT_MESH_DFU_PHASE_VERIFY_FAIL) { + LOG_WRN("Verification failed on target 0x%04x", + target->blob.addr); + target_failed(cli, target, BT_MESH_DFU_ERR_WRONG_PHASE); + } + } else if (cli->xfer.state == STATE_APPLY) { + if (phase != BT_MESH_DFU_PHASE_APPLYING && + (target->effect == BT_MESH_DFU_EFFECT_UNPROV || + phase != BT_MESH_DFU_PHASE_IDLE)) { + LOG_WRN("Target 0x%04x in phase %u after apply", + target->blob.addr, phase); + target_failed(cli, target, BT_MESH_DFU_ERR_WRONG_PHASE); + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + return 0; + } + return 0; + } else if (cli->xfer.state == STATE_CONFIRM) { + if (phase == BT_MESH_DFU_PHASE_APPLYING) { + LOG_DBG("Still pending..."); + return 0; + } + + if (phase != BT_MESH_DFU_PHASE_IDLE) { + LOG_WRN("Target 0x%04x in phase %u after apply", + target->blob.addr, phase); + target->phase = BT_MESH_DFU_PHASE_APPLY_FAIL; + target_failed(cli, target, BT_MESH_DFU_ERR_WRONG_PHASE); + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + return 0; + } + } else if (cli->xfer.state == STATE_CANCEL) { + target->phase = BT_MESH_DFU_PHASE_TRANSFER_CANCELED; + } + + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + + return 0; +} + +static int handle_info_status(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_cli *cli = mod->rt->user_data; + struct bt_mesh_dfu_target *target; + enum bt_mesh_dfu_iter it = BT_MESH_DFU_ITER_CONTINUE; + uint8_t img_cnt, idx; + + if (!cli->req.img_cb || + (cli->req.type == REQ_IMG && cli->req.addr != ctx->addr)) { + LOG_WRN("Unexpected info status from 0x%04x", ctx->addr); + return 0; + } + + img_cnt = net_buf_simple_pull_u8(buf); + if (img_cnt < cli->req.img_cnt) { + cli->req.img_cnt = img_cnt; + } + + idx = net_buf_simple_pull_u8(buf); + if (idx >= img_cnt) { + LOG_WRN("Invalid idx %u", idx); + return -ENOENT; + } + + LOG_DBG("Image list from 0x%04x from index %u", ctx->addr, idx); + + while (buf->len && cli->req.img_cb && idx < cli->req.img_cnt) { + char uri_buf[CONFIG_BT_MESH_DFU_URI_MAXLEN + 1]; + struct bt_mesh_dfu_img img; + size_t uri_len; + + img.fwid_len = net_buf_simple_pull_u8(buf); + if (buf->len < img.fwid_len + 1) { + LOG_WRN("Invalid format: fwid"); + return -EINVAL; + } + + img.fwid = net_buf_simple_pull_mem(buf, img.fwid_len); + + uri_len = net_buf_simple_pull_u8(buf); + if (buf->len < uri_len) { + LOG_WRN("Invalid format: uri"); + return -EINVAL; + } + + LOG_DBG("\tImage %u\n\r\tfwid: %s", idx, bt_hex(img.fwid, img.fwid_len)); + + if (uri_len) { + size_t uri_buf_len = + MIN(CONFIG_BT_MESH_DFU_URI_MAXLEN, uri_len); + + memcpy(uri_buf, net_buf_simple_pull_mem(buf, uri_len), + uri_buf_len); + uri_buf[uri_buf_len] = '\0'; + img.uri = uri_buf; + } else { + img.uri = NULL; + } + + it = cli->req.img_cb(cli, ctx, idx, img_cnt, &img, + cli->req.params); + if (it != BT_MESH_DFU_ITER_CONTINUE) { + if (cli->req.type == REQ_IMG) { + k_sem_give(&cli->req.sem); + } + + return 0; + } + + idx++; + } + + if (idx < cli->req.img_cnt) { + LOG_DBG("Fetching more images (%u/%u)", idx, cli->req.img_cnt); + ctx->send_ttl = cli->req.ttl; + info_get(cli, ctx, idx, cli->req.img_cnt - idx, + (cli->req.type == REQ_IMG) ? NULL : &send_cb); + return 0; + } + + if (cli->req.type == REQ_IMG) { + k_sem_give(&cli->req.sem); + return 0; + } + + /* Confirm-procedure termination: */ + target = target_get(cli, ctx->addr); + if (target) { + LOG_WRN("Target 0x%04x failed to apply image: %s", ctx->addr, + bt_hex(cli->xfer.slot->fwid, cli->xfer.slot->fwid_len)); + target->phase = BT_MESH_DFU_PHASE_APPLY_FAIL; + target_failed(cli, target, BT_MESH_DFU_ERR_INTERNAL); + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + } + + return 0; +} + +static int handle_metadata_status(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_cli *cli = mod->rt->user_data; + struct bt_mesh_dfu_metadata_status *rsp = cli->req.params; + uint8_t hdr, idx; + + hdr = net_buf_simple_pull_u8(buf); + idx = net_buf_simple_pull_u8(buf); + + if (cli->req.type != REQ_METADATA || ctx->addr != cli->req.addr || + idx != rsp->idx) { + LOG_WRN("Unexpected metadata status from 0x%04x img %u", + ctx->addr, idx); + if (cli->req.type != REQ_METADATA) { + LOG_WRN("Expected %u", cli->req.type); + } else { + LOG_WRN("Expected 0x%04x img %u", cli->req.addr, idx); + } + + return 0; + } + + rsp->status = hdr & BIT_MASK(3); + rsp->effect = (hdr >> 3); + k_sem_give(&cli->req.sem); + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_dfu_cli_op[] = { + {BT_MESH_DFU_OP_UPDATE_STATUS, BT_MESH_LEN_MIN(1), handle_status}, + {BT_MESH_DFU_OP_UPDATE_INFO_STATUS, BT_MESH_LEN_MIN(2), handle_info_status}, + {BT_MESH_DFU_OP_UPDATE_METADATA_STATUS, BT_MESH_LEN_EXACT(2), handle_metadata_status}, + BT_MESH_MODEL_OP_END, +}; + +static int dfu_cli_init(const struct bt_mesh_model *mod) +{ + struct bt_mesh_dfu_cli *cli = mod->rt->user_data; + + if (mod->rt->elem_idx != 0) { + LOG_ERR("DFU update client must be instantiated on first elem"); + return -EINVAL; + } + + cli->mod = mod; + + if (IS_ENABLED(CONFIG_BT_MESH_MODEL_EXTENSIONS)) { + bt_mesh_model_extend(mod, cli->blob.mod); + } + + k_sem_init(&cli->req.sem, 0, 1); + + return 0; +} + +static void dfu_cli_reset(const struct bt_mesh_model *mod) +{ + struct bt_mesh_dfu_cli *cli = mod->rt->user_data; + + cli->req.type = REQ_NONE; + cli->req.addr = BT_MESH_ADDR_UNASSIGNED; + cli->req.img_cnt = 0; + cli->req.img_cb = NULL; + cli->xfer.state = STATE_IDLE; + cli->xfer.flags = 0; +} + +const struct bt_mesh_model_cb _bt_mesh_dfu_cli_cb = { + .init = dfu_cli_init, + .reset = dfu_cli_reset, +}; + +/******************************************************************************* + * Public API + ******************************************************************************/ + +int bt_mesh_dfu_cli_send(struct bt_mesh_dfu_cli *cli, + const struct bt_mesh_blob_cli_inputs *inputs, + const struct bt_mesh_blob_io *io, + const struct bt_mesh_dfu_cli_xfer *xfer) +{ + struct bt_mesh_dfu_target *target; + + if (bt_mesh_dfu_cli_is_busy(cli)) { + return -EBUSY; + } + + cli->xfer.blob.mode = xfer->mode; + cli->xfer.blob.size = xfer->slot->size; + + if (xfer->blob_id == 0) { + int err = bt_rand(&cli->xfer.blob.id, sizeof(cli->xfer.blob.id)); + + if (err) { + return err; + } + } else { + cli->xfer.blob.id = xfer->blob_id; + } + + cli->xfer.io = io; + cli->blob.inputs = inputs; + cli->xfer.slot = xfer->slot; + cli->xfer.flags = 0U; + + if (xfer->blob_params) { + cli->xfer.flags |= FLAG_SKIP_CAPS_GET; + cli->xfer.blob.block_size_log = xfer->blob_params->block_size_log; + cli->xfer.blob.chunk_size = xfer->blob_params->chunk_size; + } + + /* Phase will be set based on target status messages: */ + TARGETS_FOR_EACH(cli, target) { + target->status = BT_MESH_DFU_SUCCESS; + target->phase = BT_MESH_DFU_PHASE_UNKNOWN; + } + + initiate(cli); + return 0; +} + +int bt_mesh_dfu_cli_suspend(struct bt_mesh_dfu_cli *cli) +{ + int err; + + err = bt_mesh_blob_cli_suspend(&cli->blob); + if (!err) { + cli->xfer.state = STATE_SUSPENDED; + } + + return err; +} + +int bt_mesh_dfu_cli_resume(struct bt_mesh_dfu_cli *cli) +{ + struct bt_mesh_dfu_target *target; + + if (cli->xfer.state != STATE_SUSPENDED) { + return -EINVAL; + } + + cli->xfer.flags = FLAG_RESUME; + + /* Restore timed out targets. */ + TARGETS_FOR_EACH(cli, target) { + if (!!target->blob.timedout) { + target->status = BT_MESH_DFU_SUCCESS; + target->phase = BT_MESH_DFU_PHASE_UNKNOWN; + } + } + + initiate(cli); + return 0; +} + +int bt_mesh_dfu_cli_cancel(struct bt_mesh_dfu_cli *cli, + struct bt_mesh_msg_ctx *ctx) +{ + if (ctx) { + int err; + + err = req_setup(cli, REQ_STATUS, ctx->addr, NULL); + if (err) { + return err; + } + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_CANCEL, 0); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_CANCEL); + + err = bt_mesh_model_send(cli->mod, ctx, &buf, NULL, NULL); + if (err) { + cli->req.type = REQ_NONE; + return err; + } + + return req_wait(cli, K_MSEC(dfu_cli_timeout)); + } + + if (cli->xfer.state == STATE_IDLE) { + return -EALREADY; + } + + cli->xfer.state = STATE_CANCEL; + blob_cli_broadcast_abort(&cli->blob); + cancel(cli); + return 0; +} + +int bt_mesh_dfu_cli_apply(struct bt_mesh_dfu_cli *cli) +{ + if (cli->xfer.state != STATE_VERIFIED) { + return -EBUSY; + } + + apply(cli); + + return 0; +} + +int bt_mesh_dfu_cli_confirm(struct bt_mesh_dfu_cli *cli) +{ + if (cli->xfer.state != STATE_APPLIED) { + return -EBUSY; + } + + cli->xfer.state = STATE_CONFIRM; + confirm(cli); + + return 0; +} + +uint8_t bt_mesh_dfu_cli_progress(struct bt_mesh_dfu_cli *cli) +{ + if (cli->xfer.state == STATE_TRANSFER) { + return bt_mesh_blob_cli_xfer_progress_active_get(&cli->blob); + } + + if (cli->xfer.state == STATE_IDLE) { + if (cli->xfer.flags & FLAG_COMPLETED) { + return 100U; + } + return 0U; + } + + return 100U; +} + +bool bt_mesh_dfu_cli_is_busy(struct bt_mesh_dfu_cli *cli) +{ + return (cli->xfer.state == STATE_TRANSFER || + cli->xfer.state == STATE_REFRESH || + cli->xfer.state == STATE_APPLY || + cli->xfer.state == STATE_CONFIRM) && + !(cli->xfer.flags & FLAG_FAILED); +} + +int bt_mesh_dfu_cli_imgs_get(struct bt_mesh_dfu_cli *cli, + struct bt_mesh_msg_ctx *ctx, + bt_mesh_dfu_img_cb_t cb, void *cb_data, + uint8_t max_count) +{ + int err; + + if (cli->req.img_cb) { + return -EBUSY; + } + + err = req_setup(cli, REQ_IMG, ctx->addr, NULL); + if (err) { + return err; + } + + cli->req.img_cb = cb; + cli->req.params = cb_data; + cli->req.ttl = ctx->send_ttl; + cli->req.img_cnt = max_count; + + err = info_get(cli, ctx, 0, cli->req.img_cnt, NULL); + if (err) { + cli->req.img_cb = NULL; + cli->req.type = REQ_NONE; + return err; + } + + err = req_wait(cli, K_MSEC(dfu_cli_timeout)); + + cli->req.img_cb = NULL; + + return err; +} + +int bt_mesh_dfu_cli_metadata_check(struct bt_mesh_dfu_cli *cli, + struct bt_mesh_msg_ctx *ctx, uint8_t img_idx, + const struct bt_mesh_dfu_slot *slot, + struct bt_mesh_dfu_metadata_status *rsp) +{ + int err; + + err = req_setup(cli, REQ_METADATA, ctx->addr, rsp); + if (err) { + return err; + } + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_METADATA_CHECK, + 1 + CONFIG_BT_MESH_DFU_METADATA_MAXLEN); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_METADATA_CHECK); + + net_buf_simple_add_u8(&buf, img_idx); + + if (slot->metadata_len) { + net_buf_simple_add_mem(&buf, slot->metadata, + slot->metadata_len); + } + + rsp->idx = img_idx; + + err = bt_mesh_model_send(cli->mod, ctx, &buf, NULL, NULL); + if (err) { + cli->req.type = REQ_NONE; + return err; + } + + return req_wait(cli, K_MSEC(dfu_cli_timeout)); +} + +int bt_mesh_dfu_cli_status_get(struct bt_mesh_dfu_cli *cli, + struct bt_mesh_msg_ctx *ctx, + struct bt_mesh_dfu_target_status *rsp) +{ + int err; + + err = req_setup(cli, REQ_STATUS, ctx->addr, rsp); + if (err) { + return err; + } + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_GET, 0); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_GET); + + err = bt_mesh_model_send(cli->mod, ctx, &buf, NULL, NULL); + if (err) { + cli->req.type = REQ_NONE; + return err; + } + + return req_wait(cli, K_MSEC(dfu_cli_timeout)); +} + +int32_t bt_mesh_dfu_cli_timeout_get(void) +{ + return dfu_cli_timeout; +} + +void bt_mesh_dfu_cli_timeout_set(int32_t t) +{ + dfu_cli_timeout = t; +} diff --git a/components/bt/esp_ble_mesh/v1.1/dfu/dfu_metadata.c b/components/bt/esp_ble_mesh/v1.1/dfu/dfu_metadata.c new file mode 100644 index 0000000000..51a2cd0306 --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/dfu/dfu_metadata.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include "crypto.h" +#include "access.h" + +int bt_mesh_dfu_metadata_decode(struct net_buf_simple *buf, + struct bt_mesh_dfu_metadata *metadata) +{ + if (buf->len < 12) { + return -EMSGSIZE; + } + + metadata->fw_ver.major = net_buf_simple_pull_u8(buf); + metadata->fw_ver.minor = net_buf_simple_pull_u8(buf); + metadata->fw_ver.revision = net_buf_simple_pull_le16(buf); + metadata->fw_ver.build_num = net_buf_simple_pull_le32(buf); + metadata->fw_size = net_buf_simple_pull_le24(buf); + metadata->fw_core_type = net_buf_simple_pull_u8(buf); + + if (metadata->fw_core_type & BT_MESH_DFU_FW_CORE_TYPE_APP) { + if (buf->len < 6) { + return -EMSGSIZE; + } + + metadata->comp_hash = net_buf_simple_pull_le32(buf); + metadata->elems = net_buf_simple_pull_le16(buf); + } + + metadata->user_data = buf->len > 0 ? buf->data : NULL; + metadata->user_data_len = buf->len; + + return 0; +} + + +int bt_mesh_dfu_metadata_encode(const struct bt_mesh_dfu_metadata *metadata, + struct net_buf_simple *buf) +{ + size_t md_len_min = 12 + metadata->user_data_len; + + if (metadata->fw_core_type & BT_MESH_DFU_FW_CORE_TYPE_APP) { + md_len_min += 6; + } + + if (net_buf_simple_tailroom(buf) < md_len_min) { + return -EMSGSIZE; + } + + net_buf_simple_add_u8(buf, metadata->fw_ver.major); + net_buf_simple_add_u8(buf, metadata->fw_ver.minor); + net_buf_simple_add_le16(buf, metadata->fw_ver.revision); + net_buf_simple_add_le32(buf, metadata->fw_ver.build_num); + net_buf_simple_add_le24(buf, metadata->fw_size); + net_buf_simple_add_u8(buf, metadata->fw_core_type); + net_buf_simple_add_le32(buf, metadata->comp_hash); + net_buf_simple_add_le16(buf, metadata->elems); + + if (metadata->user_data_len > 0) { + net_buf_simple_add_mem(buf, metadata->user_data, metadata->user_data_len); + } + + return 0; +} + +int bt_mesh_dfu_metadata_comp_hash_get(struct net_buf_simple *buf, uint8_t *key, uint32_t *hash) +{ + uint8_t mac[16]; + int err; + struct bt_mesh_sg sg = {.data = buf->data, .len = buf->len}; + + err = bt_mesh_aes_cmac_raw_key(key, &sg, 1, mac); + if (err) { + return err; + } + + *hash = sys_get_le32(mac); + + return 0; +} + +int bt_mesh_dfu_metadata_comp_hash_local_get(uint8_t *key, uint32_t *hash) +{ + NET_BUF_SIMPLE_DEFINE(buf, BT_MESH_TX_SDU_MAX); + int err; + + err = bt_mesh_comp_data_get_page_0(&buf, 0); + if (err) { + return err; + } + + err = bt_mesh_dfu_metadata_comp_hash_get(&buf, key, hash); + return err; +} diff --git a/components/bt/esp_ble_mesh/v1.1/dfu/dfu_slot.c b/components/bt/esp_ble_mesh/v1.1/dfu/dfu_slot.c new file mode 100644 index 0000000000..c11c16d44f --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/dfu/dfu_slot.c @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "dfu_slot.h" +#include +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_BT_MESH_DFU_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_dfu_slot); + +#define SLOT_ENTRY_BUFLEN 25 + +#define DFU_SLOT_SETTINGS_PATH "bt/mesh-dfu/slot" + +#define HEADER_SIZE offsetof(struct slot, slot.fwid) + +#define PROP_HEADER "h" +#define PROP_FWID "id" +#define PROP_METADATA "m" + +static sys_slist_t list; + +static struct slot { + uint32_t idx; + struct bt_mesh_dfu_slot slot; + sys_snode_t n; +} slots[CONFIG_BT_MESH_DFU_SLOT_CNT]; + +static uint32_t slot_index; + +static char *slot_entry_encode(uint16_t idx, char buf[SLOT_ENTRY_BUFLEN], + const char *property) +{ + snprintf(buf, SLOT_ENTRY_BUFLEN, DFU_SLOT_SETTINGS_PATH "/%x/%s", idx, + property); + + return buf; +} + +static bool slot_eq(const struct bt_mesh_dfu_slot *slot, + const uint8_t *fwid, size_t fwid_len) +{ + return (slot->fwid_len == fwid_len) && + !memcmp(fwid, slot->fwid, fwid_len); +} + +static bool is_slot_committed(struct slot *slot_to_check) +{ + struct slot *s; + + SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) { + if (s == slot_to_check) { + return true; + } + } + + return false; +} + +static int slot_store(const struct slot *slot_to_store) +{ + uint16_t idx = ARRAY_INDEX(slots, slot_to_store); + char buf[SLOT_ENTRY_BUFLEN]; + int err; + + err = settings_save_one(slot_entry_encode(idx, buf, PROP_HEADER), + slot_to_store, HEADER_SIZE); + if (err) { + return err; + } + + err = settings_save_one(slot_entry_encode(idx, buf, PROP_FWID), + slot_to_store->slot.fwid, slot_to_store->slot.fwid_len); + if (err) { + return err; + } + + err = settings_save_one(slot_entry_encode(idx, buf, + PROP_METADATA), + slot_to_store->slot.metadata, slot_to_store->slot.metadata_len); + + return err; +} + +static void slot_erase(struct slot *slot_to_erase) +{ + uint16_t idx = ARRAY_INDEX(slots, slot_to_erase); + char buf[SLOT_ENTRY_BUFLEN]; + + (void)settings_delete(slot_entry_encode(idx, buf, PROP_HEADER)); + (void)settings_delete(slot_entry_encode(idx, buf, PROP_FWID)); + (void)settings_delete(slot_entry_encode(idx, buf, PROP_METADATA)); +} + +static void slot_index_defrag(void) +{ + slot_index = 0; + struct slot *s; + + SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) { + s->idx = ++slot_index; + slot_store(s); + } +} + +int bt_mesh_dfu_slot_count(void) +{ + int cnt = 0; + sys_snode_t *n; + + SYS_SLIST_FOR_EACH_NODE(&list, n) { + cnt++; + } + + return cnt; +} + +struct bt_mesh_dfu_slot *bt_mesh_dfu_slot_reserve(void) +{ + struct slot *slot = NULL; + + for (int i = 0; i < ARRAY_SIZE(slots); ++i) { + if (slots[i].idx == 0) { + slot = &slots[i]; + break; + } + } + + if (!slot) { + LOG_WRN("No space"); + return NULL; + } + + if (slot_index == UINT32_MAX) { + slot_index_defrag(); + } + + slot->slot.fwid_len = 0; + slot->slot.metadata_len = 0; + slot->slot.size = 0; + slot->idx = ++slot_index; + + LOG_DBG("Reserved slot #%u", slot - &slots[0]); + + return &slot->slot; +} + +int bt_mesh_dfu_slot_info_set(struct bt_mesh_dfu_slot *dfu_slot, size_t size, + const uint8_t *metadata, size_t metadata_len) +{ + struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot); + + if (metadata_len > CONFIG_BT_MESH_DFU_METADATA_MAXLEN) { + return -EFBIG; + } + + if (slot->idx == 0 || is_slot_committed(slot)) { + return -EINVAL; + } + + slot->slot.size = size; + slot->slot.metadata_len = metadata_len; + memcpy(slot->slot.metadata, metadata, metadata_len); + return 0; +} + +int bt_mesh_dfu_slot_fwid_set(struct bt_mesh_dfu_slot *dfu_slot, + const uint8_t *fwid, size_t fwid_len) +{ + struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot); + + if (fwid_len > CONFIG_BT_MESH_DFU_FWID_MAXLEN) { + return -EFBIG; + } + + if (slot->idx == 0 || is_slot_committed(slot)) { + return -EINVAL; + } + + for (int i = 0; i < ARRAY_SIZE(slots); i++) { + if (slots[i].idx != 0 && + slot_eq(&slots[i].slot, fwid, fwid_len)) { + return is_slot_committed(&slots[i]) ? + -EEXIST : -EALREADY; + } + } + + slot->slot.fwid_len = fwid_len; + memcpy(slot->slot.fwid, fwid, fwid_len); + return 0; +} + +int bt_mesh_dfu_slot_commit(struct bt_mesh_dfu_slot *dfu_slot) +{ + int err; + struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot); + + if (slot->idx == 0 || + slot->slot.fwid_len == 0 || + slot->slot.size == 0 || + is_slot_committed(slot)) { + return -EINVAL; + } + + err = slot_store(slot); + if (err) { + LOG_WRN("Store failed (err: %d)", err); + return err; + } + + sys_slist_append(&list, &slot->n); + + LOG_DBG("Stored slot #%u: %s", ARRAY_INDEX(slots, slot), + bt_hex(slot->slot.fwid, slot->slot.fwid_len)); + return 0; +} + +void bt_mesh_dfu_slot_release(const struct bt_mesh_dfu_slot *dfu_slot) +{ + struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot); + + if (is_slot_committed(slot)) { + return; + } + + slot->idx = 0; +} + +int bt_mesh_dfu_slot_del(const struct bt_mesh_dfu_slot *dfu_slot) +{ + struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot); + + if (!sys_slist_find_and_remove(&list, &slot->n)) { + return -EINVAL; + } + + int idx = ARRAY_INDEX(slots, slot); + + LOG_DBG("%u", idx); + + slot_erase(slot); + slot->idx = 0; + + return 0; +} + +void bt_mesh_dfu_slot_del_all(void) +{ + struct slot *s; + SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) { + slot_erase(s); + s->idx = 0; + } + + sys_slist_init(&list); +} + +const struct bt_mesh_dfu_slot *bt_mesh_dfu_slot_at(uint16_t img_idx) +{ + struct slot *s; + + SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) { + if (!img_idx--) { + return &s->slot; + } + } + + return NULL; +} + +int bt_mesh_dfu_slot_get(const uint8_t *fwid, size_t fwid_len, struct bt_mesh_dfu_slot **slot) +{ + struct slot *s; + int idx = 0; + + SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) { + if (slot_eq(&s->slot, fwid, fwid_len)) { + if (slot) { + *slot = &s->slot; + } + return idx; + } + idx++; + } + + return -ENOENT; +} + +int bt_mesh_dfu_slot_img_idx_get(const struct bt_mesh_dfu_slot *dfu_slot) +{ + struct slot *s; + int idx = 0; + + SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) { + if (&s->slot == dfu_slot) { + return idx; + } + idx++; + } + + return -ENOENT; +} + +size_t bt_mesh_dfu_slot_foreach(bt_mesh_dfu_slot_cb_t cb, void *user_data) +{ + enum bt_mesh_dfu_iter iter; + size_t cnt = 0; + struct slot *s; + + SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) { + cnt++; + + if (!cb) { + continue; + } + + iter = cb(&s->slot, user_data); + if (iter != BT_MESH_DFU_ITER_CONTINUE) { + break; + } + } + + return cnt; +} + +static int slot_data_load(const char *key, size_t len_rd, + settings_read_cb read_cb, void *cb_arg) +{ + const char *prop; + size_t len; + uint16_t idx; + + idx = strtol(key, NULL, 16); + + if (idx >= ARRAY_SIZE(slots)) { + return 0; + } + + len = settings_name_next(key, &prop); + + if (!strncmp(prop, PROP_HEADER, len)) { + if (read_cb(cb_arg, &slots[idx], HEADER_SIZE) > 0) { + struct slot *s, *prev = NULL; + + SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) { + if (s->idx > slots[idx].idx) { + break; + } + + prev = s; + } + + if (prev == NULL) { + sys_slist_prepend(&list, &slots[idx].n); + } else { + sys_slist_insert(&list, &prev->n, &slots[idx].n); + } + + if (slots[idx].idx >= slot_index) { + slot_index = slots[idx].idx + 1; + } + } + return 0; + } + + if (!strncmp(prop, PROP_FWID, len)) { + if (read_cb(cb_arg, &slots[idx].slot.fwid, + sizeof(slots[idx].slot.fwid)) < 0) { + slots[idx].idx = 0; + sys_slist_find_and_remove(&list, &slots[idx].n); + return 0; + } + + slots[idx].slot.fwid_len = len_rd; + return 0; + } + + if (!strncmp(prop, PROP_METADATA, len)) { + if (read_cb(cb_arg, &slots[idx].slot.metadata, + sizeof(slots[idx].slot.metadata)) < 0) { + slots[idx].idx = 0; + sys_slist_find_and_remove(&list, &slots[idx].n); + return 0; + } + + slots[idx].slot.metadata_len = len_rd; + return 0; + } + + return 0; +} + +SETTINGS_STATIC_HANDLER_DEFINE(bt_mesh_dfu_slots, DFU_SLOT_SETTINGS_PATH, NULL, + slot_data_load, NULL, NULL); diff --git a/components/bt/esp_ble_mesh/v1.1/dfu/dfu_slot.h b/components/bt/esp_ble_mesh/v1.1/dfu/dfu_slot.h new file mode 100644 index 0000000000..969c6d3d25 --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/dfu/dfu_slot.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/** @brief Slot iteration callback. + * + * @param slot A valid DFU image slot. + * @param user_data User data passed to @ref bt_mesh_dfu_slot_foreach. + * + * @return Iteration action determining next step. + */ +typedef enum bt_mesh_dfu_iter (*bt_mesh_dfu_slot_cb_t)( + const struct bt_mesh_dfu_slot *slot, void *user_data); + +/** @brief Get the number of slots committed to the firmware list. + * + * @return Number of committed slots. + */ +int bt_mesh_dfu_slot_count(void); + +/** @brief Reserve a new DFU image slot for a distributable image. + * + * A DFU image slot represents a single distributable DFU image with all its + * metadata. The slot data must be set using @ref bt_mesh_dfu_slot_info_set and + * @ref bt_mesh_dfu_slot_fwid_set, and the slot committed using + * @ref bt_mesh_dfu_slot_commit for the slot to be considered part of the slot + * list. + * + * @return A pointer to the reserved slot, or NULL if allocation failed. + */ +struct bt_mesh_dfu_slot *bt_mesh_dfu_slot_reserve(void); + +/** @brief Set the size and metadata for a reserved slot. + * + * @param dfu_slot Pointer to the reserved slot for which to set the + * metadata. + * @param size The size of the image. + * @param metadata Metadata or NULL. + * @param metadata_len Length of the metadata, at most @c + * CONFIG_BT_MESH_DFU_METADATA_MAXLEN. + * + * @return 0 on success, (negative) error code otherwise. + */ +int bt_mesh_dfu_slot_info_set(struct bt_mesh_dfu_slot *dfu_slot, size_t size, + const uint8_t *metadata, size_t metadata_len); + +/** @brief Set the new fwid for the incoming image for a reserved slot. + * + * @param dfu_slot Pointer to the reserved slot for which to set the fwid. + * @param fwid Fwid to set. + * @param fwid_len Length of the fwid, at most @c + * CONFIG_BT_MESH_DFU_FWID_MAXLEN. + * + * @return 0 on success, (negative) error code otherwise. + */ +int bt_mesh_dfu_slot_fwid_set(struct bt_mesh_dfu_slot *dfu_slot, + const uint8_t *fwid, size_t fwid_len); + +/** @brief Commit the reserved slot to the list of slots, and store it + * persistently. + * + * If the commit fails for any reason, the slot will still be in the reserved + * state after this call. + * + * @param dfu_slot Pointer to the reserved slot. + * + * @return 0 on success, (negative) error code otherwise. + */ +int bt_mesh_dfu_slot_commit(struct bt_mesh_dfu_slot *dfu_slot); + +/** @brief Release a reserved slot so that it can be reserved again. + * + * @param dfu_slot Pointer to the reserved slot. + */ +void bt_mesh_dfu_slot_release(const struct bt_mesh_dfu_slot *dfu_slot); + +/** @brief Delete a committed DFU image slot. + * + * @param slot Slot to delete. Must be a valid pointer acquired from this + * module. + * + * @return 0 on success, or (negative) error code on failure. + */ +int bt_mesh_dfu_slot_del(const struct bt_mesh_dfu_slot *slot); + +/** @brief Delete all DFU image slots. + * + * @return 0 on success, or (negative) error code on failure. + */ +void bt_mesh_dfu_slot_del_all(void); + +/** @brief Get the DFU image slot at the given firmware image list index. + * + * @param idx DFU image slot index. + * + * @return The DFU image slot at the given index, or NULL if no slot exists with the + * given index. + */ +const struct bt_mesh_dfu_slot *bt_mesh_dfu_slot_at(uint16_t img_idx); + +/** @brief Get the committed DFU image slot for the image with the given + * firmware ID. + * + * @param fwid Firmware ID. + * @param fwid_len Firmware ID length. + * @param slot Slot pointer to fill. + * + * @return Slot index on success, or negative error code on failure. + */ +int bt_mesh_dfu_slot_get(const uint8_t *fwid, size_t fwid_len, struct bt_mesh_dfu_slot **slot); + +/** @brief Get the index in the firmware image list for the given slot. + * + * @param slot Slot to find. + * + * @return Slot index on success, or negative error code on failure. + */ +int bt_mesh_dfu_slot_img_idx_get(const struct bt_mesh_dfu_slot *slot); + +/** @brief Iterate through all DFU image slots. + * + * Calls the callback for every DFU image slot or until the callback returns + * something other than @ref BT_MESH_DFU_ITER_CONTINUE. + * + * @param cb Callback to call for each slot, or NULL to just count the + * number of slots. + * @param user_data User data to pass to the callback. + * + * @return The number of slots iterated over. + */ +size_t bt_mesh_dfu_slot_foreach(bt_mesh_dfu_slot_cb_t cb, void *user_data); diff --git a/components/bt/esp_ble_mesh/v1.1/dfu/dfu_srv.c b/components/bt/esp_ble_mesh/v1.1/dfu/dfu_srv.c new file mode 100644 index 0000000000..0cb4b5e524 --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/dfu/dfu_srv.c @@ -0,0 +1,617 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "dfu.h" +#include "blob.h" +#include "access.h" + +#define LOG_LEVEL CONFIG_BT_MESH_DFU_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_dfu_srv); + +#define UPDATE_IDX_NONE 0xff + +BUILD_ASSERT((DFU_UPDATE_START_MSG_MAXLEN + BT_MESH_MODEL_OP_LEN(BT_MESH_DFU_OP_UPDATE_START) + + BT_MESH_MIC_SHORT) <= BT_MESH_RX_SDU_MAX, + "The Firmware Update Start message does not fit into the maximum incoming SDU size."); + +BUILD_ASSERT((DFU_UPDATE_INFO_STATUS_MSG_MINLEN + + BT_MESH_MODEL_OP_LEN(BT_MESH_DFU_OP_UPDATE_INFO_STATUS) + BT_MESH_MIC_SHORT) + <= BT_MESH_TX_SDU_MAX, + "The Firmware Update Info Status message does not fit into the maximum outgoing SDU " + "size."); + +static void store_state(struct bt_mesh_dfu_srv *srv) +{ + bt_mesh_model_data_store(srv->mod, false, NULL, &srv->update, + sizeof(srv->update)); +} + +static void erase_state(struct bt_mesh_dfu_srv *srv) +{ + bt_mesh_model_data_store(srv->mod, false, NULL, NULL, 0); +} + +static void xfer_failed(struct bt_mesh_dfu_srv *srv) +{ + if (!bt_mesh_dfu_srv_is_busy(srv) || + srv->update.idx >= srv->img_count) { + return; + } + + erase_state(srv); + + if (srv->cb->end) { + srv->cb->end(srv, &srv->imgs[srv->update.idx], false); + } +} + +static enum bt_mesh_dfu_status metadata_check(struct bt_mesh_dfu_srv *srv, + uint8_t idx, + struct net_buf_simple *buf, + enum bt_mesh_dfu_effect *effect) +{ + *effect = BT_MESH_DFU_EFFECT_NONE; + + if (idx >= srv->img_count) { + return BT_MESH_DFU_ERR_FW_IDX; + } + + if (!srv->cb->check) { + return BT_MESH_DFU_SUCCESS; + } + + if (srv->cb->check(srv, &srv->imgs[idx], buf, effect)) { + *effect = BT_MESH_DFU_EFFECT_NONE; + return BT_MESH_DFU_ERR_METADATA; + } + + return BT_MESH_DFU_SUCCESS; +} + +static void apply_rsp_sent(int err, void *cb_params) +{ + struct bt_mesh_dfu_srv *srv = cb_params; + + if (err) { + /* return phase back to give client one more chance. */ + srv->update.phase = BT_MESH_DFU_PHASE_VERIFY_OK; + LOG_WRN("Apply response failed, wait for retry (err %d)", err); + return; + } + + LOG_DBG(""); + + if (!srv->cb->apply || srv->update.idx == UPDATE_IDX_NONE) { + srv->update.phase = BT_MESH_DFU_PHASE_IDLE; + store_state(srv); + LOG_DBG("Prerequisites for apply callback are wrong"); + return; + } + + store_state(srv); + + err = srv->cb->apply(srv, &srv->imgs[srv->update.idx]); + if (err) { + srv->update.phase = BT_MESH_DFU_PHASE_IDLE; + store_state(srv); + LOG_DBG("Application apply callback failed (err %d)", err); + } +} + +static void apply_rsp_sending(uint16_t duration, int err, void *cb_params) +{ + if (err) { + apply_rsp_sent(err, cb_params); + } +} + +static void verify(struct bt_mesh_dfu_srv *srv) +{ + srv->update.phase = BT_MESH_DFU_PHASE_VERIFY; + + if (srv->update.idx >= srv->img_count) { + bt_mesh_dfu_srv_rejected(srv); + return; + } + + if (!srv->cb->end) { + bt_mesh_dfu_srv_verified(srv); + return; + } + + srv->cb->end(srv, &srv->imgs[srv->update.idx], true); + if (srv->update.phase == BT_MESH_DFU_PHASE_VERIFY) { + store_state(srv); + } +} + +static int handle_info_get(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + uint8_t idx, limit; + + if (srv->update.phase == BT_MESH_DFU_PHASE_APPLYING) { + LOG_INF("Still applying, not responding"); + return -EBUSY; + } + + idx = net_buf_simple_pull_u8(buf); + limit = net_buf_simple_pull_u8(buf); + + LOG_DBG("from %u (limit: %u)", idx, limit); + + NET_BUF_SIMPLE_DEFINE(rsp, BT_MESH_TX_SDU_MAX); + bt_mesh_model_msg_init(&rsp, BT_MESH_DFU_OP_UPDATE_INFO_STATUS); + net_buf_simple_add_u8(&rsp, srv->img_count); + net_buf_simple_add_u8(&rsp, idx); + + for (; idx < srv->img_count && limit > 0; ++idx) { + uint32_t entry_len; + + if (!srv->imgs[idx].fwid) { + continue; + } + + entry_len = 2 + srv->imgs[idx].fwid_len; + if (srv->imgs[idx].uri) { + entry_len += strlen(srv->imgs[idx].uri); + } + + if (net_buf_simple_tailroom(&rsp) + BT_MESH_MIC_SHORT < entry_len) { + break; + } + + net_buf_simple_add_u8(&rsp, srv->imgs[idx].fwid_len); + net_buf_simple_add_mem(&rsp, srv->imgs[idx].fwid, + srv->imgs[idx].fwid_len); + + if (srv->imgs[idx].uri) { + size_t len = strlen(srv->imgs[idx].uri); + + net_buf_simple_add_u8(&rsp, len); + net_buf_simple_add_mem(&rsp, srv->imgs[idx].uri, len); + } else { + net_buf_simple_add_u8(&rsp, 0); + } + + limit--; + } + + if (srv->update.phase != BT_MESH_DFU_PHASE_IDLE) { + ctx->send_ttl = srv->update.ttl; + } + + bt_mesh_model_send(mod, ctx, &rsp, NULL, NULL); + + return 0; +} + +static int handle_metadata_check(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + enum bt_mesh_dfu_status status; + enum bt_mesh_dfu_effect effect; + uint8_t idx; + + BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_DFU_OP_UPDATE_METADATA_STATUS, 2); + bt_mesh_model_msg_init(&rsp, BT_MESH_DFU_OP_UPDATE_METADATA_STATUS); + + idx = net_buf_simple_pull_u8(buf); + status = metadata_check(srv, idx, buf, &effect); + + LOG_DBG("%u", idx); + + net_buf_simple_add_u8(&rsp, (status & BIT_MASK(3)) | (effect << 3)); + net_buf_simple_add_u8(&rsp, idx); + + if (srv->update.phase != BT_MESH_DFU_PHASE_IDLE) { + ctx->send_ttl = srv->update.ttl; + } + + bt_mesh_model_send(mod, ctx, &rsp, NULL, NULL); + + return 0; +} + +static void update_status_rsp(struct bt_mesh_dfu_srv *srv, + struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_dfu_status status, + const struct bt_mesh_send_cb *send_cb) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_STATUS, 14); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_STATUS); + + net_buf_simple_add_u8(&buf, ((status & BIT_MASK(3)) | + (srv->update.phase << 5))); + + if (srv->update.phase != BT_MESH_DFU_PHASE_IDLE) { + net_buf_simple_add_u8(&buf, srv->update.ttl); + net_buf_simple_add_u8(&buf, srv->update.effect); + net_buf_simple_add_le16(&buf, srv->update.timeout_base); + net_buf_simple_add_le64(&buf, srv->blob.state.xfer.id); + net_buf_simple_add_u8(&buf, srv->update.idx); + + ctx->send_ttl = srv->update.ttl; + } + + bt_mesh_model_send(srv->mod, ctx, &buf, send_cb, srv); +} + +static int handle_get(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + + LOG_DBG(""); + + update_status_rsp(srv, ctx, BT_MESH_DFU_SUCCESS, NULL); + + return 0; +} + +static inline bool is_active_update(struct bt_mesh_dfu_srv *srv, uint8_t idx, + uint16_t timeout_base, + const uint64_t *blob_id, uint8_t ttl, + uint16_t meta_checksum) +{ + return (srv->update.idx != idx || srv->blob.state.xfer.id != *blob_id || + srv->update.ttl != ttl || + srv->update.timeout_base != timeout_base || + srv->update.meta != meta_checksum); +} + +static int handle_start(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + const struct bt_mesh_blob_io *io; + uint16_t timeout_base, meta_checksum; + enum bt_mesh_dfu_status status; + uint8_t ttl, idx; + uint64_t blob_id; + int err; + struct net_buf_simple_state buf_state; + + ttl = net_buf_simple_pull_u8(buf); + timeout_base = net_buf_simple_pull_le16(buf); + blob_id = net_buf_simple_pull_le64(buf); + idx = net_buf_simple_pull_u8(buf); + meta_checksum = dfu_metadata_checksum(buf); + + LOG_DBG("%u ttl: %u extra time: %u", idx, ttl, timeout_base); + + if ((!buf->len || meta_checksum == srv->update.meta) && + srv->update.phase == BT_MESH_DFU_PHASE_TRANSFER_ERR && + srv->update.ttl == ttl && + srv->update.timeout_base == timeout_base && + srv->update.idx == idx && + srv->blob.state.xfer.id == blob_id) { + srv->update.phase = BT_MESH_DFU_PHASE_TRANSFER_ACTIVE; + status = BT_MESH_DFU_SUCCESS; + store_state(srv); + /* blob srv will resume the transfer. */ + LOG_DBG("Resuming transfer"); + goto rsp; + } + + if (bt_mesh_dfu_srv_is_busy(srv)) { + if (is_active_update(srv, idx, timeout_base, &blob_id, ttl, + meta_checksum)) { + status = BT_MESH_DFU_ERR_WRONG_PHASE; + } else { + status = BT_MESH_DFU_SUCCESS; + srv->update.ttl = ttl; + srv->blob.state.xfer.id = blob_id; + } + + LOG_WRN("Busy. Phase: %u", srv->update.phase); + goto rsp; + } + + net_buf_simple_save(buf, &buf_state); + status = metadata_check(srv, idx, buf, + (enum bt_mesh_dfu_effect *)&srv->update.effect); + net_buf_simple_restore(buf, &buf_state); + if (status != BT_MESH_DFU_SUCCESS) { + goto rsp; + } + + srv->update.ttl = ttl; + srv->update.timeout_base = timeout_base; + srv->update.meta = meta_checksum; + + io = NULL; + err = srv->cb->start(srv, &srv->imgs[idx], buf, &io); + if (err == -EALREADY || (!err && bt_mesh_has_addr(ctx->addr))) { + /* This image has already been received or this is a + * self-update. Skip the transfer phase and proceed to + * verifying update. + */ + status = BT_MESH_DFU_SUCCESS; + srv->update.idx = idx; + srv->blob.state.xfer.id = blob_id; + srv->update.phase = BT_MESH_DFU_PHASE_VERIFY; + update_status_rsp(srv, ctx, status, NULL); + verify(srv); + return 0; + } + + if (err == -ENOMEM) { + status = BT_MESH_DFU_ERR_RESOURCES; + goto rsp; + } + + if (err == -EBUSY) { + status = BT_MESH_DFU_ERR_TEMPORARILY_UNAVAILABLE; + goto rsp; + } + + if (err || !io || !io->wr) { + status = BT_MESH_DFU_ERR_INTERNAL; + goto rsp; + } + + err = bt_mesh_blob_srv_recv(&srv->blob, blob_id, io, + ttl, timeout_base); + if (err) { + status = BT_MESH_DFU_ERR_BLOB_XFER_BUSY; + goto rsp; + } + + srv->update.idx = idx; + srv->update.phase = BT_MESH_DFU_PHASE_TRANSFER_ACTIVE; + status = BT_MESH_DFU_SUCCESS; + store_state(srv); + +rsp: + update_status_rsp(srv, ctx, status, NULL); + + return 0; +} + +static int handle_cancel(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + + if (srv->update.idx == UPDATE_IDX_NONE) { + goto rsp; + } + + LOG_DBG(""); + + bt_mesh_blob_srv_cancel(&srv->blob); + srv->update.phase = BT_MESH_DFU_PHASE_IDLE; + xfer_failed(srv); + +rsp: + update_status_rsp(srv, ctx, BT_MESH_DFU_SUCCESS, NULL); + + return 0; +} + +static int handle_apply(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + static const struct bt_mesh_send_cb send_cb = { + .start = apply_rsp_sending, + .end = apply_rsp_sent, + }; + + LOG_DBG(""); + + if (srv->update.phase == BT_MESH_DFU_PHASE_APPLYING) { + update_status_rsp(srv, ctx, BT_MESH_DFU_SUCCESS, NULL); + return 0; + } + + if (srv->update.phase != BT_MESH_DFU_PHASE_VERIFY_OK) { + LOG_WRN("Apply: Invalid phase %u", srv->update.phase); + update_status_rsp(srv, ctx, BT_MESH_DFU_ERR_WRONG_PHASE, NULL); + return 0; + } + + /* Postponing the apply callback until the response has been sent, in + * case it triggers a reboot: + */ + srv->update.phase = BT_MESH_DFU_PHASE_APPLYING; + update_status_rsp(srv, ctx, BT_MESH_DFU_SUCCESS, &send_cb); + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_dfu_srv_op[] = { + { BT_MESH_DFU_OP_UPDATE_INFO_GET, BT_MESH_LEN_EXACT(2), handle_info_get }, + { BT_MESH_DFU_OP_UPDATE_METADATA_CHECK, BT_MESH_LEN_MIN(1), handle_metadata_check }, + { BT_MESH_DFU_OP_UPDATE_GET, BT_MESH_LEN_EXACT(0), handle_get }, + { BT_MESH_DFU_OP_UPDATE_START, BT_MESH_LEN_MIN(12), handle_start }, + { BT_MESH_DFU_OP_UPDATE_CANCEL, BT_MESH_LEN_EXACT(0), handle_cancel }, + { BT_MESH_DFU_OP_UPDATE_APPLY, BT_MESH_LEN_EXACT(0), handle_apply }, + BT_MESH_MODEL_OP_END, +}; + +static int dfu_srv_init(const struct bt_mesh_model *mod) +{ + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + + srv->mod = mod; + srv->update.idx = UPDATE_IDX_NONE; + + if (!srv->cb || !srv->cb->start || !srv->imgs || srv->img_count == 0 || + srv->img_count == UPDATE_IDX_NONE) { + LOG_ERR("Invalid DFU Server initialization"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_MESH_MODEL_EXTENSIONS)) { + bt_mesh_model_extend(mod, srv->blob.mod); + } + + return 0; +} + +static int dfu_srv_settings_set(const struct bt_mesh_model *mod, const char *name, + size_t len_rd, settings_read_cb read_cb, + void *cb_arg) +{ + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + ssize_t len; + + if (len_rd < sizeof(srv->update)) { + return -EINVAL; + } + + len = read_cb(cb_arg, &srv->update, sizeof(srv->update)); + if (len < 0) { + return len; + } + + LOG_DBG("Recovered transfer (phase: %u, idx: %u)", srv->update.phase, + srv->update.idx); + if (srv->update.phase == BT_MESH_DFU_PHASE_TRANSFER_ACTIVE) { + LOG_DBG("Settings recovered mid-transfer, setting transfer error"); + srv->update.phase = BT_MESH_DFU_PHASE_TRANSFER_ERR; + } else if (srv->update.phase == BT_MESH_DFU_PHASE_VERIFY_OK) { + LOG_DBG("Settings recovered before application, setting verification fail"); + srv->update.phase = BT_MESH_DFU_PHASE_VERIFY_FAIL; + } + + return 0; +} + +static void dfu_srv_reset(const struct bt_mesh_model *mod) +{ + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + + srv->update.phase = BT_MESH_DFU_PHASE_IDLE; + erase_state(srv); +} + +const struct bt_mesh_model_cb _bt_mesh_dfu_srv_cb = { + .init = dfu_srv_init, + .settings_set = dfu_srv_settings_set, + .reset = dfu_srv_reset, +}; + +static void blob_suspended(struct bt_mesh_blob_srv *b) +{ + struct bt_mesh_dfu_srv *srv = CONTAINER_OF(b, struct bt_mesh_dfu_srv, blob); + + srv->update.phase = BT_MESH_DFU_PHASE_TRANSFER_ERR; + store_state(srv); +} + +static void blob_end(struct bt_mesh_blob_srv *b, uint64_t id, bool success) +{ + struct bt_mesh_dfu_srv *srv = + CONTAINER_OF(b, struct bt_mesh_dfu_srv, blob); + + LOG_DBG("success: %u", success); + + if (!success) { + srv->update.phase = BT_MESH_DFU_PHASE_TRANSFER_ERR; + xfer_failed(srv); + return; + } + + verify(srv); +} + +static int blob_recover(struct bt_mesh_blob_srv *b, + struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_io **io) +{ + struct bt_mesh_dfu_srv *srv = + CONTAINER_OF(b, struct bt_mesh_dfu_srv, blob); + + if (!srv->cb->recover || + srv->update.phase != BT_MESH_DFU_PHASE_TRANSFER_ERR || + srv->update.idx >= srv->img_count) { + return -ENOTSUP; + } + + return srv->cb->recover(srv, &srv->imgs[srv->update.idx], io); +} + +const struct bt_mesh_blob_srv_cb _bt_mesh_dfu_srv_blob_cb = { + .suspended = blob_suspended, + .end = blob_end, + .recover = blob_recover, +}; + +void bt_mesh_dfu_srv_verified(struct bt_mesh_dfu_srv *srv) +{ + if (srv->update.phase != BT_MESH_DFU_PHASE_VERIFY) { + LOG_WRN("Wrong state"); + return; + } + + LOG_DBG(""); + + srv->update.phase = BT_MESH_DFU_PHASE_VERIFY_OK; + store_state(srv); +} + +void bt_mesh_dfu_srv_rejected(struct bt_mesh_dfu_srv *srv) +{ + if (srv->update.phase != BT_MESH_DFU_PHASE_VERIFY) { + LOG_WRN("Wrong state"); + return; + } + + LOG_DBG(""); + + srv->update.phase = BT_MESH_DFU_PHASE_VERIFY_FAIL; + store_state(srv); +} + +void bt_mesh_dfu_srv_cancel(struct bt_mesh_dfu_srv *srv) +{ + if (srv->update.phase == BT_MESH_DFU_PHASE_IDLE) { + LOG_WRN("Wrong state"); + return; + } + + (void)bt_mesh_blob_srv_cancel(&srv->blob); +} + +void bt_mesh_dfu_srv_applied(struct bt_mesh_dfu_srv *srv) +{ + if (srv->update.phase != BT_MESH_DFU_PHASE_APPLYING) { + LOG_WRN("Wrong state"); + return; + } + + LOG_DBG(""); + + srv->update.phase = BT_MESH_DFU_PHASE_IDLE; + store_state(srv); +} + +bool bt_mesh_dfu_srv_is_busy(const struct bt_mesh_dfu_srv *srv) +{ + return srv->update.phase != BT_MESH_DFU_PHASE_IDLE && + srv->update.phase != BT_MESH_DFU_PHASE_TRANSFER_ERR && + srv->update.phase != BT_MESH_DFU_PHASE_VERIFY_FAIL; +} + +uint8_t bt_mesh_dfu_srv_progress(const struct bt_mesh_dfu_srv *srv) +{ + if (!bt_mesh_dfu_srv_is_busy(srv)) { + return 0U; + } + + if (srv->update.phase == BT_MESH_DFU_PHASE_TRANSFER_ACTIVE) { + return bt_mesh_blob_srv_progress(&srv->blob); + } + + return 100U; +} diff --git a/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfd.h b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfd.h new file mode 100644 index 0000000000..cddde76618 --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfd.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_H__ +#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup bt_mesh_dfd Firmware Distribution models + * @ingroup bt_mesh + * @{ + */ + +/** Firmware distribution status. */ +enum bt_mesh_dfd_status { + /** The message was processed successfully. */ + BT_MESH_DFD_SUCCESS, + + /** Insufficient resources on the node. */ + BT_MESH_DFD_ERR_INSUFFICIENT_RESOURCES, + + /** The operation cannot be performed while the Server is in the current + * phase. + */ + BT_MESH_DFD_ERR_WRONG_PHASE, + + /** An internal error occurred on the node. */ + BT_MESH_DFD_ERR_INTERNAL, + + /** The requested firmware image is not stored on the Distributor. */ + BT_MESH_DFD_ERR_FW_NOT_FOUND, + + /** The AppKey identified by the AppKey Index is not known to the node. + */ + BT_MESH_DFD_ERR_INVALID_APPKEY_INDEX, + + /** There are no Target nodes in the Distribution Receivers List + * state. + */ + BT_MESH_DFD_ERR_RECEIVERS_LIST_EMPTY, + + /** Another firmware image distribution is in progress. */ + BT_MESH_DFD_ERR_BUSY_WITH_DISTRIBUTION, + + /** Another upload is in progress. */ + BT_MESH_DFD_ERR_BUSY_WITH_UPLOAD, + + /** The URI scheme name indicated by the Update URI is not supported. */ + BT_MESH_DFD_ERR_URI_NOT_SUPPORTED, + + /** The format of the Update URI is invalid. */ + BT_MESH_DFD_ERR_URI_MALFORMED, + + /** The URI is currently unreachable. */ + BT_MESH_DFD_ERR_URI_UNREACHABLE, + + /** The Check Firmware OOB procedure did not find any new firmware. */ + BT_MESH_DFD_ERR_NEW_FW_NOT_AVAILABLE, + + /** The suspension of the Distribute Firmware procedure failed. */ + BT_MESH_DFD_ERR_SUSPEND_FAILED, +}; + +/** Firmware distribution phases. */ +enum bt_mesh_dfd_phase { + /** No firmware distribution is in progress. */ + BT_MESH_DFD_PHASE_IDLE, + + /** Firmware distribution is in progress. */ + BT_MESH_DFD_PHASE_TRANSFER_ACTIVE, + + /** The Transfer BLOB procedure has completed successfully. */ + BT_MESH_DFD_PHASE_TRANSFER_SUCCESS, + + /** The Apply Firmware on Target Nodes procedure is being executed. */ + BT_MESH_DFD_PHASE_APPLYING_UPDATE, + + /** The Distribute Firmware procedure has completed successfully. */ + BT_MESH_DFD_PHASE_COMPLETED, + + /** The Distribute Firmware procedure has failed. */ + BT_MESH_DFD_PHASE_FAILED, + + /** The Cancel Firmware Update procedure is being executed. */ + BT_MESH_DFD_PHASE_CANCELING_UPDATE, + + /** The Transfer BLOB procedure is suspended. */ + BT_MESH_DFD_PHASE_TRANSFER_SUSPENDED, +}; + +/** Firmware upload phases. */ +enum bt_mesh_dfd_upload_phase { + /** No firmware upload is in progress. */ + BT_MESH_DFD_UPLOAD_PHASE_IDLE, + + /** The Store Firmware procedure is being executed. */ + BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ACTIVE, + + /** The Store Firmware procedure or Store Firmware OOB procedure failed. + */ + BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ERROR, + + /** The Store Firmware procedure or the Store Firmware OOB procedure + * completed successfully. + */ + BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_SUCCESS, +}; + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_H__ */ diff --git a/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfd_srv.h b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfd_srv.h new file mode 100644 index 0000000000..2abfbf0f90 --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfd_srv.h @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @defgroup bt_mesh_dfd_srv Firmware Distribution Server model + * @ingroup bt_mesh_dfd + * @{ + * @brief API for the Firmware Distribution Server model + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_SRV_H__ +#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_SRV_H__ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX +#define CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX 0 +#endif + +#ifndef CONFIG_BT_MESH_DFD_SRV_SLOT_MAX_SIZE +#define CONFIG_BT_MESH_DFD_SRV_SLOT_MAX_SIZE 0 +#endif + +#ifndef CONFIG_BT_MESH_DFD_SRV_SLOT_SPACE +#define CONFIG_BT_MESH_DFD_SRV_SLOT_SPACE 0 +#endif + +struct bt_mesh_dfd_srv; + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD +/** + * + * @brief Initialization parameters for the @ref bt_mesh_dfd_srv with OOB + * upload support. + * + * @param[in] _cb Pointer to a @ref bt_mesh_dfd_srv_cb instance. + * @param[in] _oob_schemes Array of OOB schemes supported by the server, + * each scheme being a code point from the + * Bluetooth SIG Assigned Numbers document. + * @param[in] _oob_schemes_count Number of schemes in @c _oob_schemes. + */ +#define BT_MESH_DFD_SRV_OOB_INIT(_cb, _oob_schemes, _oob_schemes_count) \ + { \ + .cb = _cb, \ + .dfu = BT_MESH_DFU_CLI_INIT(&_bt_mesh_dfd_srv_dfu_cb), \ + .upload = { \ + .blob = { .cb = &_bt_mesh_dfd_srv_blob_cb }, \ + }, \ + .oob_schemes = { \ + .schemes = _oob_schemes, \ + .count = _oob_schemes_count, \ + }, \ + } +#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */ + +/** + * + * @brief Initialization parameters for the @ref bt_mesh_dfd_srv. + * + * @param[in] _cb Pointer to a @ref bt_mesh_dfd_srv_cb instance. + */ +#define BT_MESH_DFD_SRV_INIT(_cb) \ + { \ + .cb = _cb, \ + .dfu = BT_MESH_DFU_CLI_INIT(&_bt_mesh_dfd_srv_dfu_cb), \ + .upload = { \ + .blob = { .cb = &_bt_mesh_dfd_srv_blob_cb }, \ + }, \ + } + +/** + * + * @brief Firmware Distribution Server model Composition Data entry. + * + * @param _srv Pointer to a @ref bt_mesh_dfd_srv instance. + */ +#define BT_MESH_MODEL_DFD_SRV(_srv) \ + BT_MESH_MODEL_DFU_CLI(&(_srv)->dfu), \ + BT_MESH_MODEL_BLOB_SRV(&(_srv)->upload.blob), \ + BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_DFD_SRV, _bt_mesh_dfd_srv_op, NULL, \ + _srv, &_bt_mesh_dfd_srv_cb) + +/** Firmware Distribution Server callbacks: */ +struct bt_mesh_dfd_srv_cb { + + /** @brief Slot receive callback. + * + * Called at the start of an upload procedure. The callback must fill + * @c io with a pointer to a writable BLOB stream for the Firmware Distribution + * Server to write the firmware image to. + * + * @param srv Firmware Distribution Server model instance. + * @param slot DFU image slot being received. + * @param io BLOB stream response pointer. + * + * @return 0 on success, or (negative) error code otherwise. + */ + int (*recv)(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot, + const struct bt_mesh_blob_io **io); + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + /** @brief Firmware upload OOB start callback. + * + * Called at the start of an OOB firmware upload. The application must + * start a firmware check using an OOB mechanism, and then call + * @ref bt_mesh_dfd_srv_oob_check_complete. Depending on the return + * value of this function, the application must then start storing the + * firmware image using an OOB mechanism, and call + * @ref bt_mesh_dfd_srv_oob_store_complete. This callback is mandatory + * to support OOB uploads. + * + * @param srv Firmware Distribution Server model instance. + * @param slot Slot to be used for the upload. + * @param uri Pointer to buffer containing the URI used to + * check for new firmware. + * @param uri_len Length of the URI buffer. + * @param fwid Pointer to buffer containing the current + * firmware ID to be used when checking for + * availability of new firmware. + * @param fwid_len Length of the current firmware ID. Must be set + * to the length of the new firmware ID if it is + * available, or to 0 if new firmware is not + * available. + * + * @return BT_MESH_DFD_SUCCESS on success, or error code otherwise. + */ + int (*start_oob_upload)(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot, + const char *uri, uint8_t uri_len, + const uint8_t *fwid, uint16_t fwid_len); + + /** @brief Cancel store OOB callback + * + * Called when an OOB store is cancelled. The application must stop + * any ongoing OOB image transfer. This callback is mandatory to + * support OOB uploads. + * + * @param srv Firmware Distribution Server model instance. + * @param slot DFU image slot to cancel + */ + void (*cancel_oob_upload)(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot); + + /** @brief Get the progress of an ongoing OOB store + * + * Called by the Firmware Distribution Server model when it needs to + * get the current progress of an ongoing OOB store from the + * application. This callback is mandatory to support OOB uploads. + * + * @param srv Firmware Distribution Server model instance. + * @param slot DFU image slot to get progress for. + * + * @return The current progress of the ongoing OOB store, in percent. + */ + uint8_t (*oob_progress_get)(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot); +#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */ + + /** @brief Slot delete callback. + * + * Called when the Firmware Distribution Server is about to delete a DFU image slot. + * All allocated data associated with the firmware slot should be + * deleted. + * + * @param srv Firmware Update Server instance. + * @param slot DFU image slot being deleted. + */ + void (*del)(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot); + + /** @brief Slot send callback. + * + * Called at the start of a distribution procedure. The callback must + * fill @c io with a pointer to a readable BLOB stream for the Firmware + * Distribution Server to read the firmware image from. + * + * @param srv Firmware Distribution Server model instance. + * @param slot DFU image slot being sent. + * @param io BLOB stream response pointer. + * + * @return 0 on success, or (negative) error code otherwise. + */ + int (*send)(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot, + const struct bt_mesh_blob_io **io); + + /** @brief Phase change callback (Optional). + * + * Called whenever the phase of the Firmware Distribution Server changes. + * + * @param srv Firmware Distribution Server model instance. + * @param phase New Firmware Distribution phase. + */ + void (*phase)(struct bt_mesh_dfd_srv *srv, enum bt_mesh_dfd_phase phase); +}; + +/** Firmware Distribution Server instance. */ +struct bt_mesh_dfd_srv { + const struct bt_mesh_dfd_srv_cb *cb; + const struct bt_mesh_model *mod; + struct bt_mesh_dfu_cli dfu; + struct bt_mesh_dfu_target targets[CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX]; + struct bt_mesh_blob_target_pull pull_ctxs[CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX]; + const struct bt_mesh_blob_io *io; + uint16_t target_cnt; + uint16_t slot_idx; + bool apply; + enum bt_mesh_dfd_phase phase; + struct bt_mesh_blob_cli_inputs inputs; + + struct { + enum bt_mesh_dfd_upload_phase phase; + struct bt_mesh_dfu_slot *slot; + const struct flash_area *area; + struct bt_mesh_blob_srv blob; +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + bool is_oob; + bool is_pending_oob_check; + struct { + uint8_t uri_len; + uint8_t uri[CONFIG_BT_MESH_DFU_URI_MAXLEN]; + uint16_t current_fwid_len; + uint8_t current_fwid[CONFIG_BT_MESH_DFU_FWID_MAXLEN]; + struct bt_mesh_msg_ctx ctx; + } oob; +#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */ + } upload; + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + struct { + const uint8_t *schemes; + const uint8_t count; + } oob_schemes; +#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */ +}; + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD +/** @brief Call when an OOB check has completed or failed + * + * This should be called by the application after an OOB check started by the @c start_oob_upload + * callback has completed or failed. The @p status param should be set to one of the following + * values: + * + * * @c BT_MESH_DFD_SUCCESS if the check was successful and a new firmware ID was found. + * * @c BT_MESH_DFD_ERR_URI_MALFORMED if the URI is not formatted correctly. + * * @c BT_MESH_DFD_ERR_URI_NOT_SUPPORTED if the URI scheme is not supported by the node. + * * @c BT_MESH_DFD_ERR_URI_UNREACHABLE if the URI can't be reached. + * * @c BT_MESH_DFD_ERR_NEW_FW_NOT_AVAILABLE if the check completes successfully but no new + * firmware is available. + * + * If this function returns 0, the application should then download the firmware to the + * slot. If an error code is returned, the application should abort the OOB upload. + * + * @param srv Firmware Distribution Server model instance. + * @param slot The slot used in the OOB upload. + * @param status The result of the firmware check. + * @param fwid If the check was successful and new firmware found, this should point to a + * buffer containing the new firmware ID to store. + * @param fwid_len The length of the firmware ID pointed to by @p fwid. + * + * @return 0 on success, (negative) error code otherwise. + */ +int bt_mesh_dfd_srv_oob_check_complete(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot, int status, + uint8_t *fwid, size_t fwid_len); + +/** @brief Call when an OOB store has completed or failed + * + * This should be called by the application after an OOB store started after a successful call to + * @c bt_mesh_dfd_srv_oob_check_complete has completed successfully or failed. + * + * @param srv Firmware Distribution Server model instance. + * @param slot The slot used when storing the firmware image. + * @param success @c true if the OOB store completed successfully, @c false otherwise. + * @param size The size of the stored firmware image, in bytes. + * @param metadata Pointer to the metadata received OOB, or @c NULL if no metadata was + * received. + * @param metadata_len Size of the metadata pointed to by @p metadata. + * + * @return 0 on success, (negative) error code otherwise. + */ +int bt_mesh_dfd_srv_oob_store_complete(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot, bool success, + size_t size, const uint8_t *metadata, size_t metadata_len); +#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */ + +/** @cond INTERNAL_HIDDEN */ +extern const struct bt_mesh_model_op _bt_mesh_dfd_srv_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_dfd_srv_cb; +extern const struct bt_mesh_dfu_cli_cb _bt_mesh_dfd_srv_dfu_cb; +extern const struct bt_mesh_blob_srv_cb _bt_mesh_dfd_srv_blob_cb; +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_SRV_H__ */ + +/** @} */ diff --git a/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfu.h b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfu.h new file mode 100644 index 0000000000..4745355f03 --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfu.h @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_H__ +#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_H__ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup bt_mesh_dfu Bluetooth Mesh Device Firmware Update + * @ingroup bt_mesh + * @{ + */ + +#ifndef CONFIG_BT_MESH_DFU_FWID_MAXLEN +#define CONFIG_BT_MESH_DFU_FWID_MAXLEN 0 +#endif + +#ifndef CONFIG_BT_MESH_DFU_METADATA_MAXLEN +#define CONFIG_BT_MESH_DFU_METADATA_MAXLEN 0 +#endif + +#ifndef CONFIG_BT_MESH_DFU_URI_MAXLEN +#define CONFIG_BT_MESH_DFU_URI_MAXLEN 0 +#endif + +#ifndef CONFIG_BT_MESH_DFU_SLOT_CNT +#define CONFIG_BT_MESH_DFU_SLOT_CNT 0 +#endif + +/** DFU transfer phase. */ +enum bt_mesh_dfu_phase { + /** Ready to start a Receive Firmware procedure. */ + BT_MESH_DFU_PHASE_IDLE, + + /** The Transfer BLOB procedure failed. */ + BT_MESH_DFU_PHASE_TRANSFER_ERR, + + /** The Receive Firmware procedure is being executed. */ + BT_MESH_DFU_PHASE_TRANSFER_ACTIVE, + + /** The Verify Firmware procedure is being executed. */ + BT_MESH_DFU_PHASE_VERIFY, + + /** The Verify Firmware procedure completed successfully. */ + BT_MESH_DFU_PHASE_VERIFY_OK, + + /** The Verify Firmware procedure failed. */ + BT_MESH_DFU_PHASE_VERIFY_FAIL, + + /** The Apply New Firmware procedure is being executed. */ + BT_MESH_DFU_PHASE_APPLYING, + + /** Firmware transfer has been canceled. */ + BT_MESH_DFU_PHASE_TRANSFER_CANCELED, + + /** Firmware applying succeeded. */ + BT_MESH_DFU_PHASE_APPLY_SUCCESS, + + /** Firmware applying failed. */ + BT_MESH_DFU_PHASE_APPLY_FAIL, + + /** Phase of a node was not yet retrieved. */ + BT_MESH_DFU_PHASE_UNKNOWN, +}; + + +/** DFU status. */ +enum bt_mesh_dfu_status { + /** The message was processed successfully. */ + BT_MESH_DFU_SUCCESS, + + /** Insufficient resources on the node */ + BT_MESH_DFU_ERR_RESOURCES, + + /** The operation cannot be performed while the Server is in the current + * phase. + */ + BT_MESH_DFU_ERR_WRONG_PHASE, + + /** An internal error occurred on the node. */ + BT_MESH_DFU_ERR_INTERNAL, + + /** The message contains a firmware index value that is not expected. */ + BT_MESH_DFU_ERR_FW_IDX, + + /** The metadata check failed. */ + BT_MESH_DFU_ERR_METADATA, + + /** The Server cannot start a firmware update. */ + BT_MESH_DFU_ERR_TEMPORARILY_UNAVAILABLE, + + /** Another BLOB transfer is in progress. */ + BT_MESH_DFU_ERR_BLOB_XFER_BUSY, +}; + +/** Expected effect of a DFU transfer. */ +enum bt_mesh_dfu_effect { + /** No changes to node Composition Data. */ + BT_MESH_DFU_EFFECT_NONE, + + /** Node Composition Data changed and the node does not support remote + * provisioning. + */ + BT_MESH_DFU_EFFECT_COMP_CHANGE_NO_RPR, + + /** Node Composition Data changed, and remote provisioning is supported. + * The node supports remote provisioning and Composition Data Page + * 0x80. Page 0x80 contains different Composition Data than Page 0x0. + */ + BT_MESH_DFU_EFFECT_COMP_CHANGE, + + /** Node will be unprovisioned after the update. */ + BT_MESH_DFU_EFFECT_UNPROV, +}; + +/** Action for DFU iteration callbacks. */ +enum bt_mesh_dfu_iter { + /** Stop iterating. */ + BT_MESH_DFU_ITER_STOP, + + /** Continue iterating. */ + BT_MESH_DFU_ITER_CONTINUE, +}; + +/** DFU image instance. + * + * Each DFU image represents a single updatable firmware image. + */ +struct bt_mesh_dfu_img { + /** Firmware ID. */ + const void *fwid; + + /** Length of the firmware ID. */ + size_t fwid_len; + + /** Update URI, or NULL. */ + const char *uri; +}; + +/** DFU image slot for DFU distribution. */ +struct bt_mesh_dfu_slot { + /** Size of the firmware in bytes. */ + size_t size; + /** Length of the firmware ID. */ + size_t fwid_len; + /** Length of the metadata. */ + size_t metadata_len; + /** Firmware ID. */ + uint8_t fwid[CONFIG_BT_MESH_DFU_FWID_MAXLEN]; + /** Metadata. */ + uint8_t metadata[CONFIG_BT_MESH_DFU_METADATA_MAXLEN]; +}; + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_H__ */ diff --git a/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfu_cli.h b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfu_cli.h new file mode 100644 index 0000000000..ad8881ebc2 --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfu_cli.h @@ -0,0 +1,409 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @defgroup bt_mesh_dfu_cli Firmware Uppdate Client model + * @ingroup bt_mesh_dfu + * @{ + * @brief API for the Bluetooth Mesh Firmware Update Client model + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_CLI_H__ +#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_CLI_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct bt_mesh_dfu_cli; + +/** + * + * @brief Initialization parameters for the @ref bt_mesh_dfu_cli. + * + * @sa bt_mesh_dfu_cli_cb. + * + * @param _handlers Handler callback structure. + */ +#define BT_MESH_DFU_CLI_INIT(_handlers) \ + { \ + .cb = _handlers, \ + .blob = { .cb = &_bt_mesh_dfu_cli_blob_handlers }, \ + } + +/** + * + * @brief Firmware Update Client model Composition Data entry. + * + * @param _cli Pointer to a @ref bt_mesh_dfu_cli instance. + */ +#define BT_MESH_MODEL_DFU_CLI(_cli) \ + BT_MESH_MODEL_BLOB_CLI(&(_cli)->blob), \ + BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_DFU_CLI, _bt_mesh_dfu_cli_op, NULL, \ + _cli, &_bt_mesh_dfu_cli_cb) + +/** DFU Target node. */ +struct bt_mesh_dfu_target { + /** BLOB Target node */ + struct bt_mesh_blob_target blob; + /** Image index on the Target node */ + uint8_t img_idx; + /** Expected DFU effect, see @ref bt_mesh_dfu_effect. */ + uint8_t effect; + /** Current DFU status, see @ref bt_mesh_dfu_status. */ + uint8_t status; + /** Current DFU phase, see @ref bt_mesh_dfu_phase. */ + uint8_t phase; +}; + +/** Metadata status response. */ +struct bt_mesh_dfu_metadata_status { + /** Image index. */ + uint8_t idx; + /** Status code. */ + enum bt_mesh_dfu_status status; + /** Effect of transfer. */ + enum bt_mesh_dfu_effect effect; +}; + +/** DFU Target node status parameters. */ +struct bt_mesh_dfu_target_status { + /** Status of the previous operation. */ + enum bt_mesh_dfu_status status; + /** Phase of the current DFU transfer. */ + enum bt_mesh_dfu_phase phase; + /** The effect the update will have on the Target device's state. */ + enum bt_mesh_dfu_effect effect; + /** BLOB ID used in the transfer. */ + uint64_t blob_id; + /** Image index to transfer. */ + uint8_t img_idx; + /** TTL used in the transfer. */ + uint8_t ttl; + + /** Additional response time for the Target nodes, in 10-second increments. + * + * The extra time can be used to give the Target nodes more time to respond + * to messages from the Client. The actual timeout will be calculated + * according to the following formula: + * + * @verbatim + * timeout = 20 seconds + (10 seconds * timeout_base) + (100 ms * TTL) + * @endverbatim + * + * If a Target node fails to respond to a message from the Client within the + * configured transfer timeout, the Target node is dropped. + */ + uint16_t timeout_base; +}; + +/** @brief DFU image callback. + * + * The image callback is called for every DFU image on the Target node when + * calling @ref bt_mesh_dfu_cli_imgs_get. + * + * @param cli Firmware Update Client model instance. + * @param ctx Message context of the received message. + * @param idx Image index. + * @param total Total number of images on the Target node. + * @param img Image information for the given image index. + * @param cb_data Callback data. + * + * @retval BT_MESH_DFU_ITER_STOP Stop iterating through the image list and + * return from @ref bt_mesh_dfu_cli_imgs_get. + * @retval BT_MESH_DFU_ITER_CONTINUE Continue iterating through the image list + * if any images remain. + */ +typedef enum bt_mesh_dfu_iter (*bt_mesh_dfu_img_cb_t)( + struct bt_mesh_dfu_cli *cli, struct bt_mesh_msg_ctx *ctx, uint8_t idx, + uint8_t total, const struct bt_mesh_dfu_img *img, void *cb_data); + +/** Firmware Update Client event callbacks. */ +struct bt_mesh_dfu_cli_cb { + /** @brief BLOB transfer is suspended. + * + * Called when the BLOB transfer is suspended due to response timeout from all Target nodes. + * + * @param cli Firmware Update Client model instance. + */ + void (*suspended)(struct bt_mesh_dfu_cli *cli); + + /** @brief DFU ended. + * + * Called when the DFU transfer ends, either because all Target nodes were + * lost or because the transfer was completed successfully. + * + * @param cli Firmware Update Client model instance. + * @param reason Reason for ending. + */ + void (*ended)(struct bt_mesh_dfu_cli *cli, + enum bt_mesh_dfu_status reason); + + /** @brief DFU transfer applied on all active Target nodes. + * + * Called at the end of the apply procedure started by @ref + * bt_mesh_dfu_cli_apply. + * + * @param cli Firmware Update Client model instance. + */ + void (*applied)(struct bt_mesh_dfu_cli *cli); + + /** @brief DFU transfer confirmed on all active Target nodes. + * + * Called at the end of the apply procedure started by @ref + * bt_mesh_dfu_cli_confirm. + * + * @param cli Firmware Update Client model instance. + */ + void (*confirmed)(struct bt_mesh_dfu_cli *cli); + + /** @brief DFU Target node was lost. + * + * A DFU Target node was dropped from the receivers list. The Target node's + * @c status is set to reflect the reason for the failure. + * + * @param cli Firmware Update Client model instance. + * @param target DFU Target node that was lost. + */ + void (*lost_target)(struct bt_mesh_dfu_cli *cli, + struct bt_mesh_dfu_target *target); +}; + +/** Firmware Update Client model instance. + * + * Should be initialized with @ref BT_MESH_DFU_CLI_INIT. + */ +struct bt_mesh_dfu_cli { + /** Callback structure. */ + const struct bt_mesh_dfu_cli_cb *cb; + /** Underlying BLOB Transfer Client. */ + struct bt_mesh_blob_cli blob; + + /* runtime state */ + + uint32_t op; + const struct bt_mesh_model *mod; + + struct { + const struct bt_mesh_dfu_slot *slot; + const struct bt_mesh_blob_io *io; + struct bt_mesh_blob_xfer blob; + uint8_t state; + uint8_t flags; + } xfer; + + struct { + uint8_t ttl; + uint8_t type; + uint8_t img_cnt; + uint16_t addr; + struct k_sem sem; + void *params; + bt_mesh_dfu_img_cb_t img_cb; + } req; +}; + +/** BLOB parameters for Firmware Update Client transfer: */ +struct bt_mesh_dfu_cli_xfer_blob_params { + /* Logarithmic representation of the block size. */ + uint8_t block_size_log; + /** Base chunk size. May be smaller for the last chunk. */ + uint16_t chunk_size; +}; + +/** Firmware Update Client transfer parameters: */ +struct bt_mesh_dfu_cli_xfer { + /** BLOB ID to use for this transfer, or 0 to set it randomly. */ + uint64_t blob_id; + /** DFU image slot to transfer. */ + const struct bt_mesh_dfu_slot *slot; + /** Transfer mode (Push (Push BLOB Transfer Mode) or Pull (Pull BLOB Transfer Mode)) */ + enum bt_mesh_blob_xfer_mode mode; + /** BLOB parameters to be used for the transfer, or NULL to retrieve Target nodes' + * capabilities before sending a firmware. + */ + const struct bt_mesh_dfu_cli_xfer_blob_params *blob_params; +}; + +/** @brief Start distributing a DFU. + * + * Starts distribution of the firmware in the given slot to the list of DFU + * Target nodes in @c ctx. The transfer runs in the background, and its end is + * signalled through the @ref bt_mesh_dfu_cli_cb::ended callback. + * + * @note The BLOB Transfer Client transfer inputs @c targets list must point to a list of @ref + * bt_mesh_dfu_target nodes. + * + * @param cli Firmware Update Client model instance. + * @param inputs BLOB Transfer Client transfer inputs. + * @param io BLOB stream to read BLOB from. + * @param xfer Firmware Update Client transfer parameters. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_dfu_cli_send(struct bt_mesh_dfu_cli *cli, + const struct bt_mesh_blob_cli_inputs *inputs, + const struct bt_mesh_blob_io *io, + const struct bt_mesh_dfu_cli_xfer *xfer); + +/** @brief Suspend a DFU transfer. + * + * @param cli Firmware Update Client instance. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_dfu_cli_suspend(struct bt_mesh_dfu_cli *cli); + +/** @brief Resume the suspended transfer. + * + * @param cli Firmware Update Client instance. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_dfu_cli_resume(struct bt_mesh_dfu_cli *cli); + +/** @brief Cancel a DFU transfer. + * + * Will cancel the ongoing DFU transfer, or the transfer on a specific Target + * node if @c ctx is valid. + * + * @param cli Firmware Update Client model instance. + * @param ctx Message context, or NULL to cancel the ongoing DFU transfer. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_dfu_cli_cancel(struct bt_mesh_dfu_cli *cli, + struct bt_mesh_msg_ctx *ctx); + +/** @brief Apply the completed DFU transfer. + * + * A transfer can only be applied after it has ended successfully. The Firmware + * Update Client's @c applied callback is called at the end of the apply procedure. + * + * @param cli Firmware Update Client model instance. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_dfu_cli_apply(struct bt_mesh_dfu_cli *cli); + +/** @brief Confirm that the active transfer has been applied on the Target nodes. + * + * A transfer can only be confirmed after it has been applied. The Firmware Update + * Client's @c confirmed callback is called at the end of the confirm + * procedure. + * + * Target nodes that have reported the effect as @ref BT_MESH_DFU_EFFECT_UNPROV + * are expected to not respond to the query, and will fail if they do. + * + * @param cli Firmware Update Client model instance. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_dfu_cli_confirm(struct bt_mesh_dfu_cli *cli); + +/** @brief Get progress as a percentage of completion. + * + * @param cli Firmware Update Client model instance. + * + * @return The progress of the current transfer in percent, or 0 if no + * transfer is active. + */ +uint8_t bt_mesh_dfu_cli_progress(struct bt_mesh_dfu_cli *cli); + +/** @brief Check whether a DFU transfer is in progress. + * + * @param cli Firmware Update Client model instance. + * + * @return true if the BLOB Transfer Client is currently participating in a transfer, + * false otherwise. + */ +bool bt_mesh_dfu_cli_is_busy(struct bt_mesh_dfu_cli *cli); + +/** @brief Perform a DFU image list request. + * + * Requests the full list of DFU images on a Target node, and iterates through + * them, calling the @c cb for every image. + * + * The DFU image list request can be used to determine which image index the + * Target node holds its different firmwares in. + * + * Waits for a response until the procedure timeout expires. + * + * @param cli Firmware Update Client model instance. + * @param ctx Message context. + * @param cb Callback to call for each image index. + * @param cb_data Callback data to pass to @c cb. + * @param max_count Max number of images to return. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_dfu_cli_imgs_get(struct bt_mesh_dfu_cli *cli, + struct bt_mesh_msg_ctx *ctx, + bt_mesh_dfu_img_cb_t cb, void *cb_data, + uint8_t max_count); + +/** @brief Perform a metadata check for the given DFU image slot. + * + * The metadata check procedure allows the Firmware Update Client to check if a Target + * node will accept a transfer of this DFU image slot, and what the effect would be. + * + * Waits for a response until the procedure timeout expires. + * + * @param cli Firmware Update Client model instance. + * @param ctx Message context. + * @param img_idx Target node's image index to check. + * @param slot DFU image slot to check for. + * @param rsp Metadata status response buffer. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_dfu_cli_metadata_check(struct bt_mesh_dfu_cli *cli, + struct bt_mesh_msg_ctx *ctx, uint8_t img_idx, + const struct bt_mesh_dfu_slot *slot, + struct bt_mesh_dfu_metadata_status *rsp); + +/** @brief Get the status of a Target node. + * + * @param cli Firmware Update Client model instance. + * @param ctx Message context. + * @param rsp Response data buffer. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_dfu_cli_status_get(struct bt_mesh_dfu_cli *cli, + struct bt_mesh_msg_ctx *ctx, + struct bt_mesh_dfu_target_status *rsp); + +/** @brief Get the current procedure timeout value. + * + * @return The configured procedure timeout. + */ +int32_t bt_mesh_dfu_cli_timeout_get(void); + +/** @brief Set the procedure timeout value. + * + * @param timeout The new procedure timeout. + */ +void bt_mesh_dfu_cli_timeout_set(int32_t timeout); + +/** @cond INTERNAL_HIDDEN */ +extern const struct bt_mesh_blob_cli_cb _bt_mesh_dfu_cli_blob_handlers; +extern const struct bt_mesh_model_cb _bt_mesh_dfu_cli_cb; +extern const struct bt_mesh_model_op _bt_mesh_dfu_cli_op[]; +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_CLI_H__ */ + +/** @} */ diff --git a/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfu_metadata.h b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfu_metadata.h new file mode 100644 index 0000000000..8f6430c4b0 --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfu_metadata.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @defgroup bt_mesh_dfu_metadata Bluetooth Mesh Device Firmware Update (DFU) metadata + * @ingroup bt_mesh_dfu + * @{ + * @brief Common types and functions for the Bluetooth Mesh DFU metadata. + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_METADATA_H__ +#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_METADATA_H__ + +#include + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Firmware version. */ +struct bt_mesh_dfu_metadata_fw_ver { + /** Firmware major version. */ + uint8_t major; + /** Firmware minor version. */ + uint8_t minor; + /** Firmware revision. */ + uint16_t revision; + /** Firmware build number. */ + uint32_t build_num; +}; + +/** Firmware core type. */ +enum bt_mesh_dfu_metadata_fw_core_type { + /** Application core. */ + BT_MESH_DFU_FW_CORE_TYPE_APP = BIT(0), + /** Network core. */ + BT_MESH_DFU_FW_CORE_TYPE_NETWORK = BIT(1), + /** Application-specific BLOB. */ + BT_MESH_DFU_FW_CORE_TYPE_APP_SPECIFIC_BLOB = BIT(2), +}; + +/** Firmware metadata. */ +struct bt_mesh_dfu_metadata { + /** New firmware version. */ + struct bt_mesh_dfu_metadata_fw_ver fw_ver; + /** New firmware size. */ + uint32_t fw_size; + /** New firmware core type. */ + enum bt_mesh_dfu_metadata_fw_core_type fw_core_type; + /** Hash of incoming Composition Data. */ + uint32_t comp_hash; + /** New number of node elements. */ + uint16_t elems; + /** Application-specific data for new firmware. This field is optional. */ + uint8_t *user_data; + /** Length of the application-specific field. */ + uint32_t user_data_len; +}; + +/** @brief Decode a firmware metadata from a network buffer. + * + * @param buf Buffer containing a raw metadata to be decoded. + * @param metadata Pointer to a metadata structure to be filled. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_dfu_metadata_decode(struct net_buf_simple *buf, + struct bt_mesh_dfu_metadata *metadata); + +/** @brief Encode a firmware metadata into a network buffer. + * + * @param metadata Firmware metadata to be encoded. + * @param buf Buffer to store the encoded metadata. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_dfu_metadata_encode(const struct bt_mesh_dfu_metadata *metadata, + struct net_buf_simple *buf); + +/** @brief Compute hash of the Composition Data state. + * + * The format of the Composition Data is defined in MshPRTv1.1: 4.2.2.1. + * + * @param buf Pointer to buffer holding Composition Data. + * @param key 128-bit key to be used in the hash computation. + * @param hash Pointer to a memory location to which the hash will be stored. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_dfu_metadata_comp_hash_get(struct net_buf_simple *buf, uint8_t *key, uint32_t *hash); + +/** @brief Compute hash of the Composition Data Page 0 of this device. + * + * @param key 128-bit key to be used in the hash computation. + * @param hash Pointer to a memory location to which the hash will be stored. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_dfu_metadata_comp_hash_local_get(uint8_t *key, uint32_t *hash); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_METADATA_H__ */ + +/** @} */ diff --git a/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfu_srv.h b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfu_srv.h new file mode 100644 index 0000000000..2beaf2d977 --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/dfu/dfu_srv.h @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @defgroup bt_mesh_dfu_srv Firmware Update Server model + * @ingroup bt_mesh_dfu + * @{ + * @brief API for the Bluetooth Mesh Firmware Update Server model + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_SRV_H__ +#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_SRV_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct bt_mesh_dfu_srv; + +/** + * + * @brief Initialization parameters for @ref bt_mesh_dfu_srv. + * + * @param _handlers DFU handler function structure. + * @param _imgs List of @ref bt_mesh_dfu_img managed by this Server. + * @param _img_count Number of DFU images managed by this Server. + */ +#define BT_MESH_DFU_SRV_INIT(_handlers, _imgs, _img_count) \ + { \ + .blob = { .cb = &_bt_mesh_dfu_srv_blob_cb }, .cb = _handlers, \ + .imgs = _imgs, .img_count = _img_count, \ + } + +/** + * + * @brief Firmware Update Server model entry. + * + * @param _srv Pointer to a @ref bt_mesh_dfu_srv instance. + */ +#define BT_MESH_MODEL_DFU_SRV(_srv) \ + BT_MESH_MODEL_BLOB_SRV(&(_srv)->blob), \ + BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_DFU_SRV, _bt_mesh_dfu_srv_op, NULL, \ + _srv, &_bt_mesh_dfu_srv_cb) + +/** @brief Firmware Update Server event callbacks. */ +struct bt_mesh_dfu_srv_cb { + /** @brief Transfer check callback. + * + * The transfer check can be used to validate the incoming transfer + * before it starts. The contents of the metadata is implementation + * specific, and should contain all the information the application + * needs to determine whether this image should be accepted, and what + * the effect of the transfer would be. + * + * If applying the image will have an effect on the provisioning state + * of the mesh stack, this can be communicated through the @c effect + * return parameter. + * + * The metadata check can be performed both as part of starting a new + * transfer and as a separate procedure. + * + * This handler is optional. + * + * @param srv Firmware Update Server instance. + * @param img DFU image the metadata check is performed on. + * @param metadata Image metadata. + * @param effect Return parameter for the image effect on the + * provisioning state of the mesh stack. + * + * @return 0 on success, or (negative) error code otherwise. + */ + int (*check)(struct bt_mesh_dfu_srv *srv, + const struct bt_mesh_dfu_img *img, + struct net_buf_simple *metadata, + enum bt_mesh_dfu_effect *effect); + + /** @brief Transfer start callback. + * + * Called when the Firmware Update Server is ready to start a new DFU transfer. + * The application must provide an initialized BLOB stream to be used + * during the DFU transfer. + * + * The following error codes are treated specially, and should be used + * to communicate these issues: + * - @c -ENOMEM: The device cannot fit this image. + * - @c -EBUSY: The application is temporarily unable to accept the + * transfer. + * - @c -EALREADY: The device has already received and verified this + * image, and there's no need to transfer it again. The Firmware Update model + * will skip the transfer phase, and mark the image as verified. + * + * This handler is mandatory. + * + * @param srv Firmware Update Server instance. + * @param img DFU image being updated. + * @param metadata Image metadata. + * @param io BLOB stream return parameter. Must be set to a + * valid BLOB stream by the callback. + * + * @return 0 on success, or (negative) error code otherwise. Return + * codes @c -ENOMEM, @c -EBUSY @c -EALREADY will be passed to + * the updater, other error codes are reported as internal + * errors. + */ + int (*start)(struct bt_mesh_dfu_srv *srv, + const struct bt_mesh_dfu_img *img, + struct net_buf_simple *metadata, + const struct bt_mesh_blob_io **io); + + /** @brief Transfer end callback. + * + * This handler is optional. + * + * If the transfer is successful, the application should verify the + * firmware image, and call either @ref bt_mesh_dfu_srv_verified or + * @ref bt_mesh_dfu_srv_rejected depending on the outcome. + * + * If the transfer fails, the Firmware Update Server will be available for new + * transfers immediately after this function returns. + * + * @param srv Firmware Update Server instance. + * @param img DFU image that failed the update. + * @param success Whether the DFU transfer was successful. + */ + void (*end)(struct bt_mesh_dfu_srv *srv, + const struct bt_mesh_dfu_img *img, bool success); + + /** @brief Transfer recovery callback. + * + * If the device reboots in the middle of a transfer, the Firmware Update Server + * calls this function when the Bluetooth Mesh subsystem is started. + * + * This callback is optional, but transfers will not be recovered after + * a reboot without it. + * + * @param srv Firmware Update Server instance. + * @param img DFU image being updated. + * @param io BLOB stream return parameter. Must be set to a valid BLOB + * stream by the callback. + * + * @return 0 on success, or (negative) error code to abandon the + * transfer. + */ + int (*recover)(struct bt_mesh_dfu_srv *srv, + const struct bt_mesh_dfu_img *img, + const struct bt_mesh_blob_io **io); + + /** @brief Transfer apply callback. + * + * Called after a transfer has been validated, and the updater sends an + * apply message to the Target nodes. + * + * This handler is optional. + * + * @param srv Firmware Update Server instance. + * @param img DFU image that should be applied. + * + * @return 0 on success, or (negative) error code otherwise. + */ + int (*apply)(struct bt_mesh_dfu_srv *srv, + const struct bt_mesh_dfu_img *img); +}; + +/** @brief Firmware Update Server instance. + * + * Should be initialized with @ref BT_MESH_DFU_SRV_INIT. + */ +struct bt_mesh_dfu_srv { + /** Underlying BLOB Transfer Server. */ + struct bt_mesh_blob_srv blob; + /** Callback structure. */ + const struct bt_mesh_dfu_srv_cb *cb; + /** List of updatable images. */ + const struct bt_mesh_dfu_img *imgs; + /** Number of updatable images. */ + size_t img_count; + + /* Runtime state */ + const struct bt_mesh_model *mod; + struct { + /* Effect of transfer, @see bt_mesh_dfu_effect. */ + uint8_t effect; + /* Current phase, @see bt_mesh_dfu_phase. */ + uint8_t phase; + uint8_t ttl; + uint8_t idx; + uint16_t timeout_base; + uint16_t meta; + } update; +}; + +/** @brief Accept the received DFU transfer. + * + * Should be called at the end of a successful DFU transfer. + * + * If the DFU transfer completes successfully, the application should verify + * the image validity (including any image authentication or integrity checks), + * and call this function if the image is ready to be applied. + * + * @param srv Firmware Update Server instance. + */ +void bt_mesh_dfu_srv_verified(struct bt_mesh_dfu_srv *srv); + +/** @brief Reject the received DFU transfer. + * + * Should be called at the end of a successful DFU transfer. + * + * If the DFU transfer completes successfully, the application should verify + * the image validity (including any image authentication or integrity checks), + * and call this function if one of the checks fail. + * + * @param srv Firmware Update Server instance. + */ +void bt_mesh_dfu_srv_rejected(struct bt_mesh_dfu_srv *srv); + +/** @brief Cancel the ongoing DFU transfer. + * + * @param srv Firmware Update Server instance. + */ +void bt_mesh_dfu_srv_cancel(struct bt_mesh_dfu_srv *srv); + +/** @brief Confirm that the received DFU transfer was applied. + * + * Should be called as a result of the @ref bt_mesh_dfu_srv_cb.apply callback. + * + * @param srv Firmware Update Server instance. + */ +void bt_mesh_dfu_srv_applied(struct bt_mesh_dfu_srv *srv); + +/** @brief Check if the Firmware Update Server is busy processing a transfer. + * + * @param srv Firmware Update Server instance. + * + * @return true if a DFU procedure is in progress, false otherwise. + */ +bool bt_mesh_dfu_srv_is_busy(const struct bt_mesh_dfu_srv *srv); + +/** @brief Get the progress of the current DFU procedure, in percent. + * + * @param srv Firmware Update Server instance. + * + * @return The current transfer progress in percent. + */ +uint8_t bt_mesh_dfu_srv_progress(const struct bt_mesh_dfu_srv *srv); + +/** @cond INTERNAL_HIDDEN */ +extern const struct bt_mesh_model_op _bt_mesh_dfu_srv_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_dfu_srv_cb; +extern const struct bt_mesh_blob_srv_cb _bt_mesh_dfu_srv_blob_cb; +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_SRV_H__ */ + +/** @} */ diff --git a/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/mbt/blob.h b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/mbt/blob.h new file mode 100644 index 0000000000..18304f39f6 --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/mbt/blob.h @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_H__ +#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_H__ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup bt_mesh_blob Bluetooth Mesh BLOB model API + * @ingroup bt_mesh + * @{ + */ + +#ifndef CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX +#define CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX 0 +#endif + +/** BLOB transfer mode. */ +enum bt_mesh_blob_xfer_mode { + /** No valid transfer mode. */ + BT_MESH_BLOB_XFER_MODE_NONE, + /** Push mode (Push BLOB Transfer Mode). */ + BT_MESH_BLOB_XFER_MODE_PUSH, + /** Pull mode (Pull BLOB Transfer Mode). */ + BT_MESH_BLOB_XFER_MODE_PULL, + /** Both modes are valid. */ + BT_MESH_BLOB_XFER_MODE_ALL, +}; + +/** Transfer phase. */ +enum bt_mesh_blob_xfer_phase { + /** The BLOB Transfer Server is awaiting configuration. */ + BT_MESH_BLOB_XFER_PHASE_INACTIVE, + /** The BLOB Transfer Server is ready to receive a BLOB transfer. */ + BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START, + /** The BLOB Transfer Server is waiting for the next block of data. */ + BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK, + /** The BLOB Transfer Server is waiting for the next chunk of data. */ + BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK, + /** The BLOB was transferred successfully. */ + BT_MESH_BLOB_XFER_PHASE_COMPLETE, + /** The BLOB transfer is paused. */ + BT_MESH_BLOB_XFER_PHASE_SUSPENDED, +}; + +/** BLOB model status codes. */ +enum bt_mesh_blob_status { + /** The message was processed successfully. */ + BT_MESH_BLOB_SUCCESS, + /** The Block Number field value is not within the range of blocks being + * transferred. + */ + BT_MESH_BLOB_ERR_INVALID_BLOCK_NUM, + /** The block size is smaller than the size indicated by the Min Block + * Size Log state or is larger than the size indicated by the Max Block + * Size Log state. + */ + BT_MESH_BLOB_ERR_INVALID_BLOCK_SIZE, + /** The chunk size exceeds the size indicated by the Max Chunk Size + * state, or the number of chunks exceeds the number specified by the + * Max Total Chunks state. + */ + BT_MESH_BLOB_ERR_INVALID_CHUNK_SIZE, + /** The operation cannot be performed while the server is in the current + * phase. + */ + BT_MESH_BLOB_ERR_WRONG_PHASE, + /** A parameter value in the message cannot be accepted. */ + BT_MESH_BLOB_ERR_INVALID_PARAM, + /** The message contains a BLOB ID value that is not expected. */ + BT_MESH_BLOB_ERR_WRONG_BLOB_ID, + /** There is not enough space available in memory to receive the BLOB. + */ + BT_MESH_BLOB_ERR_BLOB_TOO_LARGE, + /** The transfer mode is not supported by the BLOB Transfer Server + * model. + */ + BT_MESH_BLOB_ERR_UNSUPPORTED_MODE, + /** An internal error occurred on the node. */ + BT_MESH_BLOB_ERR_INTERNAL, + /** The requested information cannot be provided while the server is in + * the current phase. + */ + BT_MESH_BLOB_ERR_INFO_UNAVAILABLE, +}; + +/** BLOB transfer data block. */ +struct bt_mesh_blob_block { + /** Block size in bytes */ + size_t size; + /** Offset in bytes from the start of the BLOB. */ + off_t offset; + /** Block number */ + uint16_t number; + /** Number of chunks in block. */ + uint16_t chunk_count; + /** Bitmap of missing chunks. */ + uint8_t missing[DIV_ROUND_UP(CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX, + 8)]; +}; + +/** BLOB data chunk. */ +struct bt_mesh_blob_chunk { + /** Offset of the chunk data from the start of the block. */ + off_t offset; + /** Chunk data size. */ + size_t size; + /** Chunk data. */ + uint8_t *data; +}; + +/** BLOB transfer. */ +struct bt_mesh_blob_xfer { + /** BLOB ID. */ + uint64_t id; + /** Total BLOB size in bytes. */ + size_t size; + /** BLOB transfer mode. */ + enum bt_mesh_blob_xfer_mode mode; + /* Logarithmic representation of the block size. */ + uint8_t block_size_log; + /** Base chunk size. May be smaller for the last chunk. */ + uint16_t chunk_size; +}; + +/** BLOB stream interaction mode. */ +enum bt_mesh_blob_io_mode { + /** Read data from the stream. */ + BT_MESH_BLOB_READ, + /** Write data to the stream. */ + BT_MESH_BLOB_WRITE, +}; + +/** BLOB stream. */ +struct bt_mesh_blob_io { + /** @brief Open callback. + * + * Called when the reader is opened for reading. + * + * @param io BLOB stream. + * @param xfer BLOB transfer. + * @param mode Direction of the stream (read/write). + * + * @return 0 on success, or (negative) error code otherwise. + */ + int (*open)(const struct bt_mesh_blob_io *io, + const struct bt_mesh_blob_xfer *xfer, + enum bt_mesh_blob_io_mode mode); + + /** @brief Close callback. + * + * Called when the reader is closed. + * + * @param io BLOB stream. + * @param xfer BLOB transfer. + */ + void (*close)(const struct bt_mesh_blob_io *io, + const struct bt_mesh_blob_xfer *xfer); + + /** @brief Block start callback. + * + * Called when a new block is opened for sending. Each block is only + * sent once, and are always sent in increasing order. The data chunks + * inside a single block may be requested out of order and multiple + * times. + * + * @param io BLOB stream. + * @param xfer BLOB transfer. + * @param block Block that was started. + */ + int (*block_start)(const struct bt_mesh_blob_io *io, + const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_block *block); + + /** @brief Block end callback. + * + * Called when the current block has been transmitted in full. + * No data from this block will be requested again, and the application + * data associated with this block may be discarded. + * + * @param io BLOB stream. + * @param xfer BLOB transfer. + * @param block Block that finished sending. + */ + void (*block_end)(const struct bt_mesh_blob_io *io, + const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_block *block); + + /** @brief Chunk data write callback. + * + * Used by the BLOB Transfer Server on incoming data. + * + * Each block is divided into chunks of data. This callback is called + * when a new chunk of data is received. Chunks may be received in + * any order within their block. + * + * If the callback returns successfully, this chunk will be marked as + * received, and will not be received again unless the block is + * restarted due to a transfer suspension. If the callback returns a + * non-zero value, the chunk remains unreceived, and the BLOB Transfer + * Client will attempt to resend it later. + * + * Note that the Client will only perform a limited number of attempts + * at delivering a chunk before dropping a Target node from the transfer. + * The number of retries performed by the Client is implementation + * specific. + * + * @param io BLOB stream. + * @param xfer BLOB transfer. + * @param block Block the chunk is part of. + * @param chunk Received chunk. + * + * @return 0 on success, or (negative) error code otherwise. + */ + int (*wr)(const struct bt_mesh_blob_io *io, + const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_block *block, + const struct bt_mesh_blob_chunk *chunk); + + /** @brief Chunk data read callback. + * + * Used by the BLOB Transfer Client to fetch outgoing data. + * + * The Client calls the chunk data request callback to populate a chunk + * message going out to the Target nodes. The data request callback + * may be called out of order and multiple times for each offset, and + * cannot be used as an indication of progress. + * + * Returning a non-zero status code on the chunk data request callback + * results in termination of the transfer. + * + * @param io BLOB stream. + * @param xfer BLOB transfer. + * @param block Block the chunk is part of. + * @param chunk Chunk to get the data of. The buffer pointer to by the + * @c data member should be filled by the callback. + * + * @return 0 on success, or (negative) error code otherwise. + */ + int (*rd)(const struct bt_mesh_blob_io *io, + const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_block *block, + const struct bt_mesh_blob_chunk *chunk); +}; + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_H__ */ diff --git a/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/mbt/blob_cli.h b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/mbt/blob_cli.h new file mode 100644 index 0000000000..574ec82715 --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/mbt/blob_cli.h @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_CLI_H_ +#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_CLI_H_ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup bt_mesh_blob_cli Bluetooth Mesh BLOB Transfer Client model API + * @ingroup bt_mesh + * @{ + */ + +struct bt_mesh_blob_cli; + +/** + * + * @brief BLOB Transfer Client model Composition Data entry. + * + * @param _cli Pointer to a @ref bt_mesh_blob_cli instance. + */ +#define BT_MESH_MODEL_BLOB_CLI(_cli) \ + BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_BLOB_CLI, _bt_mesh_blob_cli_op, \ + NULL, _cli, &_bt_mesh_blob_cli_cb) + +/** Target node's Pull mode (Pull BLOB Transfer Mode) context used + * while sending chunks to the Target node. + */ +struct bt_mesh_blob_target_pull { + /** Timestamp when the Block Report Timeout Timer expires for this Target node. */ + int64_t block_report_timestamp; + + /** Missing chunks reported by this Target node. */ + uint8_t missing[DIV_ROUND_UP(CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX, 8)]; +}; + +/** BLOB Transfer Client Target node. */ +struct bt_mesh_blob_target { + /** Linked list node */ + sys_snode_t n; + + /** Target node address. */ + uint16_t addr; + + /** Target node's Pull mode context. + * Needs to be initialized when sending a BLOB in Pull mode. + */ + struct bt_mesh_blob_target_pull *pull; + + /** BLOB transfer status, see @ref bt_mesh_blob_status. */ + uint8_t status; + + uint8_t procedure_complete:1, /* Procedure has been completed. */ + acked:1, /* Message has been acknowledged. Not used when sending. */ + timedout:1, /* Target node didn't respond after specified timeout. */ + skip:1; /* Skip Target node from broadcast. */ +}; + +/** BLOB transfer information. + * + * If @c phase is @ref BT_MESH_BLOB_XFER_PHASE_INACTIVE, + * the fields below @c phase are not initialized. + * If @c phase is @ref BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START, + * the fields below @c id are not initialized. + */ +struct bt_mesh_blob_xfer_info { + /** BLOB transfer status. */ + enum bt_mesh_blob_status status; + + /** BLOB transfer mode. */ + enum bt_mesh_blob_xfer_mode mode; + + /** BLOB transfer phase. */ + enum bt_mesh_blob_xfer_phase phase; + + /** BLOB ID. */ + uint64_t id; + + /** BLOB size in octets. */ + uint32_t size; + + /** Logarithmic representation of the block size. */ + uint8_t block_size_log; + + /** MTU size in octets. */ + uint16_t mtu_size; + + /** Bit field indicating blocks that were not received. */ + const uint8_t *missing_blocks; +}; + +/** BLOB Transfer Client transfer inputs. */ +struct bt_mesh_blob_cli_inputs { + /** Linked list of Target nodes. Each node should point to @ref + * bt_mesh_blob_target::n. + */ + sys_slist_t targets; + + /** AppKey index to send with. */ + uint16_t app_idx; + + /** Group address destination for the BLOB transfer, or @ref + * BT_MESH_ADDR_UNASSIGNED to send every message to each Target + * node individually. + */ + uint16_t group; + + /** Time to live value of BLOB transfer messages. */ + uint8_t ttl; + + /** Additional response time for the Target nodes, in 10-second increments. + * + * The extra time can be used to give the Target nodes more time to respond + * to messages from the Client. The actual timeout will be calculated + * according to the following formula: + * + * @verbatim + * timeout = 20 seconds + (10 seconds * timeout_base) + (100 ms * TTL) + * @endverbatim + * + * If a Target node fails to respond to a message from the Client within the + * configured transfer timeout, the Target node is dropped. + */ + uint16_t timeout_base; +}; + +/** Transfer capabilities of a Target node. */ +struct bt_mesh_blob_cli_caps { + /** Max BLOB size. */ + size_t max_size; + + /** Logarithmic representation of the minimum block size. */ + uint8_t min_block_size_log; + + /** Logarithmic representation of the maximum block size. */ + uint8_t max_block_size_log; + + /** Max number of chunks per block. */ + uint16_t max_chunks; + + /** Max chunk size. */ + uint16_t max_chunk_size; + + /** Max MTU size. */ + uint16_t mtu_size; + + /** Supported transfer modes. */ + enum bt_mesh_blob_xfer_mode modes; +}; + +/** BLOB Transfer Client state. */ +enum bt_mesh_blob_cli_state { + /** No transfer is active. */ + BT_MESH_BLOB_CLI_STATE_NONE, + /** Retrieving transfer capabilities. */ + BT_MESH_BLOB_CLI_STATE_CAPS_GET, + /** Sending transfer start. */ + BT_MESH_BLOB_CLI_STATE_START, + /** Sending block start. */ + BT_MESH_BLOB_CLI_STATE_BLOCK_START, + /** Sending block chunks. */ + BT_MESH_BLOB_CLI_STATE_BLOCK_SEND, + /** Checking block status. */ + BT_MESH_BLOB_CLI_STATE_BLOCK_CHECK, + /** Checking transfer status. */ + BT_MESH_BLOB_CLI_STATE_XFER_CHECK, + /** Cancelling transfer. */ + BT_MESH_BLOB_CLI_STATE_CANCEL, + /** Transfer is suspended. */ + BT_MESH_BLOB_CLI_STATE_SUSPENDED, + /** Checking transfer progress. */ + BT_MESH_BLOB_CLI_STATE_XFER_PROGRESS_GET, +}; + +/** Event handler callbacks for the BLOB Transfer Client model. + * + * All handlers are optional. + */ +struct bt_mesh_blob_cli_cb { + /** @brief Capabilities retrieval completion callback. + * + * Called when the capabilities retrieval procedure completes, indicating that + * a common set of acceptable transfer parameters have been established + * for the given list of Target nodes. All compatible Target nodes have + * status code @ref BT_MESH_BLOB_SUCCESS. + * + * @param cli BLOB Transfer Client instance. + * @param caps Safe transfer capabilities if the transfer capabilities + * of at least one Target node has satisfied the Client, or NULL otherwise. + */ + void (*caps)(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_cli_caps *caps); + + /** @brief Target node loss callback. + * + * Called whenever a Target node has been lost due to some error in the + * transfer. Losing a Target node is not considered a fatal error for + * the Client until all Target nodes have been lost. + * + * @param cli BLOB Transfer Client instance. + * @param target Target node that was lost. + * @param reason Reason for the Target node loss. + */ + void (*lost_target)(struct bt_mesh_blob_cli *cli, + struct bt_mesh_blob_target *target, + enum bt_mesh_blob_status reason); + + /** @brief Transfer is suspended. + * + * Called when the transfer is suspended due to response timeout from all Target nodes. + * + * @param cli BLOB Transfer Client instance. + */ + void (*suspended)(struct bt_mesh_blob_cli *cli); + + /** @brief Transfer end callback. + * + * Called when the transfer ends. + * + * @param cli BLOB Transfer Client instance. + * @param xfer Completed transfer. + * @param success Status of the transfer. + * Is @c true if at least one Target + * node received the whole transfer. + */ + void (*end)(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_xfer *xfer, bool success); + + /** @brief Transfer progress callback + * + * The content of @c info is invalidated upon exit from the callback. + * Therefore it needs to be copied if it is planned to be used later. + * + * @param cli BLOB Transfer Client instance. + * @param target Target node that responded to the request. + * @param info BLOB transfer information. + */ + void (*xfer_progress)(struct bt_mesh_blob_cli *cli, + struct bt_mesh_blob_target *target, + const struct bt_mesh_blob_xfer_info *info); + + /** @brief End of Get Transfer Progress procedure. + * + * Called when all Target nodes have responded or the procedure timed-out. + * + * @param cli BLOB Transfer Client instance. + */ + void (*xfer_progress_complete)(struct bt_mesh_blob_cli *cli); +}; + +/** @cond INTERNAL_HIDDEN */ +struct blob_cli_broadcast_ctx { + /** Called for every Target node in unicast mode, or once in case of multicast mode. */ + void (*send)(struct bt_mesh_blob_cli *cli, uint16_t dst); + /** Called after every @ref blob_cli_broadcast_ctx::send callback. */ + void (*send_complete)(struct bt_mesh_blob_cli *cli, uint16_t dst); + /** If @ref blob_cli_broadcast_ctx::acked is true, called after all Target nodes + * have confirmed reception by @ref blob_cli_broadcast_rsp. Otherwise, called + * after transmission has been completed. + */ + void (*next)(struct bt_mesh_blob_cli *cli); + /** If true, every transmission needs to be confirmed by @ref blob_cli_broadcast_rsp before + * @ref blob_cli_broadcast_ctx::next is called. + */ + bool acked; + /** If true, the message is always sent in a unicast way. */ + bool force_unicast; + /** If true, non-responsive Target nodes won't be dropped after transfer has timed out. */ + bool optional; + /** Set to true by the BLOB Transfer Client between blob_cli_broadcast + * and broadcast_complete calls. + */ + bool is_inited; + /* Defines a time in ms by which the broadcast API postpones sending the message to a next + * target or completing the broadcast. + */ + uint32_t post_send_delay_ms; +}; +/** INTERNAL_HIDDEN @endcond */ + +/** BLOB Transfer Client model instance. */ +struct bt_mesh_blob_cli { + /** Event handler callbacks */ + const struct bt_mesh_blob_cli_cb *cb; + + /* Runtime state */ + const struct bt_mesh_model *mod; + + struct { + struct bt_mesh_blob_target *target; + struct blob_cli_broadcast_ctx ctx; + struct k_work_delayable retry; + /* Represents Client Timeout timer in a timestamp. Used in Pull mode only. */ + int64_t cli_timestamp; + struct k_work_delayable complete; + uint16_t pending; + uint8_t retries; + uint8_t sending : 1, + cancelled : 1; + } tx; + + const struct bt_mesh_blob_io *io; + const struct bt_mesh_blob_cli_inputs *inputs; + const struct bt_mesh_blob_xfer *xfer; + uint32_t chunk_interval_ms; + uint16_t block_count; + uint16_t chunk_idx; + uint16_t mtu_size; + enum bt_mesh_blob_cli_state state; + struct bt_mesh_blob_block block; + struct bt_mesh_blob_cli_caps caps; +}; + +/** @brief Retrieve transfer capabilities for a list of Target nodes. + * + * Queries the availability and capabilities of all Target nodes, producing a + * cumulative set of transfer capabilities for the Target nodes, and returning + * it through the @ref bt_mesh_blob_cli_cb::caps callback. + * + * Retrieving the capabilities may take several seconds, depending on the + * number of Target nodes and mesh network performance. The end of the procedure + * is indicated through the @ref bt_mesh_blob_cli_cb::caps callback. + * + * This procedure is not required, but strongly recommended as a + * preparation for a transfer to maximize performance and the chances of + * success. + * + * @param cli BLOB Transfer Client instance. + * @param inputs Statically allocated BLOB Transfer Client transfer inputs. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_blob_cli_caps_get(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_cli_inputs *inputs); + +/** @brief Perform a BLOB transfer. + * + * Starts sending the transfer to the Target nodes. Only Target nodes with a + * @c status of @ref BT_MESH_BLOB_SUCCESS will be considered. + * + * The transfer will keep going either until all Target nodes have been dropped, or + * the full BLOB has been sent. + * + * The BLOB transfer may take several minutes, depending on the number of + * Target nodes, size of the BLOB and mesh network performance. The end of the + * transfer is indicated through the @ref bt_mesh_blob_cli_cb::end callback. + * + * A Client only supports one transfer at the time. + * + * @param cli BLOB Transfer Client instance. + * @param inputs Statically allocated BLOB Transfer Client transfer inputs. + * @param xfer Statically allocated transfer parameters. + * @param io BLOB stream to read the transfer from. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_blob_cli_send(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_cli_inputs *inputs, + const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_io *io); + +/** @brief Suspend the active transfer. + * + * @param cli BLOB Transfer Client instance. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_blob_cli_suspend(struct bt_mesh_blob_cli *cli); + +/** @brief Resume the suspended transfer. + * + * @param cli BLOB Transfer Client instance. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_blob_cli_resume(struct bt_mesh_blob_cli *cli); + +/** @brief Cancel an ongoing transfer. + * + * @param cli BLOB Transfer Client instance. + */ +void bt_mesh_blob_cli_cancel(struct bt_mesh_blob_cli *cli); + +/** @brief Get the progress of BLOB transfer. + * + * This function can only be used if the BLOB Transfer Client is currently + * not performing a BLOB transfer. + * To get progress of the active BLOB transfer, use the + * @ref bt_mesh_blob_cli_xfer_progress_active_get function. + * + * @param cli BLOB Transfer Client instance. + * @param inputs Statically allocated BLOB Transfer Client transfer inputs. + * + * @return 0 on success, or (negative) error code otherwise. + */ +int bt_mesh_blob_cli_xfer_progress_get(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_cli_inputs *inputs); + +/** @brief Get the current progress of the active transfer in percent. + * + * @param cli BLOB Transfer Client instance. + * + * @return The current transfer progress, or 0 if no transfer is active. + */ +uint8_t bt_mesh_blob_cli_xfer_progress_active_get(struct bt_mesh_blob_cli *cli); + +/** @brief Get the current state of the BLOB Transfer Client. + * + * @param cli BLOB Transfer Client instance. + * + * @return true if the BLOB Transfer Client is currently participating in a transfer or + * retrieving the capabilities and false otherwise. + */ +bool bt_mesh_blob_cli_is_busy(struct bt_mesh_blob_cli *cli); + +/** @brief Set chunk sending interval in ms + * + * This function is optional, and can be used to define how fast chunks are sent in the BLOB Client + * Model. + * Without an added delay, for example a Bluetooth Mesh DFU can cause network blockage by + * constantly sending the next chunks, especially if the chunks are sent to group addresses or + * multiple unicast addresses. + * + * @note: Big intervals may cause timeouts. Increasing the @c timeout_base accordingly can + * circumvent this. + * + * @param cli BLOB Transfer Client instance. + * @param interval_ms the delay before each chunk is sent out in ms. + */ +void bt_mesh_blob_cli_set_chunk_interval_ms(struct bt_mesh_blob_cli *cli, uint32_t interval_ms); + +/** @cond INTERNAL_HIDDEN */ +extern const struct bt_mesh_model_op _bt_mesh_blob_cli_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_blob_cli_cb; +/** @endcond */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_CLI_H_ */ diff --git a/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/mbt/blob_srv.h b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/mbt/blob_srv.h new file mode 100644 index 0000000000..8a0c6b67a1 --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/include/mesh_v1.1/mbt/blob_srv.h @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_SRV_H_ +#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_SRV_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup bt_mesh_blob_srv Bluetooth Mesh BLOB Transfer Server model API + * @ingroup bt_mesh + * @{ + */ + +struct bt_mesh_blob_srv; + +/** + * + * @brief Max number of blocks in a single transfer. + */ +#if defined(CONFIG_BT_MESH_BLOB_SRV) +#define BT_MESH_BLOB_BLOCKS_MAX \ + (DIV_ROUND_UP(CONFIG_BT_MESH_BLOB_SIZE_MAX, \ + CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MIN)) +#else +#define BT_MESH_BLOB_BLOCKS_MAX 1 +#endif + +/** + * + * @brief BLOB Transfer Server model composition data entry. + * + * @param _srv Pointer to a @ref bt_mesh_blob_srv instance. + */ +#define BT_MESH_MODEL_BLOB_SRV(_srv) \ + BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_BLOB_SRV, _bt_mesh_blob_srv_op, \ + NULL, _srv, &_bt_mesh_blob_srv_cb) + +/** @brief BLOB Transfer Server model event handlers. + * + * All callbacks are optional. + */ +struct bt_mesh_blob_srv_cb { + /** @brief Transfer start callback. + * + * Called when the transfer has started with the prepared BLOB ID. + * + * @param srv BLOB Transfer Server instance. + * @param ctx Message context for the incoming start message. The + * entire transfer will be sent from the same source + * address. + * @param xfer Transfer parameters. + * + * @return 0 on success, or (negative) error code to reject the + * transfer. + */ + int (*start)(struct bt_mesh_blob_srv *srv, struct bt_mesh_msg_ctx *ctx, + struct bt_mesh_blob_xfer *xfer); + + /** @brief Transfer end callback. + * + * Called when the transfer ends, either because it was cancelled, or + * because it finished successfully. A new transfer may be prepared. + * + * @note The transfer may end before it's started if the start + * parameters are invalid. + * + * @param srv BLOB Transfer Server instance. + * @param id BLOB ID of the cancelled transfer. + * @param success Whether the transfer was successful. + */ + void (*end)(struct bt_mesh_blob_srv *srv, uint64_t id, bool success); + + /** @brief Transfer suspended callback. + * + * Called if the Server timed out while waiting for a transfer packet. + * A suspended transfer may resume later from the start of the current + * block. Any received chunks in the current block should be discarded, + * they will be received again if the transfer resumes. + * + * The transfer will call @c resumed again when resuming. + * + * @note The BLOB Transfer Server does not run a timer in the suspended state, + * and it's up to the application to determine whether the + * transfer should be permanently cancelled. Without interaction, + * the transfer will be suspended indefinitely, and the BLOB Transfer + * Server will not accept any new transfers. + * + * @param srv BLOB Transfer Server instance. + */ + void (*suspended)(struct bt_mesh_blob_srv *srv); + + /** @brief Transfer resume callback. + * + * Called if the transfer is resumed after being suspended. + * + * @param srv BLOB Transfer Server instance. + */ + void (*resume)(struct bt_mesh_blob_srv *srv); + + /** @brief Transfer recovery callback. + * + * Called when the Bluetooth Mesh subsystem is started if the device is rebooted + * in the middle of a transfer. + * + * Transfers will not be resumed after a reboot if this callback is not + * defined. + * + * @param srv BLOB Transfer Server instance. + * @param xfer Transfer to resume. + * @param io BLOB stream return parameter. Must be set to a valid + * BLOB stream by the callback. + * + * @return 0 on success, or (negative) error code to abandon the + * transfer. + */ + int (*recover)(struct bt_mesh_blob_srv *srv, + struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_io **io); +}; + +/** @brief BLOB Transfer Server model instance. */ +struct bt_mesh_blob_srv { + /** Event handler callbacks. */ + const struct bt_mesh_blob_srv_cb *cb; + + /* Runtime state: */ + const struct bt_mesh_blob_io *io; + struct k_work_delayable rx_timeout; + struct bt_mesh_blob_block block; + const struct bt_mesh_model *mod; + enum bt_mesh_blob_xfer_phase phase; + + struct bt_mesh_blob_srv_state { + struct bt_mesh_blob_xfer xfer; + uint16_t cli; + uint16_t app_idx; + uint16_t timeout_base; + uint16_t mtu_size; + uint8_t ttl; + + /* Bitfield of pending blocks. */ + ATOMIC_DEFINE(blocks, BT_MESH_BLOB_BLOCKS_MAX); + } state; + + /* Pull mode (Pull BLOB Transfer Mode) behavior. */ + struct { + uint16_t chunk_idx; + struct k_work_delayable report; + } pull; +}; + +/** @brief Prepare BLOB Transfer Server for an incoming transfer. + * + * Before a BLOB Transfer Server can receive a transfer, the transfer must be prepared + * through some application level mechanism. The BLOB Transfer Server will only accept + * incoming transfers with a matching BLOB ID. + * + * @param srv BLOB Transfer Server instance. + * @param id BLOB ID to accept. + * @param io BLOB stream to write the incoming BLOB to. + * @param ttl Time to live value to use in responses to the BLOB Transfer Client. + * @param timeout_base Extra time for the Client to respond in addition to the + * base 10 seconds, in 10-second increments. + * + * @return 0 on success, or (negative) error code on failure. + */ +int bt_mesh_blob_srv_recv(struct bt_mesh_blob_srv *srv, uint64_t id, + const struct bt_mesh_blob_io *io, uint8_t ttl, + uint16_t timeout_base); + +/** @brief Cancel the current BLOB transfer. + * + * Tells the BLOB Transfer Client to drop this device from the list of Targets for the + * current transfer. Note that the client may continue sending the transfer to + * other Targets. + * + * @param srv BLOB Transfer Server instance. + * + * @return 0 on success, or (negative) error code on failure. + */ +int bt_mesh_blob_srv_cancel(struct bt_mesh_blob_srv *srv); + +/** @brief Get the current state of the BLOB Transfer Server. + * + * @param srv BLOB Transfer Server instance. + * + * @return true if the BLOB Transfer Server is currently participating in a transfer, + * false otherwise. + */ +bool bt_mesh_blob_srv_is_busy(const struct bt_mesh_blob_srv *srv); + +/** @brief Get the current progress of the active transfer in percent. + * + * @param srv BLOB Transfer Server instance. + * + * @return The current transfer progress, or 0 if no transfer is active. + */ +uint8_t bt_mesh_blob_srv_progress(const struct bt_mesh_blob_srv *srv); + +/** @cond INTERNAL_HIDDEN */ +extern const struct bt_mesh_model_op _bt_mesh_blob_srv_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_blob_srv_cb; +/** @endcond */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_SRV_H_ */ diff --git a/components/bt/esp_ble_mesh/v1.1/mbt/blob.h b/components/bt/esp_ble_mesh/v1.1/mbt/blob.h new file mode 100644 index 0000000000..162725480e --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/mbt/blob.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define BT_MESH_BLOB_OP_XFER_GET BT_MESH_MODEL_OP_2(0x83, 0x00) +#define BT_MESH_BLOB_OP_XFER_START BT_MESH_MODEL_OP_2(0x83, 0x01) +#define BT_MESH_BLOB_OP_XFER_CANCEL BT_MESH_MODEL_OP_2(0x83, 0x02) +#define BT_MESH_BLOB_OP_XFER_STATUS BT_MESH_MODEL_OP_2(0x83, 0x03) +#define BT_MESH_BLOB_OP_BLOCK_GET BT_MESH_MODEL_OP_2(0x83, 0x05) +#define BT_MESH_BLOB_OP_BLOCK_START BT_MESH_MODEL_OP_2(0x83, 0x04) +#define BT_MESH_BLOB_OP_CHUNK BT_MESH_MODEL_OP_1(0x66) +#define BT_MESH_BLOB_OP_BLOCK_STATUS BT_MESH_MODEL_OP_1(0x67) +#define BT_MESH_BLOB_OP_BLOCK_REPORT BT_MESH_MODEL_OP_1(0x68) +#define BT_MESH_BLOB_OP_INFO_GET BT_MESH_MODEL_OP_2(0x83, 0x06) +#define BT_MESH_BLOB_OP_INFO_STATUS BT_MESH_MODEL_OP_2(0x83, 0x07) + +#define BLOB_BLOCK_NOT_SET 0xffff + +#define BLOB_CHUNK_SDU_OVERHEAD \ + (BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_CHUNK) + 2 + BT_MESH_MIC_SHORT) + +#define BLOB_CHUNK_SIZE_MAX(sdu_max) ((sdu_max) - BLOB_CHUNK_SDU_OVERHEAD) +#define BLOB_CHUNK_SDU_LEN(chunk_size) (BLOB_CHUNK_SDU_OVERHEAD + (chunk_size)) + +#if CONFIG_BT_MESH_ALIGN_CHUNK_SIZE_TO_MAX_SEGMENT || \ + CONFIG_BT_MESH_RX_BLOB_CHUNK_SIZE > BLOB_CHUNK_SIZE_MAX(BT_MESH_RX_SDU_MAX) +#define BLOB_RX_CHUNK_SIZE BLOB_CHUNK_SIZE_MAX(BT_MESH_RX_SDU_MAX) +#else +#define BLOB_RX_CHUNK_SIZE CONFIG_BT_MESH_RX_BLOB_CHUNK_SIZE +#endif + +#if CONFIG_BT_MESH_ALIGN_CHUNK_SIZE_TO_MAX_SEGMENT || \ + CONFIG_BT_MESH_TX_BLOB_CHUNK_SIZE > BLOB_CHUNK_SIZE_MAX(BT_MESH_TX_SDU_MAX) +#define BLOB_TX_CHUNK_SIZE BLOB_CHUNK_SIZE_MAX(BT_MESH_TX_SDU_MAX) +#else +#define BLOB_TX_CHUNK_SIZE CONFIG_BT_MESH_TX_BLOB_CHUNK_SIZE +#endif + +/* Utility macros for calculating log2 of a number at compile time. + * Used to determine the log2 representation of the block size, which + * is configured as a raw number, but encoded as log2. + * + * The macros expand to a series of ternary expressions, effectively + * searching through power of twos until a match is found. + * According to MshMBTv1.0, the block size cannot be larger than 2^20, + * so we'll stop the search at 20. + */ +#define _BLOB_LOG_2_CEIL(l, x) ((x) <= (1U << l)) ? l : +#define _BLOB_LOG_2_FLOOR(l, x) ((x) < (1U << (l + 1))) ? l : + +#define BLOB_BLOCK_SIZE_LOG_CEIL(x) (LISTIFY(20, _BLOB_LOG_2_CEIL, (), x) 20) +#define BLOB_BLOCK_SIZE_LOG_FLOOR(x) (LISTIFY(20, _BLOB_LOG_2_FLOOR, (), x) 20) + +/* Log2 representation of the minimum block size */ +#define BLOB_BLOCK_SIZE_LOG_MIN BLOB_BLOCK_SIZE_LOG_CEIL(CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MIN) +/* Log2 representation of the maximum block size */ +#define BLOB_BLOCK_SIZE_LOG_MAX BLOB_BLOCK_SIZE_LOG_FLOOR(CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX) + +#if defined(CONFIG_BT_MESH_BLOB_SRV) +#define BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN ( \ + MAX(sizeof(((struct bt_mesh_blob_block *)0)->missing), \ + CONFIG_BT_MESH_BLOB_SRV_PULL_REQ_COUNT * 3)) +#define BLOB_BLOCK_STATUS_MSG_MAXLEN (5 + \ + MAX(sizeof(((struct bt_mesh_blob_block *)0)->missing), \ + CONFIG_BT_MESH_BLOB_SRV_PULL_REQ_COUNT * 3)) +#else +#define BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN sizeof(((struct bt_mesh_blob_srv *)0)->block.missing) +#define BLOB_BLOCK_STATUS_MSG_MAXLEN (5 + sizeof(((struct bt_mesh_blob_srv *)0)->block.missing)) +#endif + +#define BLOB_XFER_STATUS_MSG_MAXLEN (17 + sizeof(((struct bt_mesh_blob_srv *)0)->state.blocks)) + +enum bt_mesh_blob_chunks_missing { + BT_MESH_BLOB_CHUNKS_MISSING_ALL, + BT_MESH_BLOB_CHUNKS_MISSING_NONE, + BT_MESH_BLOB_CHUNKS_MISSING_SOME, + BT_MESH_BLOB_CHUNKS_MISSING_ENCODED, +}; + +static inline size_t blob_block_size(size_t xfer_size, uint8_t block_size_log, + uint32_t idx) +{ + if (((idx + 1U) << block_size_log) <= xfer_size) { + return (1U << block_size_log); + } + + return xfer_size & BIT_MASK(block_size_log); +} + +static inline void blob_chunk_missing_set(uint8_t *missing_chunks, + int idx, bool missing) +{ + WRITE_BIT(missing_chunks[idx / 8], idx % 8, missing); +} + +static inline bool +blob_chunk_missing_get(const uint8_t *missing_chunks, int idx) +{ + return !!(missing_chunks[idx / 8] & BIT(idx % 8)); +} + +static inline void blob_chunk_missing_set_all(struct bt_mesh_blob_block *block) +{ + size_t bytes = block->chunk_count / 8; + + memset(block->missing, 0xff, bytes); + if (block->chunk_count % 8) { + block->missing[bytes] = BIT_MASK(block->chunk_count % 8); + } +} + +static inline void blob_chunk_missing_set_none(struct bt_mesh_blob_block *block) +{ + memset(block->missing, 0, sizeof(block->missing)); +} + +/** @brief Perform a message broadcast to all BLOB Transfer Client Target nodes. + * + * Will send to a group or each Target node individually, repeating until + * all Target nodes have responded or the retry time has run out. + * + * @param cli BLOB Transfer Client instance + * @param ctx Broadcast context + */ +void blob_cli_broadcast(struct bt_mesh_blob_cli *cli, + const struct blob_cli_broadcast_ctx *ctx); + +/** @brief Register that a Target node responded to a broadcast. + * + * @param cli BLOB Transfer Client instance + * @param target Target node that responded. + */ +void blob_cli_broadcast_rsp(struct bt_mesh_blob_cli *cli, + struct bt_mesh_blob_target *target); + +/** @brief Notify the BLOB Transfer Client that the requested transmission is complete. + * + * Should be called once for each call to the @ref blob_cli_broadcast_ctx.send + * callback. + * + * @param cli BLOB Transfer Client instance. + */ +void blob_cli_broadcast_tx_complete(struct bt_mesh_blob_cli *cli); + +/** @brief Aborts any ongoing BLOB Transfer Client operations. + * + * @param cli BLOB Transfer Client instance. + */ +void blob_cli_broadcast_abort(struct bt_mesh_blob_cli *cli); diff --git a/components/bt/esp_ble_mesh/v1.1/mbt/blob_cli.c b/components/bt/esp_ble_mesh/v1.1/mbt/blob_cli.c new file mode 100644 index 0000000000..40d3d37a6c --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/mbt/blob_cli.c @@ -0,0 +1,1658 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "mesh.h" +#include "blob.h" +#include "net.h" +#include "transport.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_blob_cli); + +#define TARGETS_FOR_EACH(cli, target) \ + SYS_SLIST_FOR_EACH_CONTAINER((sys_slist_t *)&(cli)->inputs->targets, \ + target, n) + +/* The Maximum BLOB Poll Interval - T_MBPI */ +#define BLOB_POLL_TIME_MAX_SECS 30 + +#define CLIENT_TIMEOUT_MSEC(cli) (10 * MSEC_PER_SEC * (cli->inputs->timeout_base + 2) + \ + 100 * cli->inputs->ttl) +#define BLOCK_REPORT_TIME_MSEC ((BLOB_POLL_TIME_MAX_SECS * 2 + 7) * 1000) + +/* BLOB Client is running Send Data State Machine from section 6.2.4.2. */ +#define SENDING_CHUNKS_IN_PULL_MODE(cli) ((cli)->state == BT_MESH_BLOB_CLI_STATE_BLOCK_SEND && \ + (cli)->xfer->mode == BT_MESH_BLOB_XFER_MODE_PULL) +#define UNICAST_MODE(cli) ((cli)->inputs->group == BT_MESH_ADDR_UNASSIGNED || \ + (cli)->tx.ctx.force_unicast) + +BUILD_ASSERT((BLOB_XFER_STATUS_MSG_MAXLEN + BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_XFER_STATUS) + + BT_MESH_MIC_SHORT) <= BT_MESH_RX_SDU_MAX, + "The BLOB Transfer Status message does not fit into the maximum incoming SDU size."); + +BUILD_ASSERT((BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN + + BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_BLOCK_REPORT) + BT_MESH_MIC_SHORT) + <= BT_MESH_RX_SDU_MAX, + "The BLOB Partial Block Report message does not fit into the maximum incoming SDU " + "size."); + +BUILD_ASSERT((BLOB_BLOCK_STATUS_MSG_MAXLEN + BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_BLOCK_STATUS) + + BT_MESH_MIC_SHORT) <= BT_MESH_RX_SDU_MAX, + "The BLOB Block Status message does not fit into the maximum incoming SDU size."); + + +struct block_status { + enum bt_mesh_blob_status status; + enum bt_mesh_blob_chunks_missing missing; + struct bt_mesh_blob_block block; +}; + +static struct bt_mesh_blob_target *next_target(struct bt_mesh_blob_cli *cli, + struct bt_mesh_blob_target **current); +static void transfer_cancel(struct bt_mesh_blob_cli *cli); + +static void start_retry_timer(struct bt_mesh_blob_cli *cli) +{ + k_timeout_t next_timeout; + + if (SENDING_CHUNKS_IN_PULL_MODE(cli)) { + int64_t next_timeout_ms = cli->tx.cli_timestamp; + struct bt_mesh_blob_target *target = NULL; + + TARGETS_FOR_EACH(cli, target) { + if (!target->procedure_complete && + target->status == BT_MESH_BLOB_SUCCESS && + target->pull->block_report_timestamp < next_timeout_ms) { + next_timeout_ms = target->pull->block_report_timestamp; + } + } + + /* cli_timestamp and block_report_timestamp represent absolute time, while + * k_work_* functions use relative time. + */ + next_timeout_ms -= k_uptime_get(); + next_timeout = next_timeout_ms <= 0 ? K_NO_WAIT : K_MSEC(next_timeout_ms); + } else { + next_timeout = K_MSEC(CLIENT_TIMEOUT_MSEC(cli) / + CONFIG_BT_MESH_BLOB_CLI_BLOCK_RETRIES); + } + + (void)k_work_reschedule(&cli->tx.retry, next_timeout); +} + +static void cli_state_reset(struct bt_mesh_blob_cli *cli) +{ + k_work_cancel_delayable(&cli->tx.retry); + cli->xfer = NULL; + cli->state = BT_MESH_BLOB_CLI_STATE_NONE; + cli->tx.ctx.is_inited = 0; + cli->tx.cli_timestamp = 0ll; + cli->tx.sending = 0; +} + +static struct bt_mesh_blob_target *target_get(struct bt_mesh_blob_cli *cli, + uint16_t addr) +{ + struct bt_mesh_blob_target *target; + + TARGETS_FOR_EACH(cli, target) { + if (target->addr == addr) { + return target; + } + } + + LOG_ERR("Unknown target 0x%04x", addr); + return NULL; +} + +static void target_drop(struct bt_mesh_blob_cli *cli, + struct bt_mesh_blob_target *target, + enum bt_mesh_blob_status reason) +{ + LOG_WRN("Dropping 0x%04x: %u", target->addr, reason); + + target->status = reason; + if (cli->cb && cli->cb->lost_target) { + cli->cb->lost_target(cli, target, reason); + } +} + +static uint32_t targets_reset(struct bt_mesh_blob_cli *cli) +{ + struct bt_mesh_blob_target *target; + uint32_t count = 0; + + TARGETS_FOR_EACH(cli, target) { + if (target->status == BT_MESH_BLOB_SUCCESS) { + target->acked = 0U; + count++; + } + } + + return count; +} + +static bool targets_active(struct bt_mesh_blob_cli *cli) +{ + struct bt_mesh_blob_target *target; + + TARGETS_FOR_EACH(cli, target) { + if (target->status == BT_MESH_BLOB_SUCCESS) { + return true; + } + } + + return false; +} + +static bool targets_timedout(struct bt_mesh_blob_cli *cli) +{ + struct bt_mesh_blob_target *target; + + TARGETS_FOR_EACH(cli, target) { + if (!!target->timedout) { + return true; + } + } + + return false; +} + +static int io_open(struct bt_mesh_blob_cli *cli) +{ + if (!cli->io->open) { + return 0; + } + + return cli->io->open(cli->io, cli->xfer, BT_MESH_BLOB_READ); +} + +static void io_close(struct bt_mesh_blob_cli *cli) +{ + if (!cli->io->close) { + return; + } + + cli->io->close(cli->io, cli->xfer); +} + +static uint16_t next_missing_chunk(struct bt_mesh_blob_cli *cli, + const uint8_t *missing_chunks, + uint16_t idx) +{ + do { + if (blob_chunk_missing_get(missing_chunks, idx)) { + break; + } + } while (++idx < cli->block.chunk_count); + + return idx; +} + +/* Used in Pull mode to collect all missing chunks from each target in cli->block.missing. */ +static void update_missing_chunks(struct bt_mesh_blob_cli *cli) +{ + struct bt_mesh_blob_target *target; + + memset(cli->block.missing, 0, sizeof(cli->block.missing)); + + TARGETS_FOR_EACH(cli, target) { + if (target->procedure_complete || target->timedout) { + continue; + } + + for (size_t idx = 0; idx < cli->block.chunk_count; idx++) { + bool missing = blob_chunk_missing_get(cli->block.missing, idx) || + blob_chunk_missing_get(target->pull->missing, idx); + blob_chunk_missing_set(cli->block.missing, idx, missing); + } + } +} + +static inline size_t chunk_size(const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_block *block, + uint16_t chunk_idx) +{ + if ((chunk_idx == block->chunk_count - 1) && + (block->size % xfer->chunk_size)) { + return block->size % xfer->chunk_size; + } + + return xfer->chunk_size; +} + +static int chunk_idx_decode(struct net_buf_simple *buf) +{ + uint16_t data; + uint8_t byte; + + if (buf->len == 0) { + return -EINVAL; + } + + byte = net_buf_simple_pull_u8(buf); + + /* utf-8 decoding */ + if ((byte & 0xf0) == 0xe0) { /* 0x800 - 0xffff */ + if (buf->len < 2) { + return -EINVAL; + } + + data = (byte & 0x0f) << 12; + data |= (net_buf_simple_pull_u8(buf) & 0x3f) << 6; + data |= (net_buf_simple_pull_u8(buf) & 0x3f); + } else if ((byte & 0xe0) == 0xc0) { /* 0x80 - 0x7ff */ + if (buf->len < 1) { + return -EINVAL; + } + + data = (byte & 0x1f) << 6; + data |= (net_buf_simple_pull_u8(buf) & 0x3f); + } else { /* 0x00 - 0x7f */ + data = byte & 0x7f; + } + + return data; +} + +static void block_set(struct bt_mesh_blob_cli *cli, uint16_t block_idx) +{ + cli->block.number = block_idx; + cli->block.offset = block_idx * (1UL << cli->xfer->block_size_log); + cli->block.size = blob_block_size(cli->xfer->size, cli->xfer->block_size_log, + block_idx); + cli->block.chunk_count = + DIV_ROUND_UP(cli->block.size, cli->xfer->chunk_size); + + if (cli->xfer->mode == BT_MESH_BLOB_XFER_MODE_PUSH) { + blob_chunk_missing_set_all(&cli->block); + } else { + struct bt_mesh_blob_target *target; + + /* In pull mode, the server will tell us which blocks are missing. */ + memset(cli->block.missing, 0, sizeof(cli->block.missing)); + + TARGETS_FOR_EACH(cli, target) { + memset(target->pull->missing, 0, sizeof(target->pull->missing)); + } + } + + LOG_DBG("%u size: %u chunks: %u", block_idx, cli->block.size, + cli->block.chunk_count); +} + +static void suspend(struct bt_mesh_blob_cli *cli) +{ + cli->state = BT_MESH_BLOB_CLI_STATE_SUSPENDED; + + if (cli->cb && cli->cb->suspended) { + cli->cb->suspended(cli); + } +} + +static void end(struct bt_mesh_blob_cli *cli, bool success) +{ + const struct bt_mesh_blob_xfer *xfer = cli->xfer; + + LOG_DBG("%u", success); + + io_close(cli); + cli_state_reset(cli); + if (cli->cb && cli->cb->end) { + cli->cb->end(cli, xfer, success); + } +} + +static enum bt_mesh_blob_status caps_adjust(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_cli_caps *in) +{ + if (!(in->modes & cli->caps.modes)) { + return BT_MESH_BLOB_ERR_UNSUPPORTED_MODE; + } + + if ((in->min_block_size_log > cli->caps.max_block_size_log) || + (in->max_block_size_log < cli->caps.min_block_size_log)) { + return BT_MESH_BLOB_ERR_INVALID_BLOCK_SIZE; + } + + cli->caps.min_block_size_log = + MAX(cli->caps.min_block_size_log, in->min_block_size_log); + cli->caps.max_block_size_log = + MIN(cli->caps.max_block_size_log, in->max_block_size_log); + cli->caps.max_chunks = MIN(cli->caps.max_chunks, in->max_chunks); + cli->caps.mtu_size = MIN(cli->caps.mtu_size, in->mtu_size); + cli->caps.max_chunk_size = MIN(cli->caps.max_chunk_size, in->max_chunk_size); + cli->caps.modes &= in->modes; + cli->caps.max_size = MIN(cli->caps.max_size, in->max_size); + + return BT_MESH_BLOB_SUCCESS; +} + +/******************************************************************************* + * TX State machine + * + * All messages in the transfer are going out to all the targets, either through + * group messaging or directly to each. The TX state machine implements this + * pattern for the transfer state machine to use. It will send the messages to + * all devices (through the group or directly), repeating until it receives a + * response from each device, or the attempts run out. Messages may also be + * marked as unacked if they require no response. + ******************************************************************************/ + +static struct bt_mesh_blob_target *next_target(struct bt_mesh_blob_cli *cli, + struct bt_mesh_blob_target **current) +{ + if (*current) { + *current = SYS_SLIST_PEEK_NEXT_CONTAINER(*current, n); + } else { + *current = SYS_SLIST_PEEK_HEAD_CONTAINER( + (sys_slist_t *)&cli->inputs->targets, *current, n); + } + + while (*current) { + if ((*current)->acked || (*current)->procedure_complete || + (*current)->status != BT_MESH_BLOB_SUCCESS || (*current)->timedout || + (*current)->skip) { + goto next; + } + + if (SENDING_CHUNKS_IN_PULL_MODE(cli) && + (k_uptime_get() < (*current)->pull->block_report_timestamp || + !blob_chunk_missing_get((*current)->pull->missing, cli->chunk_idx))) { + /* Skip targets that didn't time out or timed out, but confirmed + * the currently transmitted chunk (cli->chunk_idx). + */ + goto next; + } + + break; + +next: + *current = SYS_SLIST_PEEK_NEXT_CONTAINER(*current, n); + } + + return *current; +} + +static void send(struct bt_mesh_blob_cli *cli) +{ + cli->tx.sending = 1U; + if (UNICAST_MODE(cli)) { + cli->tx.ctx.send(cli, cli->tx.target->addr); + } else { + cli->tx.ctx.send(cli, cli->inputs->group); + } +} + +static void broadcast_complete(struct bt_mesh_blob_cli *cli) +{ + LOG_DBG("%s", cli->tx.cancelled ? "cancelling" : "continuing"); + + cli->tx.ctx.is_inited = 0; + k_work_cancel_delayable(&cli->tx.retry); + + if (cli->tx.cancelled) { + transfer_cancel(cli); + } else { + __ASSERT(cli->tx.ctx.next, "No next callback"); + cli->tx.ctx.next(cli); + } +} + +static void tx_complete(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_mesh_blob_cli *cli = CONTAINER_OF(dwork, struct bt_mesh_blob_cli, tx.complete); + + if (!cli->tx.ctx.is_inited || !cli->tx.sending) { + return; + } + + cli->tx.sending = 0U; + + if (cli->tx.cancelled) { + broadcast_complete(cli); + return; + } + + if (cli->tx.ctx.send_complete) { + cli->tx.ctx.send_complete(cli, cli->tx.target->addr); + } + + if (UNICAST_MODE(cli) && next_target(cli, &cli->tx.target)) { + send(cli); + return; + } + + if (cli->tx.ctx.acked && cli->tx.pending) { + start_retry_timer(cli); + return; + } + + broadcast_complete(cli); +} + +static void drop_remaining_targets(struct bt_mesh_blob_cli *cli) +{ + struct bt_mesh_blob_target *target; + + LOG_DBG(""); + + cli->tx.pending = 0; + + TARGETS_FOR_EACH(cli, target) { + if (!target->acked && !target->timedout && !target->procedure_complete && + !target->skip) { + target->timedout = 1U; + target_drop(cli, target, BT_MESH_BLOB_ERR_INTERNAL); + } + } + + /* Update missing chunks to exclude chunks from dropped targets. */ + if (SENDING_CHUNKS_IN_PULL_MODE(cli)) { + update_missing_chunks(cli); + } +} + +static void retry_timeout(struct k_work *work) +{ + struct bt_mesh_blob_cli *cli = + CONTAINER_OF(work, struct bt_mesh_blob_cli, tx.retry.work); + + /* When sending chunks in Pull mode, timeout is handled differently. Client will drop all + * non-responsive servers by cli_timestamp. By calling broadcast_complete(), client will + * either retransmit the missing chunks (if any), or proceed to the next block, or suspend + * the transfer if all targets timed out. All this is handled in block_check_end(). + * Retry logic for all other procedures in Pull mode is handled as in Push mode. + */ + if (SENDING_CHUNKS_IN_PULL_MODE(cli)) { + if (k_uptime_get() >= cli->tx.cli_timestamp) { + LOG_DBG("Transfer timed out."); + + if (!cli->tx.ctx.optional) { + drop_remaining_targets(cli); + } + } + + broadcast_complete(cli); + return; + } + + LOG_DBG("%u", cli->tx.retries); + + cli->tx.retries--; + cli->tx.target = NULL; + + __ASSERT(!cli->tx.sending, "still sending"); + __ASSERT(cli->tx.ctx.is_inited, "ctx is not initialized"); + + if (!cli->tx.retries) { + LOG_DBG("Transfer timed out."); + + if (!cli->tx.ctx.optional) { + drop_remaining_targets(cli); + } + + broadcast_complete(cli); + return; + } + + if (!cli->tx.ctx.acked || !next_target(cli, &cli->tx.target) || cli->tx.cancelled) { + broadcast_complete(cli); + return; + } + + send(cli); +} + +void blob_cli_broadcast(struct bt_mesh_blob_cli *cli, + const struct blob_cli_broadcast_ctx *ctx) +{ + if (cli->tx.ctx.is_inited || cli->tx.sending) { + LOG_ERR("BLOB cli busy"); + return; + } + + cli->tx.cancelled = 0U; + cli->tx.retries = CONFIG_BT_MESH_BLOB_CLI_BLOCK_RETRIES; + cli->tx.ctx = *ctx; + cli->tx.ctx.is_inited = 1U; + + cli->tx.pending = targets_reset(cli); + + LOG_DBG("%u targets", cli->tx.pending); + + cli->tx.target = NULL; + if (!next_target(cli, &cli->tx.target)) { + LOG_DBG("No active targets"); + broadcast_complete(cli); + return; + } + + send(cli); +} + +void blob_cli_broadcast_tx_complete(struct bt_mesh_blob_cli *cli) +{ + k_work_schedule(&cli->tx.complete, K_MSEC(cli->tx.ctx.post_send_delay_ms)); +} + +void blob_cli_broadcast_rsp(struct bt_mesh_blob_cli *cli, + struct bt_mesh_blob_target *target) +{ + if (target->acked) { + return; + } + + LOG_DBG("0x%04x, pending: %d", target->addr, cli->tx.pending); + + target->acked = 1U; + + if (!--cli->tx.pending && !cli->tx.sending) { + broadcast_complete(cli); + } +} + +void blob_cli_broadcast_abort(struct bt_mesh_blob_cli *cli) +{ + if (!cli->tx.ctx.is_inited) { + return; + } + + if ((cli)->state >= BT_MESH_BLOB_CLI_STATE_START) { + io_close(cli); + } + + cli_state_reset(cli); +} + +static void send_start(uint16_t duration, int err, void *cb_data); +static void send_end(int err, void *user_data); + +static int tx(struct bt_mesh_blob_cli *cli, uint16_t addr, + struct net_buf_simple *buf) +{ + static const struct bt_mesh_send_cb end_cb = { + .start = send_start, + .end = send_end, + }; + struct bt_mesh_msg_ctx ctx = { + .app_idx = cli->inputs->app_idx, + .addr = addr, + .send_ttl = cli->inputs->ttl, + }; + int err; + + err = bt_mesh_model_send(cli->mod, &ctx, buf, &end_cb, cli); + if (err) { + LOG_ERR("Send err: %d", err); + send_end(err, cli); + return err; + } + + return 0; +} + +static void send_start(uint16_t duration, int err, void *cb_data) +{ + if (err) { + LOG_ERR("TX Start failed: %d", err); + send_end(err, cb_data); + } +} + +static void send_end(int err, void *user_data) +{ + struct bt_mesh_blob_cli *cli = user_data; + + if (!cli->tx.ctx.is_inited) { + return; + } + + blob_cli_broadcast_tx_complete(cli); +} + +/******************************************************************************* + * TX + ******************************************************************************/ + +static void info_get_tx(struct bt_mesh_blob_cli *cli, uint16_t dst) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_INFO_GET, 0); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_INFO_GET); + + tx(cli, dst, &buf); +} + +static void xfer_start_tx(struct bt_mesh_blob_cli *cli, uint16_t dst) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_XFER_START, 16); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_XFER_START); + net_buf_simple_add_u8(&buf, cli->xfer->mode << 6); + net_buf_simple_add_le64(&buf, cli->xfer->id); + net_buf_simple_add_le32(&buf, cli->xfer->size); + net_buf_simple_add_u8(&buf, cli->xfer->block_size_log); + net_buf_simple_add_le16(&buf, BT_MESH_TX_SDU_MAX); + + tx(cli, dst, &buf); +} + +static void xfer_get_tx(struct bt_mesh_blob_cli *cli, uint16_t dst) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_XFER_GET, 0); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_XFER_GET); + + tx(cli, dst, &buf); +} + +static void xfer_cancel_tx(struct bt_mesh_blob_cli *cli, uint16_t dst) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_XFER_CANCEL, 8); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_XFER_CANCEL); + net_buf_simple_add_le64(&buf, cli->xfer->id); + + tx(cli, dst, &buf); +} + +static void block_start_tx(struct bt_mesh_blob_cli *cli, uint16_t dst) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_BLOCK_START, 4); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_BLOCK_START); + net_buf_simple_add_le16(&buf, cli->block.number); + net_buf_simple_add_le16(&buf, cli->xfer->chunk_size); + + tx(cli, dst, &buf); +} + +static void chunk_tx(struct bt_mesh_blob_cli *cli, uint16_t dst) +{ + NET_BUF_SIMPLE_DEFINE(buf, BT_MESH_TX_SDU_MAX); + struct bt_mesh_blob_chunk chunk; + int err; + + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_CHUNK); + net_buf_simple_add_le16(&buf, cli->chunk_idx); + + chunk.size = chunk_size(cli->xfer, &cli->block, cli->chunk_idx); + chunk.offset = cli->xfer->chunk_size * cli->chunk_idx; + chunk.data = net_buf_simple_add(&buf, chunk.size); + + err = cli->io->rd(cli->io, cli->xfer, &cli->block, &chunk); + if (err || cli->state == BT_MESH_BLOB_CLI_STATE_NONE) { + bt_mesh_blob_cli_cancel(cli); + return; + } + + tx(cli, dst, &buf); +} + +static void block_get_tx(struct bt_mesh_blob_cli *cli, uint16_t dst) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_BLOCK_GET, 0); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_BLOCK_GET); + + tx(cli, dst, &buf); +} + +/************************************************************************************************** + * State machine + * + * The BLOB Client state machine walks through the steps in the BLOB transfer in the following + * fashion: + * + * .---------------------------------------. + * V | + * xfer_start -> block_set -> block_start -> chunk_send -> chunk_send_end | + * A | | + * | V | + * | [more missing chunks?]-----[Yes]-----+ + * | | | + * | [No] | + * | | | + * | V | + * | [mode?] | + * | .---[Push]---' '---[Pull]---. | + * | | | | + * | V V | + * | block_check block_report_wait | + * | | | | + * | '-----------. .-------------' | + * | | | | + * | V V | + * | block_check_end | + * | | | + * | V | + * | [block completed?]------[No]------' + * | | + * | [Yes] + * | | + * | V + * '-------------------[No]------------[last block sent?] + * | + * [Yes] + * | + * V + * confirm_transfer + * | + * V + * transfer_complete + * + * In each state, the Client transmits a message to all target nodes. In each state, except when + * sending chunks (chunk_send), the Client expects a response from all target nodes, before + * proceeding to the next state. + * + * When a target node responds, the Client calls @ref blob_cli_broadcast_rsp for the corresponding + * target. Once all target nodes has responded, the Client proceeds to the next state. + * + * When sending chunks in Push mode, the Client will proceed to the next state (block_check) after + * transmitting all missing chunks. In the block_check state, the Client will request a block status + * from all target nodes. If any targets have missing chunks, the Client will resend them. + * + * When sending chunks in Pull mode, the Client addresses each target node individually using + * @ref bt_mesh_blob_target_pull structure. The Client uses @ref bt_mesh_blob_cli::block::missing + * to keep all missing chunks for the current block. Missing chunks for an individual target + * is kept in @ref bt_mesh_blob_target_pull::missing. The Client uses @ref + * bt_mesh_blob_target_pull::block_report_timeout to decide if it can send a chunk to this target. + * + * After sending all reported missing chunks to each target, the Client updates + * @ref bt_mesh_blob_target_pull::block_report_timestamp value for every target individually in + * chunk_tx_complete. The Client then proceeds to block_report_wait state and uses the earliest of + * all block_report_timestamp and cli_timestamp to schedule the retry timer. When the retry + * timer expires, the Client proceeds to the block_check_end state. + * + * In Pull mode, target nodes send a Partial Block Report message to the Client to inform about + * missing chunks. The Client doesn't control when these messages are sent by target nodes, and + * therefore it can't use @ref blob_cli_broadcast_rsp when it receives them. When the Client + * receives the Partial Block Report message, it updates missing chunks, resets + * block_report_timestamp, and explicitly calls @ref broadcast_complete to proceed to + * block_check_end state. + * + **************************************************************************************************/ +static void caps_collected(struct bt_mesh_blob_cli *cli); +static void block_start(struct bt_mesh_blob_cli *cli); +static void chunk_send(struct bt_mesh_blob_cli *cli); +static void block_check(struct bt_mesh_blob_cli *cli); +static void block_check_end(struct bt_mesh_blob_cli *cli); +static void block_report_wait(struct bt_mesh_blob_cli *cli); +static void chunk_send_end(struct bt_mesh_blob_cli *cli); +static void confirm_transfer(struct bt_mesh_blob_cli *cli); +static void transfer_complete(struct bt_mesh_blob_cli *cli); + +static void caps_get(struct bt_mesh_blob_cli *cli) +{ + const struct blob_cli_broadcast_ctx ctx = { + .send = info_get_tx, + .next = caps_collected, + .acked = true, + }; + + cli->state = BT_MESH_BLOB_CLI_STATE_CAPS_GET; + blob_cli_broadcast(cli, &ctx); +} + +static void caps_collected(struct bt_mesh_blob_cli *cli) +{ + struct bt_mesh_blob_target *target; + bool success = false; + + cli->state = BT_MESH_BLOB_CLI_STATE_NONE; + + cli_state_reset(cli); + + TARGETS_FOR_EACH(cli, target) { + if (target->status == BT_MESH_BLOB_SUCCESS) { + success = true; + break; + } + } + + while (success && + (1UL << cli->caps.max_block_size_log) > + (cli->caps.max_chunk_size * cli->caps.max_chunks)) { + cli->caps.max_block_size_log--; + } + + if (cli->cb && cli->cb->caps) { + cli->cb->caps(cli, success ? &cli->caps : NULL); + } +} + +static int xfer_start(struct bt_mesh_blob_cli *cli) +{ + const struct blob_cli_broadcast_ctx ctx = { + .send = xfer_start_tx, + .next = block_start, + .acked = true, + }; + int err; + + err = io_open(cli); + if (err) { + return -EIO; + } + + cli->state = BT_MESH_BLOB_CLI_STATE_START; + + blob_cli_broadcast(cli, &ctx); + return 0; +} + +static void block_start(struct bt_mesh_blob_cli *cli) +{ + const struct blob_cli_broadcast_ctx ctx = { + .send = block_start_tx, + .next = chunk_send, + .acked = true, + }; + struct bt_mesh_blob_target *target; + + + if (!targets_active(cli)) { + if (targets_timedout(cli)) { + suspend(cli); + return; + } + + end(cli, false); + return; + } + + LOG_DBG("%u (%u chunks, %u/%u)", cli->block.number, + cli->block.chunk_count, cli->block.number + 1, cli->block_count); + + cli->chunk_idx = 0; + cli->state = BT_MESH_BLOB_CLI_STATE_BLOCK_START; + /* Client Timeout Timer in Send Data State Machine is initialized initially after + * transmitting the first bunch of chunks (see block_report_wait()). Next time it will be + * updated after every Partial Block Report message. + */ + cli->tx.cli_timestamp = 0ll; + + TARGETS_FOR_EACH(cli, target) { + target->procedure_complete = 0U; + + if (cli->xfer->mode == BT_MESH_BLOB_XFER_MODE_PULL) { + target->pull->block_report_timestamp = 0ll; + } + } + + if (cli->io->block_start) { + cli->io->block_start(cli->io, cli->xfer, &cli->block); + if (cli->state == BT_MESH_BLOB_CLI_STATE_NONE) { + return; + } + } + + blob_cli_broadcast(cli, &ctx); +} + +static void chunk_tx_complete(struct bt_mesh_blob_cli *cli, uint16_t dst) +{ + if (cli->xfer->mode != BT_MESH_BLOB_XFER_MODE_PULL) { + return; + } + + /* Update Block Report Timer individually for each target after sending out the last chunk + * in current iteration. + */ + uint16_t chunk_idx = next_missing_chunk(cli, cli->tx.target->pull->missing, + cli->chunk_idx + 1); + if (chunk_idx < cli->block.chunk_count) { + /* Will send more chunks to this target in this iteration. */ + return; + } + + /* This was the last chunk sent for this target. Now start the Block Report Timeout Timer. + */ + struct bt_mesh_blob_target *target; + int64_t timestamp = k_uptime_get() + BLOCK_REPORT_TIME_MSEC; + + if (!UNICAST_MODE(cli)) { + /* If using group addressing, reset timestamp for all targets after all chunks are + * sent to the group address + */ + TARGETS_FOR_EACH(cli, target) { + target->pull->block_report_timestamp = timestamp; + } + return; + } + + cli->tx.target->pull->block_report_timestamp = timestamp; +} + +static void chunk_send(struct bt_mesh_blob_cli *cli) +{ + struct blob_cli_broadcast_ctx ctx = { + .send = chunk_tx, + .next = chunk_send_end, + .acked = false, + .post_send_delay_ms = cli->chunk_interval_ms, + }; + + if (cli->xfer->mode == BT_MESH_BLOB_XFER_MODE_PULL) { + ctx.send_complete = chunk_tx_complete; + } + + if (!targets_active(cli)) { + if (targets_timedout(cli)) { + suspend(cli); + return; + } + + end(cli, false); + return; + } + + LOG_DBG("%u / %u size: %u", cli->chunk_idx + 1, cli->block.chunk_count, + chunk_size(cli->xfer, &cli->block, cli->chunk_idx)); + + cli->state = BT_MESH_BLOB_CLI_STATE_BLOCK_SEND; + blob_cli_broadcast(cli, &ctx); +} + +static void chunk_send_end(struct bt_mesh_blob_cli *cli) +{ + /* In pull mode, the partial block reports are used to confirm which + * chunks have been received, while in push mode, we just assume that a + * sent chunk has been received. + */ + if (cli->xfer->mode == BT_MESH_BLOB_XFER_MODE_PUSH) { + blob_chunk_missing_set(cli->block.missing, cli->chunk_idx, false); + } + + cli->chunk_idx = next_missing_chunk(cli, cli->block.missing, cli->chunk_idx + 1); + if (cli->chunk_idx < cli->block.chunk_count) { + chunk_send(cli); + return; + } + + if (cli->xfer->mode == BT_MESH_BLOB_XFER_MODE_PUSH) { + block_check(cli); + } else { + block_report_wait(cli); + } +} + +/* The block checking pair(block_check - block_check_end) + * is relevant only for Push mode. + */ +static void block_check(struct bt_mesh_blob_cli *cli) +{ + const struct blob_cli_broadcast_ctx ctx = { + .send = block_get_tx, + .next = block_check_end, + .acked = true, + }; + + cli->state = BT_MESH_BLOB_CLI_STATE_BLOCK_CHECK; + + LOG_DBG(""); + + blob_cli_broadcast(cli, &ctx); +} + +static void block_report_wait(struct bt_mesh_blob_cli *cli) +{ + const struct blob_cli_broadcast_ctx ctx = { + .next = block_check_end, + .acked = false, + }; + + /* Check if all servers already confirmed all chunks during the transmission. */ + if (next_missing_chunk(cli, cli->block.missing, 0) >= cli->block.chunk_count) { + block_check_end(cli); + return; + } + + LOG_DBG("Waiting for partial block report..."); + cli->tx.ctx = ctx; + + /* Start Client Timeout Timer in Send Data sub-procedure for the first time. */ + if (!cli->tx.cli_timestamp) { + cli->tx.cli_timestamp = k_uptime_get() + CLIENT_TIMEOUT_MSEC(cli); + } + + start_retry_timer(cli); +} + +static void block_check_end(struct bt_mesh_blob_cli *cli) +{ + LOG_DBG(""); + + if (!targets_active(cli)) { + if (targets_timedout(cli)) { + suspend(cli); + return; + } + + end(cli, false); + return; + } + + cli->chunk_idx = next_missing_chunk(cli, cli->block.missing, 0); + if (cli->chunk_idx < cli->block.chunk_count) { + chunk_send(cli); + return; + } + + LOG_DBG("No more missing chunks for block %u", cli->block.number); + + if (cli->io->block_end) { + cli->io->block_end(cli->io, cli->xfer, &cli->block); + if (cli->state == BT_MESH_BLOB_CLI_STATE_NONE) { + return; + } + } + + if (cli->block.number == cli->block_count - 1) { + struct bt_mesh_blob_target *target; + + TARGETS_FOR_EACH(cli, target) { + target->procedure_complete = 0U; + } + + confirm_transfer(cli); + return; + } + + block_set(cli, cli->block.number + 1); + block_start(cli); +} + +static void confirm_transfer(struct bt_mesh_blob_cli *cli) +{ + const struct blob_cli_broadcast_ctx ctx = { + .send = xfer_get_tx, + .next = transfer_complete, + .acked = true, + }; + + LOG_DBG(""); + + cli->state = BT_MESH_BLOB_CLI_STATE_XFER_CHECK; + + blob_cli_broadcast(cli, &ctx); +} + +static void progress_checked(struct bt_mesh_blob_cli *cli) +{ + LOG_DBG(""); + + cli->state = BT_MESH_BLOB_CLI_STATE_NONE; + + if (cli->cb && cli->cb->end) { + cli->cb->xfer_progress_complete(cli); + } +} + +static void check_transfer(struct bt_mesh_blob_cli *cli) +{ + const struct blob_cli_broadcast_ctx ctx = { + .send = xfer_get_tx, + .next = progress_checked, + .acked = true, + }; + + LOG_DBG(""); + + cli->state = BT_MESH_BLOB_CLI_STATE_XFER_PROGRESS_GET; + + blob_cli_broadcast(cli, &ctx); +} + +static void transfer_cancel(struct bt_mesh_blob_cli *cli) +{ + const struct blob_cli_broadcast_ctx ctx = { + .send = xfer_cancel_tx, + .next = transfer_complete, + .acked = true, + }; + + LOG_DBG(""); + + cli->state = BT_MESH_BLOB_CLI_STATE_CANCEL; + + blob_cli_broadcast(cli, &ctx); +} + +static void transfer_complete(struct bt_mesh_blob_cli *cli) +{ + bool success = targets_active(cli) && + cli->state == BT_MESH_BLOB_CLI_STATE_XFER_CHECK; + + end(cli, success); +} + +/******************************************************************************* + * RX + ******************************************************************************/ + +static void rx_block_status(struct bt_mesh_blob_cli *cli, + struct bt_mesh_blob_target *target, + struct block_status *block) +{ + if (cli->state != BT_MESH_BLOB_CLI_STATE_BLOCK_START && + cli->state != BT_MESH_BLOB_CLI_STATE_BLOCK_SEND && + cli->state != BT_MESH_BLOB_CLI_STATE_BLOCK_CHECK) { + LOG_WRN("Invalid state %u", cli->state); + return; + } + + LOG_DBG("0x%04x: block: %u status: %u", target->addr, block->block.number, block->status); + + if (block->status != BT_MESH_BLOB_SUCCESS) { + target_drop(cli, target, block->status); + blob_cli_broadcast_rsp(cli, target); + return; + } + + if (block->block.number != cli->block.number) { + LOG_DBG("Invalid block num (expected %u)", cli->block.number); + return; + } + + if (block->missing == BT_MESH_BLOB_CHUNKS_MISSING_NONE) { + target->procedure_complete = 1U; + + if (cli->xfer->mode == BT_MESH_BLOB_XFER_MODE_PULL) { + memset(target->pull->missing, 0, sizeof(target->pull->missing)); + update_missing_chunks(cli); + } + + LOG_DBG("Target 0x%04x received all chunks", target->addr); + } else if (block->missing == BT_MESH_BLOB_CHUNKS_MISSING_ALL) { + blob_chunk_missing_set_all(&cli->block); + } else if (cli->xfer->mode == BT_MESH_BLOB_XFER_MODE_PULL) { + memcpy(target->pull->missing, block->block.missing, sizeof(block->block.missing)); + + LOG_DBG("Missing: %s", bt_hex(target->pull->missing, cli->block.chunk_count)); + + update_missing_chunks(cli); + + /* Target has responded. Reset the timestamp so that client can start transmitting + * missing chunks to it. + */ + target->pull->block_report_timestamp = 0ll; + } else { + for (int i = 0; i < ARRAY_SIZE(block->block.missing); ++i) { + cli->block.missing[i] |= block->block.missing[i]; + } + } + + if (SENDING_CHUNKS_IN_PULL_MODE(cli)) { + if (!cli->tx.sending) { + /* If not sending, then the retry timer is running. Call + * broadcast_complete() to proceed to block_check_end() and start + * transmitting missing chunks. + */ + broadcast_complete(cli); + } + + /* When sending chunks in Pull mode, we don't confirm transaction when receiving + * Partial Block Report message. + */ + return; + } + + blob_cli_broadcast_rsp(cli, target); +} + +static int handle_xfer_status(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_cli *cli = mod->rt->user_data; + enum bt_mesh_blob_xfer_phase expected_phase; + struct bt_mesh_blob_target *target; + struct bt_mesh_blob_xfer_info info = { 0 }; + uint8_t status_and_mode; + + status_and_mode = net_buf_simple_pull_u8(buf); + info.status = status_and_mode & BIT_MASK(4); + info.mode = status_and_mode >> 6; + info.phase = net_buf_simple_pull_u8(buf); + + if (buf->len) { + info.id = net_buf_simple_pull_le64(buf); + } + + if (buf->len >= 7) { + info.size = net_buf_simple_pull_le32(buf); + info.block_size_log = net_buf_simple_pull_u8(buf); + info.mtu_size = net_buf_simple_pull_le16(buf); + info.missing_blocks = net_buf_simple_pull(buf, buf->len); + } + + LOG_DBG("status: %u %s phase: %u %s", info.status, + info.mode == BT_MESH_BLOB_XFER_MODE_PUSH ? "push" : "pull", + info.phase, bt_hex(&info.id, 8)); + + + if (cli->state != BT_MESH_BLOB_CLI_STATE_START && + cli->state != BT_MESH_BLOB_CLI_STATE_XFER_CHECK && + cli->state != BT_MESH_BLOB_CLI_STATE_CANCEL && + cli->state != BT_MESH_BLOB_CLI_STATE_XFER_PROGRESS_GET) { + LOG_WRN("Wrong state: %d", cli->state); + return -EBUSY; + } + + target = target_get(cli, ctx->addr); + if (!target) { + return -ENOENT; + } + + if (cli->state == BT_MESH_BLOB_CLI_STATE_START) { + expected_phase = BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK; + } else if (cli->state == BT_MESH_BLOB_CLI_STATE_XFER_CHECK) { + expected_phase = BT_MESH_BLOB_XFER_PHASE_COMPLETE; + } else if (cli->state != BT_MESH_BLOB_CLI_STATE_XFER_PROGRESS_GET) { + expected_phase = BT_MESH_BLOB_XFER_PHASE_INACTIVE; + } else { /* cli->state == BT_MESH_BLOB_CLI_STATE_XFER_PROGRESS_GET */ + blob_cli_broadcast_rsp(cli, target); + if (cli->cb && cli->cb->xfer_progress) { + cli->cb->xfer_progress(cli, target, &info); + } + return 0; + } + + if (info.status != BT_MESH_BLOB_SUCCESS) { + target_drop(cli, target, info.status); + } else if (info.phase != expected_phase) { + LOG_WRN("Wrong phase: %u != %u", expected_phase, info.phase); + return -EINVAL; + } else if (info.phase != BT_MESH_BLOB_XFER_PHASE_INACTIVE && + info.id != cli->xfer->id) { + target_drop(cli, target, BT_MESH_BLOB_ERR_WRONG_BLOB_ID); + } + + blob_cli_broadcast_rsp(cli, target); + + return 0; +} + +static int handle_block_report(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_cli *cli = mod->rt->user_data; + struct block_status status = { + .status = BT_MESH_BLOB_SUCCESS, + .block.number = cli->block.number, + .missing = (buf->len ? BT_MESH_BLOB_CHUNKS_MISSING_ENCODED : + BT_MESH_BLOB_CHUNKS_MISSING_NONE), + }; + struct bt_mesh_blob_target *target; + + if (!cli->xfer) { + return -EINVAL; + } + + if (cli->xfer->mode == BT_MESH_BLOB_XFER_MODE_PUSH) { + LOG_WRN("Unexpected encoded block report in push mode"); + return -EINVAL; + } + + LOG_DBG(""); + + target = target_get(cli, ctx->addr); + if (!target) { + return -ENOENT; + } + + while (buf->len) { + int idx; + + idx = chunk_idx_decode(buf); + if (idx < 0) { + return idx; + } + + blob_chunk_missing_set(status.block.missing, idx, true); + } + + /* If all chunks were already confirmed by this target, Send Data State Machine is in Final + * state for this target. Therefore, the message should be ignored. + */ + if (next_missing_chunk(cli, target->pull->missing, 0) >= cli->block.chunk_count) { + LOG_DBG("All chunks already confirmed"); + return 0; + } + + cli->tx.cli_timestamp = k_uptime_get() + CLIENT_TIMEOUT_MSEC(cli); + + rx_block_status(cli, target, &status); + + return 0; +} + +static int handle_block_status(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_cli *cli = mod->rt->user_data; + struct bt_mesh_blob_target *target; + struct block_status status = { 0 }; + uint8_t status_and_format; + uint16_t chunk_size; + size_t len; + int idx; + + target = target_get(cli, ctx->addr); + if (!target) { + return -ENOENT; + } + + status_and_format = net_buf_simple_pull_u8(buf); + status.status = status_and_format & BIT_MASK(4); + status.missing = status_and_format >> 6; + status.block.number = net_buf_simple_pull_le16(buf); + chunk_size = net_buf_simple_pull_le16(buf); + status.block.chunk_count = + DIV_ROUND_UP(cli->block.size, chunk_size); + + LOG_DBG("status: %u block: %u encoding: %u", status.status, + status.block.number, status.missing); + + switch (status.missing) { + case BT_MESH_BLOB_CHUNKS_MISSING_ALL: + blob_chunk_missing_set_all(&status.block); + break; + case BT_MESH_BLOB_CHUNKS_MISSING_NONE: + break; + case BT_MESH_BLOB_CHUNKS_MISSING_SOME: + if (buf->len > sizeof(status.block.missing)) { + return -EINVAL; + } + + len = buf->len; + memcpy(status.block.missing, net_buf_simple_pull_mem(buf, len), + len); + + LOG_DBG("Missing: %s", bt_hex(status.block.missing, len)); + break; + case BT_MESH_BLOB_CHUNKS_MISSING_ENCODED: + /** MshMBTv1.0: 5.3.8: An empty Missing Chunks field entails that there are no + * missing chunks for this block. + */ + if (!buf->len) { + status.missing = BT_MESH_BLOB_CHUNKS_MISSING_NONE; + } + + while (buf->len) { + idx = chunk_idx_decode(buf); + if (idx < 0 || idx >= status.block.chunk_count) { + LOG_ERR("Invalid encoding"); + return -EINVAL; + } + + LOG_DBG("Missing %d", idx); + + blob_chunk_missing_set(status.block.missing, idx, true); + } + break; + } + + rx_block_status(cli, target, &status); + + return 0; +} + +static int handle_info_status(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_cli *cli = mod->rt->user_data; + struct bt_mesh_blob_cli_caps caps; + enum bt_mesh_blob_status status; + struct bt_mesh_blob_target *target; + + if (cli->state != BT_MESH_BLOB_CLI_STATE_CAPS_GET) { + return -EBUSY; + } + + caps.min_block_size_log = net_buf_simple_pull_u8(buf); + caps.max_block_size_log = net_buf_simple_pull_u8(buf); + caps.max_chunks = net_buf_simple_pull_le16(buf); + caps.max_chunk_size = net_buf_simple_pull_le16(buf); + caps.max_size = net_buf_simple_pull_le32(buf); + caps.mtu_size = net_buf_simple_pull_le16(buf); + caps.modes = net_buf_simple_pull_u8(buf); + + if (caps.min_block_size_log < 0x06 || + caps.max_block_size_log > 0x20 || + caps.max_block_size_log < caps.min_block_size_log || + caps.max_chunks == 0 || caps.max_chunk_size < 8 || + caps.max_size == 0 || caps.mtu_size < 0x14) { + return -EINVAL; + } + + LOG_DBG("0x%04x\n\tblock size: %u - %u\n\tchunks: %u\n\tchunk size: %u\n" + "\tblob size: %u\n\tmtu size: %u\n\tmodes: %x", + ctx->addr, caps.min_block_size_log, caps.max_block_size_log, + caps.max_chunks, caps.max_chunk_size, caps.max_size, + caps.mtu_size, caps.modes); + + target = target_get(cli, ctx->addr); + if (!target) { + return -ENOENT; + } + + status = caps_adjust(cli, &caps); + if (status != BT_MESH_BLOB_SUCCESS) { + target_drop(cli, target, status); + } + + blob_cli_broadcast_rsp(cli, target); + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_blob_cli_op[] = { + { BT_MESH_BLOB_OP_XFER_STATUS, BT_MESH_LEN_MIN(2), handle_xfer_status }, + { BT_MESH_BLOB_OP_BLOCK_REPORT, BT_MESH_LEN_MIN(0), handle_block_report }, + { BT_MESH_BLOB_OP_BLOCK_STATUS, BT_MESH_LEN_MIN(5), handle_block_status }, + { BT_MESH_BLOB_OP_INFO_STATUS, BT_MESH_LEN_EXACT(13), handle_info_status }, + BT_MESH_MODEL_OP_END, +}; + +static int blob_cli_init(const struct bt_mesh_model *mod) +{ + struct bt_mesh_blob_cli *cli = mod->rt->user_data; + + cli->mod = mod; + + bt_mesh_blob_cli_set_chunk_interval_ms(cli, CONFIG_BT_MESH_TX_BLOB_CHUNK_SEND_INTERVAL); + cli->tx.cli_timestamp = 0ll; + k_work_init_delayable(&cli->tx.retry, retry_timeout); + k_work_init_delayable(&cli->tx.complete, tx_complete); + + return 0; +} + +static void blob_cli_reset(const struct bt_mesh_model *mod) +{ + struct bt_mesh_blob_cli *cli = mod->rt->user_data; + + cli_state_reset(cli); +} + +const struct bt_mesh_model_cb _bt_mesh_blob_cli_cb = { + .init = blob_cli_init, + .reset = blob_cli_reset, +}; + + +int bt_mesh_blob_cli_caps_get(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_cli_inputs *inputs) +{ + if (bt_mesh_blob_cli_is_busy(cli)) { + return -EBUSY; + } + + cli->inputs = inputs; + + cli->caps.min_block_size_log = 0x06; + cli->caps.max_block_size_log = 0x20; + cli->caps.max_chunks = CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX; + cli->caps.max_chunk_size = BLOB_TX_CHUNK_SIZE; + cli->caps.max_size = 0xffffffff; + cli->caps.mtu_size = 0xffff; + cli->caps.modes = BT_MESH_BLOB_XFER_MODE_ALL; + + if (!targets_reset(cli)) { + LOG_ERR("No valid targets"); + return -ENODEV; + } + + caps_get(cli); + + return 0; +} + +int bt_mesh_blob_cli_send(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_cli_inputs *inputs, + const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_io *io) +{ + if (bt_mesh_blob_cli_is_busy(cli)) { + LOG_ERR("BLOB Client is busy"); + return -EBUSY; + } + + if (!(xfer->mode & BT_MESH_BLOB_XFER_MODE_ALL) || xfer->block_size_log < 0x06 || + xfer->block_size_log > 0x20 || xfer->chunk_size < 8 || + xfer->chunk_size > BLOB_TX_CHUNK_SIZE) { + LOG_ERR("Incompatible transfer parameters"); + return -EINVAL; + } + + cli->xfer = xfer; + cli->inputs = inputs; + cli->io = io; + + if (cli->xfer->block_size_log == 0x20) { + cli->block_count = 1; + } else { + cli->block_count = DIV_ROUND_UP(cli->xfer->size, (1U << cli->xfer->block_size_log)); + } + + block_set(cli, 0); + + if (cli->block.chunk_count > CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX) { + LOG_ERR("Too many chunks"); + return -EINVAL; + } + + if (!targets_reset(cli)) { + LOG_ERR("No valid targets"); + return -ENODEV; + } + + LOG_DBG("\n\tblock size log: %u\n\tchunk size: %u\n" + "\tblob size: %u\n\tmode: %x", + cli->xfer->block_size_log, cli->xfer->chunk_size, + cli->xfer->size, cli->xfer->mode); + + return xfer_start(cli); +} + +int bt_mesh_blob_cli_suspend(struct bt_mesh_blob_cli *cli) +{ + if (cli->state == BT_MESH_BLOB_CLI_STATE_SUSPENDED) { + return 0; + } + + if (cli->state != BT_MESH_BLOB_CLI_STATE_BLOCK_START && + cli->state != BT_MESH_BLOB_CLI_STATE_BLOCK_SEND && + cli->state != BT_MESH_BLOB_CLI_STATE_BLOCK_CHECK) { + LOG_WRN("BLOB xfer not started: %d", cli->state); + return -EINVAL; + } + + cli->state = BT_MESH_BLOB_CLI_STATE_SUSPENDED; + (void)k_work_cancel_delayable(&cli->tx.retry); + cli->tx.ctx.is_inited = 0; + cli->tx.sending = 0; + cli->tx.cli_timestamp = 0ll; + return 0; +} + +int bt_mesh_blob_cli_resume(struct bt_mesh_blob_cli *cli) +{ + struct bt_mesh_blob_target *target; + + if (cli->state != BT_MESH_BLOB_CLI_STATE_SUSPENDED) { + LOG_WRN("Not suspended"); + return -EINVAL; + } + + /* Restore timed out targets. */ + TARGETS_FOR_EACH(cli, target) { + if (!!target->timedout) { + target->status = BT_MESH_BLOB_SUCCESS; + target->timedout = 0U; + } + } + + if (!targets_reset(cli)) { + LOG_ERR("No valid targets"); + return -ENODEV; + } + + block_set(cli, 0); + return xfer_start(cli); +} + +void bt_mesh_blob_cli_cancel(struct bt_mesh_blob_cli *cli) +{ + if (!bt_mesh_blob_cli_is_busy(cli)) { + LOG_WRN("BLOB xfer already cancelled"); + return; + } + + LOG_DBG(""); + + if (cli->state == BT_MESH_BLOB_CLI_STATE_CAPS_GET || + cli->state == BT_MESH_BLOB_CLI_STATE_SUSPENDED) { + cli_state_reset(cli); + return; + } + + cli->tx.cancelled = 1U; + cli->state = BT_MESH_BLOB_CLI_STATE_CANCEL; +} + +int bt_mesh_blob_cli_xfer_progress_get(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_cli_inputs *inputs) +{ + if (bt_mesh_blob_cli_is_busy(cli)) { + return -EBUSY; + } + + cli->inputs = inputs; + + check_transfer(cli); + + return 0; +} + +uint8_t bt_mesh_blob_cli_xfer_progress_active_get(struct bt_mesh_blob_cli *cli) +{ + if (cli->state < BT_MESH_BLOB_CLI_STATE_START) { + return 0; + } + + return (100U * cli->block.number) / cli->block_count; +} + +bool bt_mesh_blob_cli_is_busy(struct bt_mesh_blob_cli *cli) +{ + return cli->state != BT_MESH_BLOB_CLI_STATE_NONE; +} + +void bt_mesh_blob_cli_set_chunk_interval_ms(struct bt_mesh_blob_cli *cli, uint32_t interval_ms) +{ + cli->chunk_interval_ms = interval_ms; +} diff --git a/components/bt/esp_ble_mesh/v1.1/mbt/blob_srv.c b/components/bt/esp_ble_mesh/v1.1/mbt/blob_srv.c new file mode 100644 index 0000000000..2d4ec561f8 --- /dev/null +++ b/components/bt/esp_ble_mesh/v1.1/mbt/blob_srv.c @@ -0,0 +1,1013 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "net.h" +#include "access.h" +#include "transport.h" +#include "lpn.h" +#include "blob.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_blob_srv); + +#define MTU_SIZE_MAX (BT_MESH_RX_SDU_MAX - BT_MESH_MIC_SHORT) + +/* The Receive BLOB Timeout Timer */ +#define SERVER_TIMEOUT_SECS(srv) (10 * (1 + (srv)->state.timeout_base)) +/* The initial timer value used by an instance of the Pull BLOB State machine - T_BPI */ +#define REPORT_TIMER_TIMEOUT K_SECONDS(CONFIG_BT_MESH_BLOB_REPORT_TIMEOUT) + +BUILD_ASSERT(BLOB_BLOCK_SIZE_LOG_MIN <= BLOB_BLOCK_SIZE_LOG_MAX, + "The must be at least one number between the min and " + "max block size that is the power of two."); + +BUILD_ASSERT((BLOB_XFER_STATUS_MSG_MAXLEN + BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_XFER_STATUS) + + BT_MESH_MIC_SHORT) <= BT_MESH_TX_SDU_MAX, + "The BLOB Transfer Status message does not fit into the maximum outgoing SDU size."); + +BUILD_ASSERT((BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN + + BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_BLOCK_REPORT) + BT_MESH_MIC_SHORT) + <= BT_MESH_TX_SDU_MAX, + "The BLOB Partial Block Report message does not fit into the maximum outgoing SDU " + "size."); + +BUILD_ASSERT((BLOB_BLOCK_STATUS_MSG_MAXLEN + BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_BLOCK_STATUS) + + BT_MESH_MIC_SHORT) <= BT_MESH_TX_SDU_MAX, + "The BLOB Block Status message does not fit into the maximum outgoing SDU size."); + +static void cancel(struct bt_mesh_blob_srv *srv); +static void suspend(struct bt_mesh_blob_srv *srv); + +static inline uint32_t block_count_get(const struct bt_mesh_blob_srv *srv) +{ + return DIV_ROUND_UP(srv->state.xfer.size, + (1U << srv->state.xfer.block_size_log)); +} + +static inline uint32_t max_chunk_size(const struct bt_mesh_blob_srv *srv) +{ + return MIN((srv->state.mtu_size - 2 - BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_CHUNK)), + BLOB_RX_CHUNK_SIZE); +} + +static inline uint32_t max_chunk_count(const struct bt_mesh_blob_srv *srv) +{ + return MIN(8 * (srv->state.mtu_size - 6), + CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX); +} + +static inline uint32_t missing_chunks(const struct bt_mesh_blob_block *block) +{ + int i; + uint32_t count = 0; + + for (i = 0; i < ARRAY_SIZE(block->missing); ++i) { + count += POPCOUNT(block->missing[i]); + } + + return count; +} + +static void store_state(const struct bt_mesh_blob_srv *srv) +{ + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) { + return; + } + + /* Convert bit count to byte count: */ + uint32_t block_len = DIV_ROUND_UP(block_count_get(srv), 8); + + bt_mesh_model_data_store( + srv->mod, false, NULL, &srv->state, + offsetof(struct bt_mesh_blob_srv_state, blocks) + block_len); +} + +static void erase_state(struct bt_mesh_blob_srv *srv) +{ + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) { + return; + } + + bt_mesh_model_data_store(srv->mod, false, NULL, NULL, 0); +} + +static int io_open(struct bt_mesh_blob_srv *srv) +{ + if (!srv->io->open) { + return 0; + } + + return srv->io->open(srv->io, &srv->state.xfer, BT_MESH_BLOB_WRITE); +} + +static void io_close(struct bt_mesh_blob_srv *srv) +{ + if (!srv->io->close) { + return; + } + + srv->io->close(srv->io, &srv->state.xfer); +} + +static void reset_timer(struct bt_mesh_blob_srv *srv) +{ + uint32_t timeout_secs = + srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL ? + MAX(SERVER_TIMEOUT_SECS(srv), + CONFIG_BT_MESH_BLOB_REPORT_TIMEOUT + 1) : + SERVER_TIMEOUT_SECS(srv); + k_work_reschedule(&srv->rx_timeout, K_SECONDS(timeout_secs)); +} + +static void buf_chunk_index_add(struct net_buf_simple *buf, uint16_t chunk) +{ + /* utf-8 encoded: */ + if (chunk < 0x80) { + net_buf_simple_add_u8(buf, chunk); + } else if (chunk < 0x8000) { + net_buf_simple_add_u8(buf, 0xc0 | chunk >> 6); + net_buf_simple_add_u8(buf, 0x80 | (chunk & BIT_MASK(6))); + } else { + net_buf_simple_add_u8(buf, 0xe0 | chunk >> 12); + net_buf_simple_add_u8(buf, 0x80 | ((chunk >> 6) & BIT_MASK(6))); + net_buf_simple_add_u8(buf, 0x80 | (chunk & BIT_MASK(6))); + } +} + +static int pull_req_max(const struct bt_mesh_blob_srv *srv) +{ + int count = CONFIG_BT_MESH_BLOB_SRV_PULL_REQ_COUNT; + +#if defined(CONFIG_BT_MESH_LOW_POWER) + /* No point in requesting more than the friend node can hold: */ + if (bt_mesh_lpn_established()) { + uint32_t segments_per_chunk = DIV_ROUND_UP( + BLOB_CHUNK_SDU_LEN(srv->state.xfer.chunk_size), + BT_MESH_APP_SEG_SDU_MAX); + + count = MIN(CONFIG_BT_MESH_BLOB_SRV_PULL_REQ_COUNT, + bt_mesh.lpn.queue_size / segments_per_chunk); + } +#endif + + return MIN(count, missing_chunks(&srv->block)); +} + +static void report_sent(int err, void *cb_data) +{ + struct bt_mesh_blob_srv *srv = cb_data; + + LOG_DBG(""); + + if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER) && bt_mesh_lpn_established()) { + bt_mesh_lpn_poll(); + } + + if (k_work_delayable_is_pending(&srv->rx_timeout)) { + k_work_reschedule(&srv->pull.report, REPORT_TIMER_TIMEOUT); + } +} + +static void block_report(struct bt_mesh_blob_srv *srv) +{ + static const struct bt_mesh_send_cb report_cb = { .end = report_sent }; + struct bt_mesh_msg_ctx ctx = { + .app_idx = srv->state.app_idx, + .send_ttl = srv->state.ttl, + .addr = srv->state.cli, + }; + int count; + int i; + + LOG_DBG("rx BLOB Timeout Timer: %i", k_work_delayable_is_pending(&srv->rx_timeout)); + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_BLOCK_REPORT, + BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_BLOCK_REPORT); + + count = pull_req_max(srv); + + for (i = 0; i < srv->block.chunk_count && count; ++i) { + if (blob_chunk_missing_get(srv->block.missing, i)) { + buf_chunk_index_add(&buf, i); + count--; + } + } + + (void)bt_mesh_model_send(srv->mod, &ctx, &buf, &report_cb, srv); +} + +static void phase_set(struct bt_mesh_blob_srv *srv, + enum bt_mesh_blob_xfer_phase phase) +{ + srv->phase = phase; + LOG_DBG("Phase: %u", phase); +} + +static void cancel(struct bt_mesh_blob_srv *srv) +{ + /* TODO: Could this state be preserved instead somehow? Wiping the + * entire transfer state is a bit overkill + */ + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_INACTIVE); + srv->state.xfer.mode = BT_MESH_BLOB_XFER_MODE_NONE; + srv->state.ttl = BT_MESH_TTL_DEFAULT; + srv->block.number = 0xffff; + memset(srv->block.missing, 0, sizeof(srv->block.missing)); + srv->state.xfer.chunk_size = 0xffff; + k_work_cancel_delayable(&srv->rx_timeout); + k_work_cancel_delayable(&srv->pull.report); + io_close(srv); + erase_state(srv); + + if (srv->cb && srv->cb->end) { + srv->cb->end(srv, srv->state.xfer.id, false); + } +} + +static void suspend(struct bt_mesh_blob_srv *srv) +{ + LOG_DBG(""); + k_work_cancel_delayable(&srv->rx_timeout); + k_work_cancel_delayable(&srv->pull.report); + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_SUSPENDED); + if (srv->cb && srv->cb->suspended) { + srv->cb->suspended(srv); + } +} + +static void resume(struct bt_mesh_blob_srv *srv) +{ + LOG_DBG("Resuming"); + + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK); + reset_timer(srv); +} + +static void end(struct bt_mesh_blob_srv *srv) +{ + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_COMPLETE); + k_work_cancel_delayable(&srv->rx_timeout); + k_work_cancel_delayable(&srv->pull.report); + io_close(srv); + erase_state(srv); + + if (srv->cb && srv->cb->end) { + srv->cb->end(srv, srv->state.xfer.id, true); + } +} + +static bool all_blocks_received(struct bt_mesh_blob_srv *srv) +{ + for (int i = 0; i < ARRAY_SIZE(srv->state.blocks); ++i) { + if (srv->state.blocks[i]) { + return false; + } + } + + return true; +} + +static bool pull_mode_xfer_complete(struct bt_mesh_blob_srv *srv) +{ + return srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL && + srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK && + all_blocks_received(srv); +} + +static void timeout(struct k_work *work) +{ + struct bt_mesh_blob_srv *srv = + CONTAINER_OF(work, struct bt_mesh_blob_srv, rx_timeout.work); + + LOG_DBG(""); + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START) { + cancel(srv); + } else if (pull_mode_xfer_complete(srv)) { + end(srv); + } else { + suspend(srv); + } +} + +static void report_timeout(struct k_work *work) +{ + struct bt_mesh_blob_srv *srv = + CONTAINER_OF(work, struct bt_mesh_blob_srv, pull.report.work); + + LOG_DBG(""); + + if (srv->phase != BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK && + srv->phase != BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK) { + return; + } + + block_report(srv); +} + +/******************************************************************************* + * Message handling + ******************************************************************************/ + +static void xfer_status_rsp(struct bt_mesh_blob_srv *srv, + struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_blob_status status) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_XFER_STATUS, + BLOB_XFER_STATUS_MSG_MAXLEN); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_XFER_STATUS); + + net_buf_simple_add_u8(&buf, ((status & BIT_MASK(4)) | + (srv->state.xfer.mode << 6))); + net_buf_simple_add_u8(&buf, srv->phase); + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE) { + goto send; + } + + net_buf_simple_add_le64(&buf, srv->state.xfer.id); + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START) { + goto send; + } + + net_buf_simple_add_le32(&buf, srv->state.xfer.size); + net_buf_simple_add_u8(&buf, srv->state.xfer.block_size_log); + net_buf_simple_add_le16(&buf, srv->state.mtu_size); + net_buf_simple_add_mem(&buf, srv->state.blocks, + DIV_ROUND_UP(block_count_get(srv), 8)); + +send: + ctx->send_ttl = srv->state.ttl; + (void)bt_mesh_model_send(srv->mod, ctx, &buf, NULL, NULL); +} + +static void block_status_rsp(struct bt_mesh_blob_srv *srv, + struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_blob_status status) +{ + enum bt_mesh_blob_chunks_missing format; + uint32_t missing; + int i; + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_BLOCK_STATUS, + BLOB_BLOCK_STATUS_MSG_MAXLEN); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_BLOCK_STATUS); + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE || + srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START) { + missing = srv->block.chunk_count; + } else if (srv->phase == BT_MESH_BLOB_XFER_PHASE_COMPLETE) { + missing = 0U; + } else { + missing = missing_chunks(&srv->block); + } + + if (srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL) { + format = BT_MESH_BLOB_CHUNKS_MISSING_ENCODED; + } else if (missing == srv->block.chunk_count) { + format = BT_MESH_BLOB_CHUNKS_MISSING_ALL; + } else if (missing == 0) { + format = BT_MESH_BLOB_CHUNKS_MISSING_NONE; + } else { + format = BT_MESH_BLOB_CHUNKS_MISSING_SOME; + } + + LOG_DBG("Status: %u, missing: %u/%u", status, missing, srv->block.chunk_count); + + net_buf_simple_add_u8(&buf, (status & BIT_MASK(4)) | (format << 6)); + net_buf_simple_add_le16(&buf, srv->block.number); + net_buf_simple_add_le16(&buf, srv->state.xfer.chunk_size); + + if (format == BT_MESH_BLOB_CHUNKS_MISSING_SOME) { + net_buf_simple_add_mem(&buf, srv->block.missing, + DIV_ROUND_UP(srv->block.chunk_count, + 8)); + + LOG_DBG("Bits: %s", + bt_hex(srv->block.missing, + DIV_ROUND_UP(srv->block.chunk_count, 8))); + + } else if (format == BT_MESH_BLOB_CHUNKS_MISSING_ENCODED) { + int count = pull_req_max(srv); + + for (i = 0; (i < srv->block.chunk_count) && count; ++i) { + if (blob_chunk_missing_get(srv->block.missing, i)) { + LOG_DBG("Missing %u", i); + buf_chunk_index_add(&buf, i); + count--; + } + } + } + + if (srv->phase != BT_MESH_BLOB_XFER_PHASE_INACTIVE) { + ctx->send_ttl = srv->state.ttl; + } + + (void)bt_mesh_model_send(srv->mod, ctx, &buf, NULL, NULL); +} + +static int handle_xfer_get(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + + LOG_DBG(""); + + if (pull_mode_xfer_complete(srv)) { + /* The client requested transfer. If we are in Pull mode and all blocks were + * received, we should change the Transfer state here to Complete so that the client + * receives the correct state. + */ + end(srv); + } + + xfer_status_rsp(srv, ctx, BT_MESH_BLOB_SUCCESS); + + return 0; +} + +static int handle_xfer_start(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + enum bt_mesh_blob_status status; + enum bt_mesh_blob_xfer_mode mode; + uint64_t id; + size_t size; + uint8_t block_size_log; + uint32_t block_count; + uint16_t mtu_size; + int err; + + mode = (net_buf_simple_pull_u8(buf) >> 6); + id = net_buf_simple_pull_le64(buf); + size = net_buf_simple_pull_le32(buf); + block_size_log = net_buf_simple_pull_u8(buf); + mtu_size = net_buf_simple_pull_le16(buf); + + LOG_DBG("\n\tsize: %u block size: %u\n\tmtu_size: %u\n\tmode: %s", + size, (1U << block_size_log), mtu_size, + mode == BT_MESH_BLOB_XFER_MODE_PUSH ? "push" : "pull"); + + if (mode != BT_MESH_BLOB_XFER_MODE_PULL && + mode != BT_MESH_BLOB_XFER_MODE_PUSH) { + LOG_WRN("Invalid mode 0x%x", mode); + return -EINVAL; + } + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE) { + status = BT_MESH_BLOB_ERR_WRONG_PHASE; + LOG_WRN("Uninitialized"); + goto rsp; + } + + if (srv->state.xfer.id != id) { + status = BT_MESH_BLOB_ERR_WRONG_BLOB_ID; + /* bt_hex uses static array for the resulting hex string. + * Not possible to use bt_hex in the same logging function twice. + */ + LOG_WRN("Invalid ID: %s", bt_hex(&id, sizeof(uint64_t))); + LOG_WRN("Expected ID: %s", bt_hex(&srv->state.xfer.id, sizeof(uint64_t))); + goto rsp; + } + + if (srv->phase != BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START) { + if (srv->state.xfer.mode != mode || + srv->state.xfer.size != size || + srv->state.xfer.block_size_log != block_size_log || + srv->state.mtu_size > mtu_size) { + status = BT_MESH_BLOB_ERR_WRONG_PHASE; + LOG_WRN("Busy"); + goto rsp; + } + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_SUSPENDED) { + resume(srv); + store_state(srv); + } else { + LOG_DBG("Duplicate"); + } + + status = BT_MESH_BLOB_SUCCESS; + goto rsp; + } + + if (size > CONFIG_BT_MESH_BLOB_SIZE_MAX) { + LOG_WRN("Too large"); + status = BT_MESH_BLOB_ERR_BLOB_TOO_LARGE; + goto rsp; + } + + if (((1U << block_size_log) < CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MIN) || + ((1U << block_size_log) > CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX)) { + LOG_WRN("Invalid block size: %u", block_size_log); + status = BT_MESH_BLOB_ERR_INVALID_BLOCK_SIZE; + goto rsp; + } + + srv->state.cli = ctx->addr; + srv->state.app_idx = ctx->app_idx; + srv->state.mtu_size = MIN(mtu_size, MTU_SIZE_MAX); + srv->state.xfer.id = id; + srv->state.xfer.size = size; + srv->state.xfer.mode = mode; + srv->state.xfer.block_size_log = block_size_log; + srv->state.xfer.chunk_size = 0xffff; + srv->block.number = 0xffff; + + block_count = block_count_get(srv); + if (block_count > BT_MESH_BLOB_BLOCKS_MAX) { + LOG_WRN("Invalid block count (%u)", block_count); + status = BT_MESH_BLOB_ERR_INVALID_PARAM; + cancel(srv); + goto rsp; + } + + memset(srv->state.blocks, 0, sizeof(srv->state.blocks)); + for (int i = 0; i < block_count; i++) { + atomic_set_bit(srv->state.blocks, i); + } + + err = io_open(srv); + if (err) { + LOG_ERR("Couldn't open stream (err: %d)", err); + status = BT_MESH_BLOB_ERR_INTERNAL; + cancel(srv); + goto rsp; + } + + if (srv->cb && srv->cb->start) { + err = srv->cb->start(srv, ctx, &srv->state.xfer); + if (err) { + LOG_ERR("Couldn't start transfer (err: %d)", err); + status = BT_MESH_BLOB_ERR_INTERNAL; + cancel(srv); + goto rsp; + } + } + + reset_timer(srv); + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK); + store_state(srv); + status = BT_MESH_BLOB_SUCCESS; + +rsp: + xfer_status_rsp(srv, ctx, status); + + return 0; +} + +static int handle_xfer_cancel(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + enum bt_mesh_blob_status status = BT_MESH_BLOB_SUCCESS; + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + uint64_t id; + + id = net_buf_simple_pull_le64(buf); + + LOG_DBG("%u", (uint32_t)id); + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE) { + goto rsp; + } + + if (srv->state.xfer.id != id) { + status = BT_MESH_BLOB_ERR_WRONG_BLOB_ID; + goto rsp; + } + + cancel(srv); + +rsp: + xfer_status_rsp(srv, ctx, status); + + return 0; +} + +static int handle_block_get(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + enum bt_mesh_blob_status status; + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + + switch (srv->phase) { + case BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK: + case BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK: + case BT_MESH_BLOB_XFER_PHASE_COMPLETE: + status = BT_MESH_BLOB_SUCCESS; + break; + case BT_MESH_BLOB_XFER_PHASE_SUSPENDED: + status = BT_MESH_BLOB_ERR_INFO_UNAVAILABLE; + break; + case BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START: + case BT_MESH_BLOB_XFER_PHASE_INACTIVE: + status = BT_MESH_BLOB_ERR_WRONG_PHASE; + break; + default: + status = BT_MESH_BLOB_ERR_INTERNAL; + break; + } + + LOG_DBG(""); + + block_status_rsp(srv, ctx, status); + + return 0; +} + +static int handle_block_start(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + enum bt_mesh_blob_status status; + uint16_t block_number, chunk_size; + int err; + + block_number = net_buf_simple_pull_le16(buf); + chunk_size = net_buf_simple_pull_le16(buf); + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START || + srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE) { + status = BT_MESH_BLOB_ERR_WRONG_PHASE; + goto rsp; + } + + reset_timer(srv); + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK) { + if (block_number != srv->block.number || + chunk_size != srv->state.xfer.chunk_size) { + status = BT_MESH_BLOB_ERR_WRONG_PHASE; + } else { + status = BT_MESH_BLOB_SUCCESS; + } + + goto rsp; + } + + if (block_number >= block_count_get(srv)) { + status = BT_MESH_BLOB_ERR_INVALID_BLOCK_NUM; + goto rsp; + } + + if (!chunk_size || chunk_size > max_chunk_size(srv) || + (DIV_ROUND_UP((1 << srv->state.xfer.block_size_log), chunk_size) > + max_chunk_count(srv))) { + LOG_WRN("Invalid chunk size: (chunk size: %u, max: %u, block log: %u, count: %u)", + chunk_size, max_chunk_size(srv), + srv->state.xfer.block_size_log, + max_chunk_count(srv)); + status = BT_MESH_BLOB_ERR_INVALID_CHUNK_SIZE; + goto rsp; + } + + srv->block.size = blob_block_size( + srv->state.xfer.size, srv->state.xfer.block_size_log, block_number); + srv->block.number = block_number; + srv->block.chunk_count = DIV_ROUND_UP(srv->block.size, chunk_size); + srv->state.xfer.chunk_size = chunk_size; + srv->block.offset = block_number * (1UL << srv->state.xfer.block_size_log); + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_COMPLETE || + !atomic_test_bit(srv->state.blocks, block_number)) { + memset(srv->block.missing, 0, sizeof(srv->block.missing)); + status = BT_MESH_BLOB_SUCCESS; + goto rsp; + } + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_SUSPENDED && srv->cb && + srv->cb->resume) { + srv->cb->resume(srv); + } + + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK); + blob_chunk_missing_set_all(&srv->block); + + LOG_DBG("%u: (%u/%u)\n\tsize: %u\n\tchunk size: %u\n\tchunk count: %u", + srv->block.number, srv->block.number + 1, block_count_get(srv), + srv->block.size, chunk_size, srv->block.chunk_count); + + if (srv->io->block_start) { + err = srv->io->block_start(srv->io, &srv->state.xfer, + &srv->block); + if (err) { + cancel(srv); + status = BT_MESH_BLOB_ERR_INTERNAL; + goto rsp; + } + } + + if (srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL) { + /* Wait for the client to send the first chunk */ + k_work_reschedule(&srv->pull.report, REPORT_TIMER_TIMEOUT); + } + + status = BT_MESH_BLOB_SUCCESS; + +rsp: + block_status_rsp(srv, ctx, status); + + return 0; +} + +static int handle_chunk(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + struct bt_mesh_blob_chunk chunk; + size_t expected_size = 0; + uint16_t idx; + int err; + + idx = net_buf_simple_pull_le16(buf); + chunk.size = buf->len; + chunk.data = net_buf_simple_pull_mem(buf, chunk.size); + chunk.offset = idx * srv->state.xfer.chunk_size; + + if (srv->phase != BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK || + idx >= srv->block.chunk_count) { + LOG_ERR("Invalid phase or index (%u %u)", srv->phase, + idx); + return -EINVAL; + } + + if (idx == srv->block.chunk_count - 1) { + expected_size = srv->block.size % srv->state.xfer.chunk_size; + } + + if (expected_size == 0) { + expected_size = srv->state.xfer.chunk_size; + } + + if (chunk.size != expected_size) { + LOG_ERR("Unexpected size: %u != %u", expected_size, chunk.size); + return -EINVAL; + } + + LOG_DBG("%u/%u (%u bytes)", idx + 1, srv->block.chunk_count, + chunk.size); + + reset_timer(srv); + if (srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL) { + k_work_reschedule(&srv->pull.report, REPORT_TIMER_TIMEOUT); + } + + if (!blob_chunk_missing_get(srv->block.missing, idx)) { + LOG_DBG("Duplicate chunk %u", idx); + return -EALREADY; + } + + err = srv->io->wr(srv->io, &srv->state.xfer, &srv->block, &chunk); + if (err) { + return err; + } + + blob_chunk_missing_set(srv->block.missing, idx, false); + if (missing_chunks(&srv->block)) { + return 0; + } + + if (srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL) { + block_report(srv); + } + + if (srv->io->block_end) { + srv->io->block_end(srv->io, &srv->state.xfer, &srv->block); + } + + atomic_clear_bit(srv->state.blocks, srv->block.number); + + if (!all_blocks_received(srv)) { + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK); + store_state(srv); + return 0; + } + + if (srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL) { + /* By spec (section 5.2.4), the BLOB Server stops sending BLOB Partial Block Report + * messages "If the current block is the last block, then the server determines that + * the client knows the transfer is complete. For example, a higher-layer model may + * indicate that the client considers the transfer complete." + * + * We don't have any way for higher-layer model to indicate that the transfer is + * complete. Therefore we need to keep sending Partial Block Report messages until + * the client sends BLOB Transfer Get message or the Block Timer expires. + */ + return 0; + } + + end(srv); + return 0; +} + +static int handle_info_get(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + + LOG_DBG(""); + + BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_BLOB_OP_INFO_STATUS, 15); + bt_mesh_model_msg_init(&rsp, BT_MESH_BLOB_OP_INFO_STATUS); + net_buf_simple_add_u8(&rsp, BLOB_BLOCK_SIZE_LOG_MIN); + net_buf_simple_add_u8(&rsp, BLOB_BLOCK_SIZE_LOG_MAX); + net_buf_simple_add_le16(&rsp, CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX); + net_buf_simple_add_le16(&rsp, BLOB_RX_CHUNK_SIZE); + net_buf_simple_add_le32(&rsp, CONFIG_BT_MESH_BLOB_SIZE_MAX); + net_buf_simple_add_le16(&rsp, MTU_SIZE_MAX); + net_buf_simple_add_u8(&rsp, BT_MESH_BLOB_XFER_MODE_ALL); + + if (srv->phase != BT_MESH_BLOB_XFER_PHASE_INACTIVE) { + ctx->send_ttl = srv->state.ttl; + } + + (void)bt_mesh_model_send(srv->mod, ctx, &rsp, NULL, NULL); + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_blob_srv_op[] = { + { BT_MESH_BLOB_OP_XFER_GET, BT_MESH_LEN_EXACT(0), handle_xfer_get }, + { BT_MESH_BLOB_OP_XFER_START, BT_MESH_LEN_EXACT(16), handle_xfer_start }, + { BT_MESH_BLOB_OP_XFER_CANCEL, BT_MESH_LEN_EXACT(8), handle_xfer_cancel }, + { BT_MESH_BLOB_OP_BLOCK_GET, BT_MESH_LEN_EXACT(0), handle_block_get }, + { BT_MESH_BLOB_OP_BLOCK_START, BT_MESH_LEN_EXACT(4), handle_block_start }, + { BT_MESH_BLOB_OP_CHUNK, BT_MESH_LEN_MIN(2), handle_chunk }, + { BT_MESH_BLOB_OP_INFO_GET, BT_MESH_LEN_EXACT(0), handle_info_get }, + BT_MESH_MODEL_OP_END, +}; + +static int blob_srv_init(const struct bt_mesh_model *mod) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + + srv->mod = mod; + srv->state.ttl = BT_MESH_TTL_DEFAULT; + srv->block.number = 0xffff; + srv->state.xfer.chunk_size = 0xffff; + k_work_init_delayable(&srv->rx_timeout, timeout); + k_work_init_delayable(&srv->pull.report, report_timeout); + + return 0; +} + +static int blob_srv_settings_set(const struct bt_mesh_model *mod, const char *name, + size_t len_rd, settings_read_cb read_cb, + void *cb_arg) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + ssize_t len; + + if (len_rd < offsetof(struct bt_mesh_blob_srv_state, blocks)) { + return -EINVAL; + } + + len = read_cb(cb_arg, &srv->state, sizeof(srv->state)); + if (len < 0) { + return len; + } + + srv->block.number = 0xffff; + srv->state.xfer.chunk_size = 0xffff; + + if (block_count_get(srv) > BT_MESH_BLOB_BLOCKS_MAX) { + LOG_WRN("Loaded block count too high (%u, max: %u)", + block_count_get(srv), BT_MESH_BLOB_BLOCKS_MAX); + return 0; + } + + /* If device restarted before it handled `XFER_START` server we restore state into + * BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START phase, so `XFER_START` can be accepted + * as it would before reboot + */ + if (srv->state.cli == BT_MESH_ADDR_UNASSIGNED) { + LOG_DBG("Transfer (id=%llu) waiting for start", srv->state.xfer.id); + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START); + } else { + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_SUSPENDED); + + LOG_DBG("Recovered transfer from 0x%04x (%llu)", srv->state.cli, + srv->state.xfer.id); + } + + return 0; +} + +static int blob_srv_start(const struct bt_mesh_model *mod) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + int err = -ENOTSUP; + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE) { + return 0; + } + + if (srv->cb && srv->cb->recover) { + srv->io = NULL; + err = srv->cb->recover(srv, &srv->state.xfer, &srv->io); + if (!err && srv->io) { + err = io_open(srv); + } + } + + if (err || !srv->io) { + LOG_WRN("Abandoning transfer."); + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_INACTIVE); + srv->state.xfer.mode = BT_MESH_BLOB_XFER_MODE_NONE; + srv->state.ttl = BT_MESH_TTL_DEFAULT; + erase_state(srv); + } + + return 0; +} + +static void blob_srv_reset(const struct bt_mesh_model *mod) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_INACTIVE); + srv->state.xfer.mode = BT_MESH_BLOB_XFER_MODE_NONE; + k_work_cancel_delayable(&srv->rx_timeout); + k_work_cancel_delayable(&srv->pull.report); + erase_state(srv); +} + +const struct bt_mesh_model_cb _bt_mesh_blob_srv_cb = { + .init = blob_srv_init, + .settings_set = blob_srv_settings_set, + .start = blob_srv_start, + .reset = blob_srv_reset, +}; + +int bt_mesh_blob_srv_recv(struct bt_mesh_blob_srv *srv, uint64_t id, + const struct bt_mesh_blob_io *io, uint8_t ttl, + uint16_t timeout_base) +{ + if (bt_mesh_blob_srv_is_busy(srv)) { + return -EBUSY; + } + + if (!io || !io->wr) { + return -EINVAL; + } + + srv->state.xfer.id = id; + srv->state.ttl = ttl; + srv->state.timeout_base = timeout_base; + srv->io = io; + srv->block.number = 0xffff; + srv->state.xfer.chunk_size = 0xffff; + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START); + store_state(srv); + + return 0; +} + +int bt_mesh_blob_srv_cancel(struct bt_mesh_blob_srv *srv) +{ + if (!bt_mesh_blob_srv_is_busy(srv)) { + return -EALREADY; + } + + cancel(srv); + + return 0; +} + +bool bt_mesh_blob_srv_is_busy(const struct bt_mesh_blob_srv *srv) +{ + return srv->phase != BT_MESH_BLOB_XFER_PHASE_INACTIVE && + srv->phase != BT_MESH_BLOB_XFER_PHASE_SUSPENDED && + srv->phase != BT_MESH_BLOB_XFER_PHASE_COMPLETE; +} + +uint8_t bt_mesh_blob_srv_progress(const struct bt_mesh_blob_srv *srv) +{ + uint32_t total; + uint32_t received; + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE || + srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START) { + return 0; + } + + total = block_count_get(srv); + + received = 0; + for (int i = 0; i < total; ++i) { + if (!atomic_test_bit(srv->state.blocks, i)) { + received++; + } + } + + return (100U * received) / total; +}