1 /*
2  * Copyright (c) 2022 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include "blob.h"
8 
9 #include <stdlib.h>
10 #include <zephyr/shell/shell.h>
11 #include <zephyr/bluetooth/mesh.h>
12 #include <zephyr/bluetooth/mesh/shell.h>
13 
14 #include "common/bt_shell_private.h"
15 #include "utils.h"
16 
17 /***************************************************************************************************
18  * Implementation of models' instances
19  **************************************************************************************************/
20 
21 static uint8_t blob_rx_sum;
22 bool bt_mesh_shell_blob_valid;
23 static const char *blob_data = "blob";
24 
blob_io_open(const struct bt_mesh_blob_io * io,const struct bt_mesh_blob_xfer * xfer,enum bt_mesh_blob_io_mode mode)25 static int blob_io_open(const struct bt_mesh_blob_io *io,
26 		    const struct bt_mesh_blob_xfer *xfer,
27 		    enum bt_mesh_blob_io_mode mode)
28 {
29 	blob_rx_sum = 0;
30 	bt_mesh_shell_blob_valid = true;
31 	return 0;
32 }
33 
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)34 static int blob_chunk_wr(const struct bt_mesh_blob_io *io,
35 			 const struct bt_mesh_blob_xfer *xfer,
36 			 const struct bt_mesh_blob_block *block,
37 			 const struct bt_mesh_blob_chunk *chunk)
38 {
39 	int i;
40 
41 	for (i = 0; i < chunk->size; ++i) {
42 		blob_rx_sum += chunk->data[i];
43 		if (chunk->data[i] !=
44 		    blob_data[(i + chunk->offset) % sizeof(blob_data)]) {
45 			bt_mesh_shell_blob_valid = false;
46 		}
47 	}
48 
49 	return 0;
50 }
51 
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)52 static int blob_chunk_rd(const struct bt_mesh_blob_io *io,
53 			 const struct bt_mesh_blob_xfer *xfer,
54 			 const struct bt_mesh_blob_block *block,
55 			 const struct bt_mesh_blob_chunk *chunk)
56 {
57 	for (int i = 0; i < chunk->size; ++i) {
58 		chunk->data[i] =
59 			blob_data[(i + chunk->offset) % sizeof(blob_data)];
60 	}
61 
62 	return 0;
63 }
64 
65 static const struct bt_mesh_blob_io dummy_blob_io = {
66 	.open = blob_io_open,
67 	.rd = blob_chunk_rd,
68 	.wr = blob_chunk_wr,
69 };
70 
71 const struct bt_mesh_blob_io *bt_mesh_shell_blob_io;
72 
73 #if defined(CONFIG_BT_MESH_SHELL_BLOB_CLI)
74 
75 static struct {
76 	struct bt_mesh_blob_cli_inputs inputs;
77 	struct bt_mesh_blob_target targets[32];
78 	struct bt_mesh_blob_target_pull pull[32];
79 	uint8_t target_count;
80 	struct bt_mesh_blob_xfer xfer;
81 } blob_cli_xfer;
82 
blob_cli_lost_target(struct bt_mesh_blob_cli * cli,struct bt_mesh_blob_target * target,enum bt_mesh_blob_status reason)83 static void blob_cli_lost_target(struct bt_mesh_blob_cli *cli,
84 				 struct bt_mesh_blob_target *target,
85 				 enum bt_mesh_blob_status reason)
86 {
87 	bt_shell_print("Mesh Blob: Lost target 0x%04x (reason: %u)", target->addr, reason);
88 }
89 
blob_cli_caps(struct bt_mesh_blob_cli * cli,const struct bt_mesh_blob_cli_caps * caps)90 static void blob_cli_caps(struct bt_mesh_blob_cli *cli,
91 			  const struct bt_mesh_blob_cli_caps *caps)
92 {
93 	static const char * const modes[] = {
94 		"none",
95 		"push",
96 		"pull",
97 		"all",
98 	};
99 
100 	if (!caps) {
101 		bt_shell_print("None of the targets can be used for BLOB transfer");
102 		return;
103 	}
104 
105 	bt_shell_print("Mesh BLOB: capabilities:");
106 	bt_shell_print("\tMax BLOB size: %u bytes", caps->max_size);
107 	bt_shell_print("\tBlock size: %u-%u (%u-%u bytes)",
108 		       caps->min_block_size_log, caps->max_block_size_log,
109 		       1 << caps->min_block_size_log,
110 		       1 << caps->max_block_size_log);
111 	bt_shell_print("\tMax chunks: %u", caps->max_chunks);
112 	bt_shell_print("\tChunk size: %u", caps->max_chunk_size);
113 	bt_shell_print("\tMTU size: %u", caps->mtu_size);
114 	bt_shell_print("\tModes: %s", modes[caps->modes]);
115 }
116 
blob_cli_end(struct bt_mesh_blob_cli * cli,const struct bt_mesh_blob_xfer * xfer,bool success)117 static void blob_cli_end(struct bt_mesh_blob_cli *cli,
118 			 const struct bt_mesh_blob_xfer *xfer, bool success)
119 {
120 	if (success) {
121 		bt_shell_print("Mesh BLOB transfer complete.");
122 	} else {
123 		bt_shell_print("Mesh BLOB transfer failed.");
124 	}
125 }
126 
get_progress(const struct bt_mesh_blob_xfer_info * info)127 static uint8_t get_progress(const struct bt_mesh_blob_xfer_info *info)
128 {
129 	uint8_t total_blocks;
130 	uint8_t blocks_not_rxed = 0;
131 	uint8_t blocks_not_rxed_size;
132 	int i;
133 
134 	total_blocks = DIV_ROUND_UP(info->size, 1U << info->block_size_log);
135 
136 	blocks_not_rxed_size = DIV_ROUND_UP(total_blocks, 8);
137 
138 	for (i = 0; i < blocks_not_rxed_size; i++) {
139 		blocks_not_rxed += info->missing_blocks[i % 8] & (1 << (i % 8));
140 	}
141 
142 	return (total_blocks - blocks_not_rxed) / total_blocks;
143 }
144 
xfer_progress(struct bt_mesh_blob_cli * cli,struct bt_mesh_blob_target * target,const struct bt_mesh_blob_xfer_info * info)145 static void xfer_progress(struct bt_mesh_blob_cli *cli,
146 			  struct bt_mesh_blob_target *target,
147 			  const struct bt_mesh_blob_xfer_info *info)
148 {
149 	uint8_t progress = get_progress(info);
150 
151 	bt_shell_print("BLOB transfer progress received from target 0x%04x:\n"
152 		       "\tphase: %d\n"
153 		       "\tprogress: %u%%",
154 		       target->addr, info->phase, progress);
155 }
156 
xfer_progress_complete(struct bt_mesh_blob_cli * cli)157 static void xfer_progress_complete(struct bt_mesh_blob_cli *cli)
158 {
159 	bt_shell_print("Determine BLOB transfer progress procedure complete");
160 }
161 
162 static const struct bt_mesh_blob_cli_cb blob_cli_handlers = {
163 	.lost_target = blob_cli_lost_target,
164 	.caps = blob_cli_caps,
165 	.end = blob_cli_end,
166 	.xfer_progress = xfer_progress,
167 	.xfer_progress_complete = xfer_progress_complete,
168 };
169 
170 struct bt_mesh_blob_cli bt_mesh_shell_blob_cli = {
171 	.cb = &blob_cli_handlers
172 };
173 
174 #endif /* CONFIG_BT_MESH_SHELL_BLOB_CLI */
175 
176 #if defined(CONFIG_BT_MESH_SHELL_BLOB_SRV)
177 
178 static int64_t blob_time;
179 
blob_srv_start(struct bt_mesh_blob_srv * srv,struct bt_mesh_msg_ctx * ctx,struct bt_mesh_blob_xfer * xfer)180 static int blob_srv_start(struct bt_mesh_blob_srv *srv,
181 			  struct bt_mesh_msg_ctx *ctx,
182 			  struct bt_mesh_blob_xfer *xfer)
183 {
184 	bt_shell_print("BLOB start");
185 	blob_time = k_uptime_get();
186 	return 0;
187 }
188 
blob_srv_end(struct bt_mesh_blob_srv * srv,uint64_t id,bool success)189 static void blob_srv_end(struct bt_mesh_blob_srv *srv, uint64_t id,
190 			 bool success)
191 {
192 	if (success) {
193 		int64_t duration = k_uptime_delta(&blob_time);
194 
195 		bt_shell_print("BLOB completed in %u.%03u s",
196 			       (uint32_t)(duration / MSEC_PER_SEC),
197 			       (uint32_t)(duration % MSEC_PER_SEC));
198 	} else {
199 		bt_shell_print("BLOB cancelled");
200 	}
201 }
202 
203 static const struct bt_mesh_blob_srv_cb blob_srv_cb = {
204 	.start = blob_srv_start,
205 	.end = blob_srv_end,
206 };
207 
208 struct bt_mesh_blob_srv bt_mesh_shell_blob_srv = {
209 	.cb = &blob_srv_cb
210 };
211 
212 #endif /* CONFIG_BT_MESH_SHELL_BLOB_SRV */
213 
bt_mesh_shell_blob_cmds_init(void)214 void bt_mesh_shell_blob_cmds_init(void)
215 {
216 	bt_mesh_shell_blob_io = &dummy_blob_io;
217 }
218 
219 /***************************************************************************************************
220  * Shell Commands
221  **************************************************************************************************/
222 
223 #if defined(CONFIG_BT_MESH_SHELL_BLOB_IO_FLASH)
224 
225 static struct bt_mesh_blob_io_flash blob_flash_stream;
226 
cmd_flash_stream_set(const struct shell * sh,size_t argc,char * argv[])227 static int cmd_flash_stream_set(const struct shell *sh, size_t argc, char *argv[])
228 {
229 	uint8_t area_id;
230 	uint32_t offset = 0;
231 	int err = 0;
232 
233 	if (argc < 2) {
234 		return -EINVAL;
235 	}
236 
237 	area_id = shell_strtoul(argv[1], 0, &err);
238 
239 	if (argc >= 3) {
240 		offset = shell_strtoul(argv[2], 0, &err);
241 	}
242 
243 	if (err) {
244 		shell_warn(sh, "Unable to parse input string argument");
245 		return err;
246 	}
247 
248 	err = bt_mesh_blob_io_flash_init(&blob_flash_stream, area_id, offset);
249 	if (err) {
250 		shell_error(sh, "Failed to init BLOB IO Flash module: %d\n", err);
251 	}
252 
253 	bt_mesh_shell_blob_io = &blob_flash_stream.io;
254 
255 	shell_print(sh, "Flash stream is initialized with area %u, offset: %u", area_id, offset);
256 
257 	return 0;
258 }
259 
cmd_flash_stream_unset(const struct shell * sh,size_t argc,char * argv[])260 static int cmd_flash_stream_unset(const struct shell *sh, size_t argc, char *argv[])
261 {
262 	bt_mesh_shell_blob_io = &dummy_blob_io;
263 	return 0;
264 }
265 
266 #endif /* CONFIG_BT_MESH_SHELL_BLOB_IO_FLASH */
267 
268 #if defined(CONFIG_BT_MESH_SHELL_BLOB_CLI)
269 
270 static const struct bt_mesh_model *mod_cli;
271 
blob_cli_inputs_prepare(uint16_t group)272 static void blob_cli_inputs_prepare(uint16_t group)
273 {
274 	int i;
275 
276 	blob_cli_xfer.inputs.ttl = BT_MESH_TTL_DEFAULT;
277 	blob_cli_xfer.inputs.group = group;
278 	blob_cli_xfer.inputs.app_idx = bt_mesh_shell_target_ctx.app_idx;
279 	sys_slist_init(&blob_cli_xfer.inputs.targets);
280 
281 	for (i = 0; i < blob_cli_xfer.target_count; ++i) {
282 		/* Reset target context. */
283 		uint16_t addr = blob_cli_xfer.targets[i].addr;
284 
285 		memset(&blob_cli_xfer.targets[i], 0, sizeof(struct bt_mesh_blob_target));
286 		memset(&blob_cli_xfer.pull[i], 0, sizeof(struct bt_mesh_blob_target_pull));
287 		blob_cli_xfer.targets[i].addr = addr;
288 		blob_cli_xfer.targets[i].pull = &blob_cli_xfer.pull[i];
289 
290 		sys_slist_append(&blob_cli_xfer.inputs.targets,
291 				 &blob_cli_xfer.targets[i].n);
292 	}
293 }
294 
cmd_tx(const struct shell * sh,size_t argc,char * argv[])295 static int cmd_tx(const struct shell *sh, size_t argc, char *argv[])
296 {
297 	uint16_t group;
298 	int err = 0;
299 
300 	if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) {
301 		return -ENODEV;
302 	}
303 
304 	blob_cli_xfer.xfer.id = shell_strtoul(argv[1], 0, &err);
305 	blob_cli_xfer.xfer.size = shell_strtoul(argv[2], 0, &err);
306 	blob_cli_xfer.xfer.block_size_log = shell_strtoul(argv[3], 0, &err);
307 	blob_cli_xfer.xfer.chunk_size = shell_strtoul(argv[4], 0, &err);
308 
309 	if (argc >= 6) {
310 		group = shell_strtoul(argv[5], 0, &err);
311 	} else {
312 		group = BT_MESH_ADDR_UNASSIGNED;
313 	}
314 
315 	if (argc < 7 || !strcmp(argv[6], "push")) {
316 		blob_cli_xfer.xfer.mode = BT_MESH_BLOB_XFER_MODE_PUSH;
317 	} else if (!strcmp(argv[6], "pull")) {
318 		blob_cli_xfer.xfer.mode = BT_MESH_BLOB_XFER_MODE_PULL;
319 	} else {
320 		shell_print(sh, "Mode must be either push or pull");
321 		return -EINVAL;
322 	}
323 
324 	if (argc >= 8) {
325 		blob_cli_xfer.inputs.timeout_base = shell_strtoul(argv[7], 0, &err);
326 	} else {
327 		blob_cli_xfer.inputs.timeout_base = 0;
328 	}
329 
330 	if (err) {
331 		shell_warn(sh, "Unable to parse input string argument");
332 		return err;
333 	}
334 
335 	if (!blob_cli_xfer.target_count) {
336 		shell_print(sh, "Failed: No targets");
337 		return 0;
338 	}
339 
340 	blob_cli_inputs_prepare(group);
341 
342 	shell_print(sh,
343 		    "Sending transfer 0x%x (mode: %s, %u bytes) to 0x%04x",
344 		    (uint32_t)blob_cli_xfer.xfer.id,
345 		    blob_cli_xfer.xfer.mode == BT_MESH_BLOB_XFER_MODE_PUSH ?
346 			    "push" :
347 			    "pull",
348 		    blob_cli_xfer.xfer.size, group);
349 
350 	err = bt_mesh_blob_cli_send((struct bt_mesh_blob_cli *)mod_cli->rt->user_data,
351 				    &blob_cli_xfer.inputs,
352 				    &blob_cli_xfer.xfer, bt_mesh_shell_blob_io);
353 	if (err) {
354 		shell_print(sh, "BLOB transfer TX failed (err: %d)", err);
355 	}
356 
357 	return 0;
358 }
359 
cmd_target(const struct shell * sh,size_t argc,char * argv[])360 static int cmd_target(const struct shell *sh, size_t argc, char *argv[])
361 {
362 	struct bt_mesh_blob_target *t;
363 	int err = 0;
364 
365 	if (blob_cli_xfer.target_count ==
366 	    ARRAY_SIZE(blob_cli_xfer.targets)) {
367 		shell_print(sh, "No more room");
368 		return 0;
369 	}
370 
371 	t = &blob_cli_xfer.targets[blob_cli_xfer.target_count];
372 	t->addr = shell_strtoul(argv[1], 0, &err);
373 
374 	if (err) {
375 		shell_warn(sh, "Unable to parse input string argument");
376 		return err;
377 	}
378 
379 	shell_print(sh, "Added target 0x%04x", t->addr);
380 
381 	blob_cli_xfer.target_count++;
382 	return 0;
383 }
384 
cmd_caps(const struct shell * sh,size_t argc,char * argv[])385 static int cmd_caps(const struct shell *sh, size_t argc, char *argv[])
386 {
387 	uint16_t group;
388 	int err = 0;
389 
390 	if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) {
391 		return -ENODEV;
392 	}
393 
394 	shell_print(sh, "Retrieving transfer capabilities...");
395 
396 	if (argc > 1) {
397 		group = shell_strtoul(argv[1], 0, &err);
398 	} else {
399 		group = BT_MESH_ADDR_UNASSIGNED;
400 	}
401 
402 	if (argc > 2) {
403 		blob_cli_xfer.inputs.timeout_base = shell_strtoul(argv[2], 0, &err);
404 	} else {
405 		blob_cli_xfer.inputs.timeout_base = 0;
406 	}
407 
408 	if (err) {
409 		shell_warn(sh, "Unable to parse input string argument");
410 		return err;
411 	}
412 
413 	if (!blob_cli_xfer.target_count) {
414 		shell_print(sh, "Failed: No targets");
415 		return 0;
416 	}
417 
418 	blob_cli_inputs_prepare(group);
419 
420 	err = bt_mesh_blob_cli_caps_get((struct bt_mesh_blob_cli *)mod_cli->rt->user_data,
421 					&blob_cli_xfer.inputs);
422 	if (err) {
423 		shell_print(sh, "Boundary check start failed (err: %d)", err);
424 	}
425 
426 	return 0;
427 }
428 
cmd_tx_cancel(const struct shell * sh,size_t argc,char * argv[])429 static int cmd_tx_cancel(const struct shell *sh, size_t argc,
430 			      char *argv[])
431 {
432 	if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) {
433 		return -ENODEV;
434 	}
435 
436 	shell_print(sh, "Cancelling transfer");
437 	bt_mesh_blob_cli_cancel((struct bt_mesh_blob_cli *)mod_cli->rt->user_data);
438 
439 	return 0;
440 }
441 
cmd_tx_get(const struct shell * sh,size_t argc,char * argv[])442 static int cmd_tx_get(const struct shell *sh, size_t argc, char *argv[])
443 {
444 	uint16_t group;
445 	int err;
446 
447 	if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) {
448 		return -ENODEV;
449 	}
450 
451 	if (argc > 1) {
452 		group = shell_strtoul(argv[1], 0, &err);
453 	} else {
454 		group = BT_MESH_ADDR_UNASSIGNED;
455 	}
456 
457 	if (!blob_cli_xfer.target_count) {
458 		shell_print(sh, "Failed: No targets");
459 		return -EINVAL;
460 	}
461 
462 	blob_cli_inputs_prepare(group);
463 
464 	err = bt_mesh_blob_cli_xfer_progress_get((struct bt_mesh_blob_cli *)mod_cli->rt->user_data,
465 						 &blob_cli_xfer.inputs);
466 	if (err) {
467 		shell_print(sh, "ERR %d", err);
468 		return err;
469 	}
470 	return 0;
471 }
472 
cmd_tx_suspend(const struct shell * sh,size_t argc,char * argv[])473 static int cmd_tx_suspend(const struct shell *sh, size_t argc,
474 			       char *argv[])
475 {
476 	if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) {
477 		return -ENODEV;
478 	}
479 
480 	shell_print(sh, "Suspending transfer");
481 	bt_mesh_blob_cli_suspend((struct bt_mesh_blob_cli *)mod_cli->rt->user_data);
482 
483 	return 0;
484 }
485 
cmd_tx_resume(const struct shell * sh,size_t argc,char * argv[])486 static int cmd_tx_resume(const struct shell *sh, size_t argc, char *argv[])
487 {
488 	if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) {
489 		return -ENODEV;
490 	}
491 
492 	shell_print(sh, "Resuming transfer");
493 	bt_mesh_blob_cli_resume((struct bt_mesh_blob_cli *)mod_cli->rt->user_data);
494 
495 	return 0;
496 }
497 
498 #endif /* CONFIG_BT_MESH_SHELL_BLOB_CLI */
499 
500 #if defined(CONFIG_BT_MESH_SHELL_BLOB_SRV)
501 
502 static const struct bt_mesh_model *mod_srv;
503 
cmd_rx(const struct shell * sh,size_t argc,char * argv[])504 static int cmd_rx(const struct shell *sh, size_t argc, char *argv[])
505 {
506 	uint16_t timeout_base;
507 	uint32_t id;
508 	int err = 0;
509 
510 	if (!mod_srv && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_SRV, &mod_srv)) {
511 		return -ENODEV;
512 	}
513 
514 	id = shell_strtoul(argv[1], 0, &err);
515 	blob_rx_sum = 0;
516 
517 	if (argc > 2) {
518 		timeout_base = shell_strtoul(argv[2], 0, &err);
519 	} else {
520 		timeout_base = 0U;
521 	}
522 
523 	if (err) {
524 		shell_warn(sh, "Unable to parse input string argument");
525 		return err;
526 	}
527 
528 	shell_print(sh, "Receive BLOB 0x%x", id);
529 	err = bt_mesh_blob_srv_recv((struct bt_mesh_blob_srv *)mod_srv->rt->user_data,
530 				    id, bt_mesh_shell_blob_io, BT_MESH_TTL_MAX, timeout_base);
531 	if (err) {
532 		shell_print(sh, "BLOB RX setup failed (%d)", err);
533 	}
534 
535 	return 0;
536 }
537 
cmd_rx_cancel(const struct shell * sh,size_t argc,char * argv[])538 static int cmd_rx_cancel(const struct shell *sh, size_t argc, char *argv[])
539 {
540 	int err;
541 
542 	if (!mod_srv && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_SRV, &mod_srv)) {
543 		return -ENODEV;
544 	}
545 
546 	shell_print(sh, "Cancelling BLOB rx");
547 	err = bt_mesh_blob_srv_cancel((struct bt_mesh_blob_srv *)mod_srv->rt->user_data);
548 	if (err) {
549 		shell_print(sh, "BLOB cancel failed (%d)", err);
550 	}
551 
552 	return 0;
553 }
554 
555 #endif /* CONFIG_BT_MESH_SHELL_BLOB_SRV */
556 
557 #if defined(CONFIG_BT_MESH_SHELL_BLOB_CLI)
558 BT_MESH_SHELL_MDL_INSTANCE_CMDS(cli_instance_cmds, BT_MESH_MODEL_ID_BLOB_CLI, mod_cli);
559 #endif
560 #if defined(CONFIG_BT_MESH_SHELL_BLOB_SRV)
561 BT_MESH_SHELL_MDL_INSTANCE_CMDS(srv_instance_cmds, BT_MESH_MODEL_ID_BLOB_SRV, mod_srv);
562 #endif
563 
564 #if defined(CONFIG_BT_MESH_SHELL_BLOB_CLI)
565 SHELL_STATIC_SUBCMD_SET_CREATE(
566 	blob_cli_cmds,
567 	/* BLOB Client Model Operations */
568 	SHELL_CMD_ARG(target, NULL, "<Addr>", cmd_target, 2, 0),
569 	SHELL_CMD_ARG(caps, NULL, "[<Group> [<TimeoutBase>]]", cmd_caps, 1, 2),
570 	SHELL_CMD_ARG(tx, NULL, "<ID> <Size> <BlockSizeLog> "
571 		      "<ChunkSize> [<Group> [<Mode(push, pull)> "
572 		      "[<TimeoutBase>]]]", cmd_tx, 5, 3),
573 	SHELL_CMD_ARG(tx-cancel, NULL, NULL, cmd_tx_cancel, 1, 0),
574 	SHELL_CMD_ARG(tx-get, NULL, "[Group]", cmd_tx_get, 1, 1),
575 	SHELL_CMD_ARG(tx-suspend, NULL, NULL, cmd_tx_suspend, 1, 0),
576 	SHELL_CMD_ARG(tx-resume, NULL, NULL, cmd_tx_resume, 1, 0),
577 	SHELL_CMD(instance, &cli_instance_cmds, "Instance commands", bt_mesh_shell_mdl_cmds_help),
578 	SHELL_SUBCMD_SET_END);
579 #endif
580 
581 #if defined(CONFIG_BT_MESH_SHELL_BLOB_SRV)
582 SHELL_STATIC_SUBCMD_SET_CREATE(
583 	blob_srv_cmds,
584 	/* BLOB Server Model Operations */
585 	SHELL_CMD_ARG(rx, NULL, "<ID> [<TimeoutBase(10s steps)>]", cmd_rx, 2, 1),
586 	SHELL_CMD_ARG(rx-cancel, NULL, NULL, cmd_rx_cancel, 1, 0),
587 	SHELL_CMD(instance, &srv_instance_cmds, "Instance commands", bt_mesh_shell_mdl_cmds_help),
588 	SHELL_SUBCMD_SET_END);
589 #endif
590 
591 SHELL_STATIC_SUBCMD_SET_CREATE(
592 	blob_cmds,
593 #if defined(CONFIG_BT_MESH_SHELL_BLOB_IO_FLASH)
594 	SHELL_CMD_ARG(flash-stream-set, NULL, "<AreaID> [<Offset>]",
595 		      cmd_flash_stream_set, 2, 1),
596 	SHELL_CMD_ARG(flash-stream-unset, NULL, NULL, cmd_flash_stream_unset, 1, 0),
597 #endif
598 #if defined(CONFIG_BT_MESH_SHELL_BLOB_CLI)
599 	SHELL_CMD(cli, &blob_cli_cmds, "BLOB Cli commands", bt_mesh_shell_mdl_cmds_help),
600 #endif
601 #if defined(CONFIG_BT_MESH_SHELL_BLOB_SRV)
602 	SHELL_CMD(srv, &blob_srv_cmds, "BLOB Srv commands", bt_mesh_shell_mdl_cmds_help),
603 #endif
604 	SHELL_SUBCMD_SET_END);
605 
606 SHELL_SUBCMD_ADD((mesh, models), blob, &blob_cmds, "BLOB models commands",
607 		 bt_mesh_shell_mdl_cmds_help, 1, 1);
608