/* * Copyright (c) 2022 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include "blob.h" #include #include #include #include #include "utils.h" extern const struct shell *bt_mesh_shell_ctx_shell; /*************************************************************************************************** * Implementation of models' instances **************************************************************************************************/ static uint8_t blob_rx_sum; bool bt_mesh_shell_blob_valid; static const char *blob_data = "blob"; static int blob_io_open(const struct bt_mesh_blob_io *io, const struct bt_mesh_blob_xfer *xfer, enum bt_mesh_blob_io_mode mode) { blob_rx_sum = 0; bt_mesh_shell_blob_valid = true; return 0; } static int blob_chunk_wr(const struct bt_mesh_blob_io *io, const struct bt_mesh_blob_xfer *xfer, const struct bt_mesh_blob_block *block, const struct bt_mesh_blob_chunk *chunk) { int i; for (i = 0; i < chunk->size; ++i) { blob_rx_sum += chunk->data[i]; if (chunk->data[i] != blob_data[(i + chunk->offset) % sizeof(blob_data)]) { bt_mesh_shell_blob_valid = false; } } return 0; } static int blob_chunk_rd(const struct bt_mesh_blob_io *io, const struct bt_mesh_blob_xfer *xfer, const struct bt_mesh_blob_block *block, const struct bt_mesh_blob_chunk *chunk) { for (int i = 0; i < chunk->size; ++i) { chunk->data[i] = blob_data[(i + chunk->offset) % sizeof(blob_data)]; } return 0; } static const struct bt_mesh_blob_io dummy_blob_io = { .open = blob_io_open, .rd = blob_chunk_rd, .wr = blob_chunk_wr, }; const struct bt_mesh_blob_io *bt_mesh_shell_blob_io; #if defined(CONFIG_BT_MESH_SHELL_BLOB_CLI) static struct { struct bt_mesh_blob_cli_inputs inputs; struct bt_mesh_blob_target targets[32]; struct bt_mesh_blob_target_pull pull[32]; uint8_t target_count; struct bt_mesh_blob_xfer xfer; } blob_cli_xfer; static void blob_cli_lost_target(struct bt_mesh_blob_cli *cli, struct bt_mesh_blob_target *target, enum bt_mesh_blob_status reason) { shell_print(bt_mesh_shell_ctx_shell, "Mesh Blob: Lost target 0x%04x (reason: %u)", target->addr, reason); } static void blob_cli_caps(struct bt_mesh_blob_cli *cli, const struct bt_mesh_blob_cli_caps *caps) { static const char * const modes[] = { "none", "push", "pull", "all", }; if (!caps) { shell_print(bt_mesh_shell_ctx_shell, "None of the targets can be used for BLOB transfer"); return; } shell_print(bt_mesh_shell_ctx_shell, "Mesh BLOB: capabilities:"); shell_print(bt_mesh_shell_ctx_shell, "\tMax BLOB size: %u bytes", caps->max_size); shell_print(bt_mesh_shell_ctx_shell, "\tBlock size: %u-%u (%u-%u bytes)", caps->min_block_size_log, caps->max_block_size_log, 1 << caps->min_block_size_log, 1 << caps->max_block_size_log); shell_print(bt_mesh_shell_ctx_shell, "\tMax chunks: %u", caps->max_chunks); shell_print(bt_mesh_shell_ctx_shell, "\tChunk size: %u", caps->max_chunk_size); shell_print(bt_mesh_shell_ctx_shell, "\tMTU size: %u", caps->mtu_size); shell_print(bt_mesh_shell_ctx_shell, "\tModes: %s", modes[caps->modes]); } static void blob_cli_end(struct bt_mesh_blob_cli *cli, const struct bt_mesh_blob_xfer *xfer, bool success) { if (success) { shell_print(bt_mesh_shell_ctx_shell, "Mesh BLOB transfer complete."); } else { shell_print(bt_mesh_shell_ctx_shell, "Mesh BLOB transfer failed."); } } static uint8_t get_progress(const struct bt_mesh_blob_xfer_info *info) { uint8_t total_blocks; uint8_t blocks_not_rxed = 0; uint8_t blocks_not_rxed_size; int i; total_blocks = DIV_ROUND_UP(info->size, 1U << info->block_size_log); blocks_not_rxed_size = DIV_ROUND_UP(total_blocks, 8); for (i = 0; i < blocks_not_rxed_size; i++) { blocks_not_rxed += info->missing_blocks[i % 8] & (1 << (i % 8)); } return (total_blocks - blocks_not_rxed) / total_blocks; } static void xfer_progress(struct bt_mesh_blob_cli *cli, struct bt_mesh_blob_target *target, const struct bt_mesh_blob_xfer_info *info) { uint8_t progress = get_progress(info); shell_print(bt_mesh_shell_ctx_shell, "BLOB transfer progress received from target 0x%04x:\n" "\tphase: %d\n" "\tprogress: %u%%", target->addr, info->phase, progress); } static void xfer_progress_complete(struct bt_mesh_blob_cli *cli) { shell_print(bt_mesh_shell_ctx_shell, "Determine BLOB transfer progress procedure complete"); } static const struct bt_mesh_blob_cli_cb blob_cli_handlers = { .lost_target = blob_cli_lost_target, .caps = blob_cli_caps, .end = blob_cli_end, .xfer_progress = xfer_progress, .xfer_progress_complete = xfer_progress_complete, }; struct bt_mesh_blob_cli bt_mesh_shell_blob_cli = { .cb = &blob_cli_handlers }; #endif /* CONFIG_BT_MESH_SHELL_BLOB_CLI */ #if defined(CONFIG_BT_MESH_SHELL_BLOB_SRV) static int64_t blob_time; static int blob_srv_start(struct bt_mesh_blob_srv *srv, struct bt_mesh_msg_ctx *ctx, struct bt_mesh_blob_xfer *xfer) { shell_print(bt_mesh_shell_ctx_shell, "BLOB start"); blob_time = k_uptime_get(); return 0; } static void blob_srv_end(struct bt_mesh_blob_srv *srv, uint64_t id, bool success) { if (success) { int64_t duration = k_uptime_delta(&blob_time); shell_print(bt_mesh_shell_ctx_shell, "BLOB completed in %u.%03u s", (uint32_t)(duration / MSEC_PER_SEC), (uint32_t)(duration % MSEC_PER_SEC)); } else { shell_print(bt_mesh_shell_ctx_shell, "BLOB cancelled"); } } static const struct bt_mesh_blob_srv_cb blob_srv_cb = { .start = blob_srv_start, .end = blob_srv_end, }; struct bt_mesh_blob_srv bt_mesh_shell_blob_srv = { .cb = &blob_srv_cb }; #endif /* CONFIG_BT_MESH_SHELL_BLOB_SRV */ void bt_mesh_shell_blob_cmds_init(void) { bt_mesh_shell_blob_io = &dummy_blob_io; } /*************************************************************************************************** * Shell Commands **************************************************************************************************/ #if defined(CONFIG_BT_MESH_SHELL_BLOB_IO_FLASH) static struct bt_mesh_blob_io_flash blob_flash_stream; static int cmd_flash_stream_set(const struct shell *sh, size_t argc, char *argv[]) { uint8_t area_id; uint32_t offset = 0; int err = 0; if (argc < 2) { return -EINVAL; } area_id = shell_strtoul(argv[1], 0, &err); if (argc >= 3) { offset = shell_strtoul(argv[2], 0, &err); } if (err) { shell_warn(sh, "Unable to parse input string argument"); return err; } err = bt_mesh_blob_io_flash_init(&blob_flash_stream, area_id, offset); if (err) { shell_error(sh, "Failed to init BLOB IO Flash module: %d\n", err); } bt_mesh_shell_blob_io = &blob_flash_stream.io; shell_print(sh, "Flash stream is initialized with area %u, offset: %u", area_id, offset); return 0; } static int cmd_flash_stream_unset(const struct shell *sh, size_t argc, char *argv[]) { bt_mesh_shell_blob_io = &dummy_blob_io; return 0; } #endif /* CONFIG_BT_MESH_SHELL_BLOB_IO_FLASH */ #if defined(CONFIG_BT_MESH_SHELL_BLOB_CLI) static const struct bt_mesh_model *mod_cli; static void blob_cli_inputs_prepare(uint16_t group) { int i; blob_cli_xfer.inputs.ttl = BT_MESH_TTL_DEFAULT; blob_cli_xfer.inputs.group = group; blob_cli_xfer.inputs.app_idx = bt_mesh_shell_target_ctx.app_idx; sys_slist_init(&blob_cli_xfer.inputs.targets); for (i = 0; i < blob_cli_xfer.target_count; ++i) { /* Reset target context. */ uint16_t addr = blob_cli_xfer.targets[i].addr; memset(&blob_cli_xfer.targets[i], 0, sizeof(struct bt_mesh_blob_target)); memset(&blob_cli_xfer.pull[i], 0, sizeof(struct bt_mesh_blob_target_pull)); blob_cli_xfer.targets[i].addr = addr; blob_cli_xfer.targets[i].pull = &blob_cli_xfer.pull[i]; sys_slist_append(&blob_cli_xfer.inputs.targets, &blob_cli_xfer.targets[i].n); } } static int cmd_tx(const struct shell *sh, size_t argc, char *argv[]) { uint16_t group; int err = 0; if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) { return -ENODEV; } blob_cli_xfer.xfer.id = shell_strtoul(argv[1], 0, &err); blob_cli_xfer.xfer.size = shell_strtoul(argv[2], 0, &err); blob_cli_xfer.xfer.block_size_log = shell_strtoul(argv[3], 0, &err); blob_cli_xfer.xfer.chunk_size = shell_strtoul(argv[4], 0, &err); if (argc >= 6) { group = shell_strtoul(argv[5], 0, &err); } else { group = BT_MESH_ADDR_UNASSIGNED; } if (argc < 7 || !strcmp(argv[6], "push")) { blob_cli_xfer.xfer.mode = BT_MESH_BLOB_XFER_MODE_PUSH; } else if (!strcmp(argv[6], "pull")) { blob_cli_xfer.xfer.mode = BT_MESH_BLOB_XFER_MODE_PULL; } else { shell_print(sh, "Mode must be either push or pull"); return -EINVAL; } if (argc >= 8) { blob_cli_xfer.inputs.timeout_base = shell_strtoul(argv[7], 0, &err); } else { blob_cli_xfer.inputs.timeout_base = 0; } if (err) { shell_warn(sh, "Unable to parse input string argument"); return err; } if (!blob_cli_xfer.target_count) { shell_print(sh, "Failed: No targets"); return 0; } blob_cli_inputs_prepare(group); shell_print(sh, "Sending transfer 0x%x (mode: %s, %u bytes) to 0x%04x", (uint32_t)blob_cli_xfer.xfer.id, blob_cli_xfer.xfer.mode == BT_MESH_BLOB_XFER_MODE_PUSH ? "push" : "pull", blob_cli_xfer.xfer.size, group); err = bt_mesh_blob_cli_send((struct bt_mesh_blob_cli *)mod_cli->rt->user_data, &blob_cli_xfer.inputs, &blob_cli_xfer.xfer, bt_mesh_shell_blob_io); if (err) { shell_print(sh, "BLOB transfer TX failed (err: %d)", err); } return 0; } static int cmd_target(const struct shell *sh, size_t argc, char *argv[]) { struct bt_mesh_blob_target *t; int err = 0; if (blob_cli_xfer.target_count == ARRAY_SIZE(blob_cli_xfer.targets)) { shell_print(sh, "No more room"); return 0; } t = &blob_cli_xfer.targets[blob_cli_xfer.target_count]; t->addr = shell_strtoul(argv[1], 0, &err); if (err) { shell_warn(sh, "Unable to parse input string argument"); return err; } shell_print(sh, "Added target 0x%04x", t->addr); blob_cli_xfer.target_count++; return 0; } static int cmd_caps(const struct shell *sh, size_t argc, char *argv[]) { uint16_t group; int err = 0; if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) { return -ENODEV; } shell_print(sh, "Retrieving transfer capabilities..."); if (argc > 1) { group = shell_strtoul(argv[1], 0, &err); } else { group = BT_MESH_ADDR_UNASSIGNED; } if (argc > 2) { blob_cli_xfer.inputs.timeout_base = shell_strtoul(argv[2], 0, &err); } else { blob_cli_xfer.inputs.timeout_base = 0; } if (err) { shell_warn(sh, "Unable to parse input string argument"); return err; } if (!blob_cli_xfer.target_count) { shell_print(sh, "Failed: No targets"); return 0; } blob_cli_inputs_prepare(group); err = bt_mesh_blob_cli_caps_get((struct bt_mesh_blob_cli *)mod_cli->rt->user_data, &blob_cli_xfer.inputs); if (err) { shell_print(sh, "Boundary check start failed (err: %d)", err); } return 0; } static int cmd_tx_cancel(const struct shell *sh, size_t argc, char *argv[]) { if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) { return -ENODEV; } shell_print(sh, "Cancelling transfer"); bt_mesh_blob_cli_cancel((struct bt_mesh_blob_cli *)mod_cli->rt->user_data); return 0; } static int cmd_tx_get(const struct shell *sh, size_t argc, char *argv[]) { uint16_t group; int err; if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) { return -ENODEV; } if (argc > 1) { group = shell_strtoul(argv[1], 0, &err); } else { group = BT_MESH_ADDR_UNASSIGNED; } if (!blob_cli_xfer.target_count) { shell_print(sh, "Failed: No targets"); return -EINVAL; } blob_cli_inputs_prepare(group); err = bt_mesh_blob_cli_xfer_progress_get((struct bt_mesh_blob_cli *)mod_cli->rt->user_data, &blob_cli_xfer.inputs); if (err) { shell_print(sh, "ERR %d", err); return err; } return 0; } static int cmd_tx_suspend(const struct shell *sh, size_t argc, char *argv[]) { if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) { return -ENODEV; } shell_print(sh, "Suspending transfer"); bt_mesh_blob_cli_suspend((struct bt_mesh_blob_cli *)mod_cli->rt->user_data); return 0; } static int cmd_tx_resume(const struct shell *sh, size_t argc, char *argv[]) { if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) { return -ENODEV; } shell_print(sh, "Resuming transfer"); bt_mesh_blob_cli_resume((struct bt_mesh_blob_cli *)mod_cli->rt->user_data); return 0; } #endif /* CONFIG_BT_MESH_SHELL_BLOB_CLI */ #if defined(CONFIG_BT_MESH_SHELL_BLOB_SRV) static const struct bt_mesh_model *mod_srv; static int cmd_rx(const struct shell *sh, size_t argc, char *argv[]) { uint16_t timeout_base; uint32_t id; int err = 0; if (!mod_srv && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_SRV, &mod_srv)) { return -ENODEV; } id = shell_strtoul(argv[1], 0, &err); blob_rx_sum = 0; if (argc > 2) { timeout_base = shell_strtoul(argv[2], 0, &err); } else { timeout_base = 0U; } if (err) { shell_warn(sh, "Unable to parse input string argument"); return err; } shell_print(sh, "Receive BLOB 0x%x", id); err = bt_mesh_blob_srv_recv((struct bt_mesh_blob_srv *)mod_srv->rt->user_data, id, bt_mesh_shell_blob_io, BT_MESH_TTL_MAX, timeout_base); if (err) { shell_print(sh, "BLOB RX setup failed (%d)", err); } return 0; } static int cmd_rx_cancel(const struct shell *sh, size_t argc, char *argv[]) { int err; if (!mod_srv && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_SRV, &mod_srv)) { return -ENODEV; } shell_print(sh, "Cancelling BLOB rx"); err = bt_mesh_blob_srv_cancel((struct bt_mesh_blob_srv *)mod_srv->rt->user_data); if (err) { shell_print(sh, "BLOB cancel failed (%d)", err); } return 0; } #endif /* CONFIG_BT_MESH_SHELL_BLOB_SRV */ #if defined(CONFIG_BT_MESH_SHELL_BLOB_CLI) BT_MESH_SHELL_MDL_INSTANCE_CMDS(cli_instance_cmds, BT_MESH_MODEL_ID_BLOB_CLI, mod_cli); #endif #if defined(CONFIG_BT_MESH_SHELL_BLOB_SRV) BT_MESH_SHELL_MDL_INSTANCE_CMDS(srv_instance_cmds, BT_MESH_MODEL_ID_BLOB_SRV, mod_srv); #endif #if defined(CONFIG_BT_MESH_SHELL_BLOB_CLI) SHELL_STATIC_SUBCMD_SET_CREATE( blob_cli_cmds, /* BLOB Client Model Operations */ SHELL_CMD_ARG(target, NULL, "", cmd_target, 2, 0), SHELL_CMD_ARG(caps, NULL, "[ []]", cmd_caps, 1, 2), SHELL_CMD_ARG(tx, NULL, " " " [ [ " "[]]]", cmd_tx, 5, 3), SHELL_CMD_ARG(tx-cancel, NULL, NULL, cmd_tx_cancel, 1, 0), SHELL_CMD_ARG(tx-get, NULL, "[Group]", cmd_tx_get, 1, 1), SHELL_CMD_ARG(tx-suspend, NULL, NULL, cmd_tx_suspend, 1, 0), SHELL_CMD_ARG(tx-resume, NULL, NULL, cmd_tx_resume, 1, 0), SHELL_CMD(instance, &cli_instance_cmds, "Instance commands", bt_mesh_shell_mdl_cmds_help), SHELL_SUBCMD_SET_END); #endif #if defined(CONFIG_BT_MESH_SHELL_BLOB_SRV) SHELL_STATIC_SUBCMD_SET_CREATE( blob_srv_cmds, /* BLOB Server Model Operations */ SHELL_CMD_ARG(rx, NULL, " []", cmd_rx, 2, 1), SHELL_CMD_ARG(rx-cancel, NULL, NULL, cmd_rx_cancel, 1, 0), SHELL_CMD(instance, &srv_instance_cmds, "Instance commands", bt_mesh_shell_mdl_cmds_help), SHELL_SUBCMD_SET_END); #endif SHELL_STATIC_SUBCMD_SET_CREATE( blob_cmds, #if defined(CONFIG_BT_MESH_SHELL_BLOB_IO_FLASH) SHELL_CMD_ARG(flash-stream-set, NULL, " []", cmd_flash_stream_set, 2, 1), SHELL_CMD_ARG(flash-stream-unset, NULL, NULL, cmd_flash_stream_unset, 1, 0), #endif #if defined(CONFIG_BT_MESH_SHELL_BLOB_CLI) SHELL_CMD(cli, &blob_cli_cmds, "BLOB Cli commands", bt_mesh_shell_mdl_cmds_help), #endif #if defined(CONFIG_BT_MESH_SHELL_BLOB_SRV) SHELL_CMD(srv, &blob_srv_cmds, "BLOB Srv commands", bt_mesh_shell_mdl_cmds_help), #endif SHELL_SUBCMD_SET_END); SHELL_SUBCMD_ADD((mesh, models), blob, &blob_cmds, "BLOB models commands", bt_mesh_shell_mdl_cmds_help, 1, 1);