/* * 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) { int err; 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; } const struct bt_mesh_model *blob_srv = bt_mesh_model_find(bt_mesh_model_elem(mod), BT_MESH_MODEL_ID_BLOB_SRV); if (blob_srv == NULL) { LOG_ERR("Missing BLOB Srv."); return -EINVAL; } err = bt_mesh_model_extend(mod, srv->blob.mod); if (err) { return err; } 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; }