Avoid divizion by zero when chunk_size is zero. Coverity-CID: 316406, GitHub issue #58531 Signed-off-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no>
972 lines
26 KiB
C
972 lines
26 KiB
C
/*
|
|
* Copyright (c) 2020 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include <string.h>
|
|
#include <zephyr/bluetooth/mesh.h>
|
|
#include <common/bt_str.h>
|
|
#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 <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(bt_mesh_blob_srv);
|
|
|
|
#define CHUNK_SIZE_MAX BLOB_CHUNK_SIZE_MAX(BT_MESH_RX_SDU_MAX)
|
|
#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)),
|
|
CHUNK_SIZE_MAX);
|
|
}
|
|
|
|
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;
|
|
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 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 {
|
|
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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_blob_srv *srv = mod->user_data;
|
|
|
|
LOG_DBG("");
|
|
xfer_status_rsp(srv, ctx, BT_MESH_BLOB_SUCCESS);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_xfer_start(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_blob_srv *srv = mod->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(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->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(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->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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_blob_srv *srv = mod->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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_blob_srv *srv = mod->user_data;
|
|
struct bt_mesh_blob_chunk chunk;
|
|
size_t expected_size = 0;
|
|
uint16_t idx;
|
|
int i, 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);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(srv->state.blocks); ++i) {
|
|
if (!srv->state.blocks[i]) {
|
|
continue;
|
|
}
|
|
|
|
phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK);
|
|
store_state(srv);
|
|
return 0;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_info_get(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_blob_srv *srv = mod->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, CHUNK_SIZE_MAX);
|
|
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(struct bt_mesh_model *mod)
|
|
{
|
|
struct bt_mesh_blob_srv *srv = mod->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(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->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(struct bt_mesh_model *mod)
|
|
{
|
|
struct bt_mesh_blob_srv *srv = mod->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(struct bt_mesh_model *mod)
|
|
{
|
|
struct bt_mesh_blob_srv *srv = mod->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;
|
|
}
|