| /* |
| * 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 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, 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(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; |
| } |