blob: 282a40074c9d9bdc18ff6b8d7324d2b568ee9e5c [file] [log] [blame]
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/mesh.h>
#include "dfu.h"
#include "blob.h"
#include "access.h"
#define LOG_LEVEL CONFIG_BT_MESH_DFU_LOG_LEVEL
#include <zephyr/logging/log.h>
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) {
LOG_WRN("Apply response failed, wait for retry");
return;
}
LOG_DBG("");
if (!srv->cb->apply || srv->update.idx == UPDATE_IDX_NONE) {
srv->update.phase = BT_MESH_DFU_PHASE_IDLE;
store_state(srv);
return;
}
err = srv->cb->apply(srv, &srv->imgs[srv->update.idx]);
if (err) {
srv->update.phase = BT_MESH_DFU_PHASE_IDLE;
}
store_state(srv);
}
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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
struct bt_mesh_dfu_srv *srv = mod->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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
struct bt_mesh_dfu_srv *srv = mod->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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
struct bt_mesh_dfu_srv *srv = mod->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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
struct bt_mesh_dfu_srv *srv = mod->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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
struct bt_mesh_dfu_srv *srv = mod->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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
struct bt_mesh_dfu_srv *srv = mod->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;
store_state(srv);
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(struct bt_mesh_model *mod)
{
struct bt_mesh_dfu_srv *srv = mod->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(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->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(struct bt_mesh_model *mod)
{
struct bt_mesh_dfu_srv *srv = mod->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;
}