Updates Bluetooth mesh specification references: - Change "Profile" to "Protocol" since the main specification has changed its name. - Update/align formating of specification references. This will make it easier to find spec references in the future. - Change some section references to point to the correct section of the newest version of the specification (v1.1). Signed-off-by: Anders Storrø <anders.storro@nordicsemi.no>
1653 lines
45 KiB
C
1653 lines
45 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 "mesh.h"
|
|
#include "blob.h"
|
|
#include "net.h"
|
|
#include "transport.h"
|
|
|
|
#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL
|
|
#include <zephyr/logging/log.h>
|
|
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)
|
|
|
|
#define CHUNK_SIZE_MAX BLOB_CHUNK_SIZE_MAX(BT_MESH_TX_SDU_MAX)
|
|
|
|
/* 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 bt_mesh_blob_cli *cli =
|
|
CONTAINER_OF(work, 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_submit(&cli->tx.complete);
|
|
}
|
|
|
|
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 proceedes 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 proceedes 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 adressing, 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,
|
|
};
|
|
|
|
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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_blob_cli *cli = mod->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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_blob_cli *cli = mod->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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_blob_cli *cli = mod->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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_blob_cli *cli = mod->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(struct bt_mesh_model *mod)
|
|
{
|
|
struct bt_mesh_blob_cli *cli = mod->user_data;
|
|
|
|
cli->mod = mod;
|
|
|
|
cli->tx.cli_timestamp = 0ll;
|
|
k_work_init_delayable(&cli->tx.retry, retry_timeout);
|
|
k_work_init(&cli->tx.complete, tx_complete);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void blob_cli_reset(struct bt_mesh_model *mod)
|
|
{
|
|
struct bt_mesh_blob_cli *cli = mod->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 = CHUNK_SIZE_MAX;
|
|
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 > CHUNK_SIZE_MAX) {
|
|
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;
|
|
}
|