1 /**
2 * @file
3 * @brief Shell APIs for Bluetooth BASS client
4 *
5 * Copyright (c) 2020-2023 Nordic Semiconductor ASA
6 *
7 * SPDX-License-Identifier: Apache-2.0
8 */
9
10 #include <errno.h>
11 #include <stdbool.h>
12 #include <stddef.h>
13 #include <stdint.h>
14 #include <string.h>
15
16 #include <zephyr/autoconf.h>
17 #include <zephyr/bluetooth/audio/audio.h>
18 #include <zephyr/bluetooth/audio/bap.h>
19 #include <zephyr/bluetooth/addr.h>
20 #include <zephyr/bluetooth/bluetooth.h>
21 #include <zephyr/bluetooth/gap.h>
22 #include <zephyr/bluetooth/gatt.h>
23 #include <zephyr/bluetooth/hci.h>
24 #include <zephyr/bluetooth/iso.h>
25 #include <zephyr/bluetooth/uuid.h>
26 #include <zephyr/kernel.h>
27 #include <zephyr/net_buf.h>
28 #include <zephyr/shell/shell.h>
29 #include <zephyr/shell/shell_string_conv.h>
30 #include <zephyr/sys/byteorder.h>
31 #include <zephyr/sys/util.h>
32 #include <zephyr/sys/util_macro.h>
33 #include <zephyr/types.h>
34
35 #include "host/shell/bt.h"
36 #include "../../host/hci_core.h"
37 #include "audio.h"
38
39 static uint8_t received_base[UINT8_MAX];
40 static size_t received_base_size;
41
42 static struct bt_auto_scan {
43 uint32_t broadcast_id;
44 char broadcast_name[BT_AUDIO_BROADCAST_NAME_LEN_MAX + 1];
45 bool pa_sync;
46 struct bt_bap_bass_subgroup subgroup;
47 } auto_scan = {
48 .broadcast_id = BT_BAP_INVALID_BROADCAST_ID,
49 };
50
51 struct bt_scan_recv_info {
52 uint32_t broadcast_id;
53 char broadcast_name[BT_AUDIO_BROADCAST_NAME_LEN_MAX + 1];
54 };
55
pa_decode_base(struct bt_data * data,void * user_data)56 static bool pa_decode_base(struct bt_data *data, void *user_data)
57 {
58 const struct bt_bap_base *base = bt_bap_base_get_base_from_ad(data);
59 int base_size;
60
61 /* Base is NULL if the data does not contain a valid BASE */
62 if (base == NULL) {
63 return true;
64 }
65
66 base_size = bt_bap_base_get_size(base);
67 if (base_size < 0) {
68 shell_error(ctx_shell, "BASE get size failed (%d)", base_size);
69
70 return true;
71 }
72
73 /* Compare BASE and print if different */
74 if ((size_t)base_size != received_base_size ||
75 memcmp(base, received_base, (size_t)base_size) != 0) {
76 (void)memcpy(received_base, base, base_size);
77 received_base_size = (size_t)base_size;
78
79 print_base((const struct bt_bap_base *)received_base);
80 }
81
82 return false;
83 }
84
pa_recv(struct bt_le_per_adv_sync * sync,const struct bt_le_per_adv_sync_recv_info * info,struct net_buf_simple * buf)85 static void pa_recv(struct bt_le_per_adv_sync *sync,
86 const struct bt_le_per_adv_sync_recv_info *info,
87 struct net_buf_simple *buf)
88 {
89 bt_data_parse(buf, pa_decode_base, NULL);
90 }
91
bap_broadcast_assistant_discover_cb(struct bt_conn * conn,int err,uint8_t recv_state_count)92 static void bap_broadcast_assistant_discover_cb(struct bt_conn *conn, int err,
93 uint8_t recv_state_count)
94 {
95 if (err != 0) {
96 shell_error(ctx_shell, "BASS discover failed (%d)", err);
97 } else {
98 shell_print(ctx_shell, "BASS discover done with %u recv states",
99 recv_state_count);
100 }
101
102 }
103
bap_broadcast_assistant_scan_cb(const struct bt_le_scan_recv_info * info,uint32_t broadcast_id)104 static void bap_broadcast_assistant_scan_cb(const struct bt_le_scan_recv_info *info,
105 uint32_t broadcast_id)
106 {
107 char le_addr[BT_ADDR_LE_STR_LEN];
108
109 bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
110 shell_print(
111 ctx_shell,
112 "[DEVICE]: %s, broadcast_id 0x%06X, interval (ms) %u (0x%04x)), SID 0x%x, RSSI %i",
113 le_addr, broadcast_id, BT_GAP_PER_ADV_INTERVAL_TO_MS(info->interval),
114 info->interval, info->sid, info->rssi);
115 }
116
metadata_entry(struct bt_data * data,void * user_data)117 static bool metadata_entry(struct bt_data *data, void *user_data)
118 {
119 char metadata[512];
120
121 bin2hex(data->data, data->data_len, metadata, sizeof(metadata));
122
123 shell_print(ctx_shell, "\t\tMetadata length %u, type %u, data: %s",
124 data->data_len, data->type, metadata);
125
126 return true;
127 }
128
bap_broadcast_assistant_recv_state_cb(struct bt_conn * conn,int err,const struct bt_bap_scan_delegator_recv_state * state)129 static void bap_broadcast_assistant_recv_state_cb(
130 struct bt_conn *conn, int err,
131 const struct bt_bap_scan_delegator_recv_state *state)
132 {
133 char le_addr[BT_ADDR_LE_STR_LEN];
134 char bad_code[33];
135 bool is_bad_code;
136
137 if (err != 0) {
138 shell_error(ctx_shell, "BASS recv state read failed (%d)", err);
139 return;
140 }
141
142 bt_addr_le_to_str(&state->addr, le_addr, sizeof(le_addr));
143 bin2hex(state->bad_code, BT_ISO_BROADCAST_CODE_SIZE, bad_code, sizeof(bad_code));
144
145 is_bad_code = state->encrypt_state == BT_BAP_BIG_ENC_STATE_BAD_CODE;
146 shell_print(ctx_shell,
147 "BASS recv state: src_id %u, addr %s, sid %u, broadcast_id 0x%06X, sync_state "
148 "%u, encrypt_state %u%s%s",
149 state->src_id, le_addr, state->adv_sid, state->broadcast_id,
150 state->pa_sync_state, state->encrypt_state, is_bad_code ? ", bad code" : "",
151 is_bad_code ? bad_code : "");
152
153 for (int i = 0; i < state->num_subgroups; i++) {
154 const struct bt_bap_bass_subgroup *subgroup = &state->subgroups[i];
155 struct net_buf_simple buf;
156
157 shell_print(ctx_shell, "\t[%d]: BIS sync 0x%04X, metadata_len %zu", i,
158 subgroup->bis_sync, subgroup->metadata_len);
159
160 net_buf_simple_init_with_data(&buf, (void *)subgroup->metadata,
161 subgroup->metadata_len);
162 bt_data_parse(&buf, metadata_entry, NULL);
163 }
164
165
166 if (state->pa_sync_state == BT_BAP_PA_STATE_INFO_REQ) {
167 struct bt_le_per_adv_sync *per_adv_sync = NULL;
168 struct bt_le_ext_adv *ext_adv = NULL;
169
170 /* Lookup matching PA sync */
171 for (size_t i = 0U; i < ARRAY_SIZE(per_adv_syncs); i++) {
172 if (per_adv_syncs[i] != NULL &&
173 bt_addr_le_eq(&per_adv_syncs[i]->addr, &state->addr)) {
174 per_adv_sync = per_adv_syncs[i];
175 shell_print(ctx_shell, "Found matching PA sync [%zu]", i);
176 break;
177 }
178 }
179
180 if (per_adv_sync && IS_ENABLED(CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER)) {
181 shell_print(ctx_shell, "Sending PAST");
182
183 err = bt_le_per_adv_sync_transfer(per_adv_sync,
184 conn,
185 BT_UUID_BASS_VAL);
186
187 if (err != 0) {
188 shell_error(ctx_shell, "Could not transfer periodic adv sync: %d",
189 err);
190 }
191
192 return;
193 }
194
195 /* If no PA sync was found, check for local advertisers */
196 for (int i = 0; i < ARRAY_SIZE(adv_sets); i++) {
197 struct bt_le_oob oob_local;
198
199 if (adv_sets[i] == NULL) {
200 continue;
201 }
202
203 err = bt_le_ext_adv_oob_get_local(adv_sets[i],
204 &oob_local);
205
206 if (err != 0) {
207 shell_error(ctx_shell,
208 "Could not get local OOB %d",
209 err);
210 return;
211 }
212
213 if (bt_addr_le_eq(&oob_local.addr, &state->addr)) {
214 ext_adv = adv_sets[i];
215 break;
216 }
217 }
218
219 if (ext_adv != NULL && IS_ENABLED(CONFIG_BT_PER_ADV) &&
220 IS_ENABLED(CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER)) {
221 shell_print(ctx_shell, "Sending local PAST");
222
223 err = bt_le_per_adv_set_info_transfer(ext_adv, conn,
224 BT_UUID_BASS_VAL);
225
226 if (err != 0) {
227 shell_error(ctx_shell,
228 "Could not transfer per adv set info: %d",
229 err);
230 }
231 } else {
232 shell_error(ctx_shell,
233 "Could not send PA to Scan Delegator");
234 }
235 }
236 }
237
bap_broadcast_assistant_recv_state_removed_cb(struct bt_conn * conn,uint8_t src_id)238 static void bap_broadcast_assistant_recv_state_removed_cb(struct bt_conn *conn, uint8_t src_id)
239 {
240 shell_print(ctx_shell, "BASS recv state %u removed", src_id);
241 }
242
bap_broadcast_assistant_scan_start_cb(struct bt_conn * conn,int err)243 static void bap_broadcast_assistant_scan_start_cb(struct bt_conn *conn, int err)
244 {
245 if (err != 0) {
246 shell_error(ctx_shell, "BASS scan start failed (%d)", err);
247 } else {
248 shell_print(ctx_shell, "BASS scan start successful");
249 }
250 }
251
bap_broadcast_assistant_scan_stop_cb(struct bt_conn * conn,int err)252 static void bap_broadcast_assistant_scan_stop_cb(struct bt_conn *conn, int err)
253 {
254 if (err != 0) {
255 shell_error(ctx_shell, "BASS scan stop failed (%d)", err);
256 } else {
257 shell_print(ctx_shell, "BASS scan stop successful");
258 }
259 }
260
bap_broadcast_assistant_add_src_cb(struct bt_conn * conn,int err)261 static void bap_broadcast_assistant_add_src_cb(struct bt_conn *conn, int err)
262 {
263 if (err != 0) {
264 shell_error(ctx_shell, "BASS add source failed (%d)", err);
265 } else {
266 shell_print(ctx_shell, "BASS add source successful");
267 }
268 }
269
bap_broadcast_assistant_mod_src_cb(struct bt_conn * conn,int err)270 static void bap_broadcast_assistant_mod_src_cb(struct bt_conn *conn, int err)
271 {
272 if (err != 0) {
273 shell_error(ctx_shell, "BASS modify source failed (%d)", err);
274 } else {
275 shell_print(ctx_shell, "BASS modify source successful");
276 }
277 }
278
bap_broadcast_assistant_broadcast_code_cb(struct bt_conn * conn,int err)279 static void bap_broadcast_assistant_broadcast_code_cb(struct bt_conn *conn,
280 int err)
281 {
282 if (err != 0) {
283 shell_error(ctx_shell, "BASS broadcast code failed (%d)", err);
284 } else {
285 shell_print(ctx_shell, "BASS broadcast code successful");
286 }
287 }
288
bap_broadcast_assistant_rem_src_cb(struct bt_conn * conn,int err)289 static void bap_broadcast_assistant_rem_src_cb(struct bt_conn *conn, int err)
290 {
291 if (err != 0) {
292 shell_error(ctx_shell, "BASS remove source failed (%d)", err);
293 } else {
294 shell_print(ctx_shell, "BASS remove source successful");
295 }
296 }
297
298 static struct bt_bap_broadcast_assistant_cb cbs = {
299 .discover = bap_broadcast_assistant_discover_cb,
300 .scan = bap_broadcast_assistant_scan_cb,
301 .recv_state = bap_broadcast_assistant_recv_state_cb,
302 .recv_state_removed = bap_broadcast_assistant_recv_state_removed_cb,
303 .scan_start = bap_broadcast_assistant_scan_start_cb,
304 .scan_stop = bap_broadcast_assistant_scan_stop_cb,
305 .add_src = bap_broadcast_assistant_add_src_cb,
306 .mod_src = bap_broadcast_assistant_mod_src_cb,
307 .broadcast_code = bap_broadcast_assistant_broadcast_code_cb,
308 .rem_src = bap_broadcast_assistant_rem_src_cb,
309 };
310
cmd_bap_broadcast_assistant_scan_start(const struct shell * sh,size_t argc,char ** argv)311 static int cmd_bap_broadcast_assistant_scan_start(const struct shell *sh,
312 size_t argc, char **argv)
313 {
314 int result;
315 int start_scan = false;
316
317 if (argc > 1) {
318 result = 0;
319
320 start_scan = shell_strtobool(argv[1], 0, &result);
321 if (result != 0) {
322 shell_error(sh, "Could not parse start_scan: %d",
323 result);
324
325 return -ENOEXEC;
326 }
327 }
328
329 result = bt_bap_broadcast_assistant_scan_start(default_conn,
330 (bool)start_scan);
331 if (result) {
332 shell_print(sh, "Fail: %d", result);
333 }
334
335 return result;
336 }
337
cmd_bap_broadcast_assistant_scan_stop(const struct shell * sh,size_t argc,char ** argv)338 static int cmd_bap_broadcast_assistant_scan_stop(const struct shell *sh,
339 size_t argc, char **argv)
340 {
341 int result;
342
343 result = bt_bap_broadcast_assistant_scan_stop(default_conn);
344 if (result) {
345 shell_print(sh, "Fail: %d", result);
346 }
347
348 return result;
349 }
350
cmd_bap_broadcast_assistant_add_src(const struct shell * sh,size_t argc,char ** argv)351 static int cmd_bap_broadcast_assistant_add_src(const struct shell *sh,
352 size_t argc, char **argv)
353 {
354 struct bt_bap_broadcast_assistant_add_src_param param = { 0 };
355 struct bt_bap_bass_subgroup subgroup = { 0 };
356 unsigned long broadcast_id;
357 unsigned long adv_sid;
358 int result;
359
360 result = bt_addr_le_from_str(argv[1], argv[2], ¶m.addr);
361 if (result) {
362 shell_error(sh, "Invalid peer address (err %d)", result);
363
364 return -ENOEXEC;
365 }
366
367 adv_sid = shell_strtoul(argv[3], 0, &result);
368 if (result != 0) {
369 shell_error(sh, "Could not parse adv_sid: %d", result);
370
371 return -ENOEXEC;
372 }
373
374 if (adv_sid > BT_GAP_SID_MAX) {
375 shell_error(sh, "Invalid adv_sid: %lu", adv_sid);
376
377 return -ENOEXEC;
378 }
379
380 param.adv_sid = adv_sid;
381
382 param.pa_sync = shell_strtobool(argv[4], 0, &result);
383 if (result != 0) {
384 shell_error(sh, "Could not parse adv_sid: %d", result);
385
386 return -ENOEXEC;
387 }
388
389 broadcast_id = shell_strtoul(argv[5], 0, &result);
390 if (result != 0) {
391 shell_error(sh, "Could not parse broadcast_id: %d", result);
392
393 return -ENOEXEC;
394 }
395
396 if (broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) {
397 shell_error(sh, "Invalid broadcast_id: %lu", broadcast_id);
398
399 return -ENOEXEC;
400 }
401
402 param.broadcast_id = broadcast_id;
403
404 if (argc > 6) {
405 unsigned long pa_interval;
406
407 pa_interval = shell_strtoul(argv[6], 0, &result);
408 if (result) {
409 shell_error(sh, "Could not parse pa_interval: %d",
410 result);
411
412 return -ENOEXEC;
413 }
414
415 if (!IN_RANGE(pa_interval,
416 BT_GAP_PER_ADV_MIN_INTERVAL,
417 BT_GAP_PER_ADV_MAX_INTERVAL)) {
418 shell_error(sh, "Invalid pa_interval: %lu",
419 pa_interval);
420
421 return -ENOEXEC;
422 }
423
424 param.pa_interval = pa_interval;
425 } else {
426 param.pa_interval = BT_BAP_PA_INTERVAL_UNKNOWN;
427 }
428
429 /* TODO: Support multiple subgroups */
430 if (argc > 7) {
431 unsigned long bis_sync;
432
433 bis_sync = shell_strtoul(argv[7], 0, &result);
434 if (result) {
435 shell_error(sh, "Could not parse bis_sync: %d", result);
436
437 return -ENOEXEC;
438 }
439
440 if (!BT_BAP_BASS_VALID_BIT_BITFIELD(bis_sync)) {
441 shell_error(sh, "Invalid bis_sync: %lu", bis_sync);
442
443 return -ENOEXEC;
444 }
445
446 subgroup.bis_sync = bis_sync;
447 }
448
449 if (argc > 8) {
450 size_t metadata_len;
451
452 metadata_len = hex2bin(argv[8], strlen(argv[8]),
453 subgroup.metadata,
454 sizeof(subgroup.metadata));
455
456 if (metadata_len == 0U) {
457 shell_error(sh, "Could not parse metadata");
458
459 return -ENOEXEC;
460 }
461
462 /* sizeof(subgroup.metadata) can always fit in uint8_t */
463
464 subgroup.metadata_len = metadata_len;
465 }
466
467 param.num_subgroups = 1;
468 param.subgroups = &subgroup;
469
470 result = bt_bap_broadcast_assistant_add_src(default_conn, ¶m);
471 if (result) {
472 shell_print(sh, "Fail: %d", result);
473 }
474
475 return result;
476 }
477
broadcast_source_found(struct bt_data * data,void * user_data)478 static bool broadcast_source_found(struct bt_data *data, void *user_data)
479 {
480 struct bt_scan_recv_info *sr_info = (struct bt_scan_recv_info *)user_data;
481 struct bt_uuid_16 adv_uuid;
482
483 switch (data->type) {
484 case BT_DATA_SVC_DATA16:
485 if (data->data_len < BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE) {
486 return true;
487 }
488
489 if (!bt_uuid_create(&adv_uuid.uuid, data->data, BT_UUID_SIZE_16)) {
490 return true;
491 }
492
493 if (bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_BROADCAST_AUDIO) != 0) {
494 return true;
495 }
496
497 sr_info->broadcast_id = sys_get_le24(data->data + BT_UUID_SIZE_16);
498 return true;
499 case BT_DATA_BROADCAST_NAME:
500 if (!IN_RANGE(data->data_len, BT_AUDIO_BROADCAST_NAME_LEN_MIN,
501 BT_AUDIO_BROADCAST_NAME_LEN_MAX)) {
502 return true;
503 }
504
505 utf8_lcpy(sr_info->broadcast_name, data->data, (data->data_len) + 1);
506 return true;
507 default:
508 return true;
509 }
510 }
511
scan_recv_cb(const struct bt_le_scan_recv_info * info,struct net_buf_simple * ad)512 static void scan_recv_cb(const struct bt_le_scan_recv_info *info,
513 struct net_buf_simple *ad)
514 {
515 struct bt_scan_recv_info sr_info = { 0 };
516 struct bt_bap_broadcast_assistant_add_src_param param = { 0 };
517 int err;
518
519 sr_info.broadcast_id = BT_BAP_INVALID_BROADCAST_ID;
520
521 if ((auto_scan.broadcast_id == BT_BAP_INVALID_BROADCAST_ID) &&
522 (strlen(auto_scan.broadcast_name) == 0U)) {
523 /* no op */
524 return;
525 }
526
527 /* We are only interested in non-connectable periodic advertisers */
528 if ((info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) != 0 ||
529 info->interval == 0) {
530 return;
531 }
532
533 if (!passes_scan_filter(info, ad)) {
534 return;
535 }
536
537 bt_data_parse(ad, broadcast_source_found, (void *)&sr_info);
538
539 /* Verify that it is a BAP broadcaster*/
540 if (sr_info.broadcast_id != BT_BAP_INVALID_BROADCAST_ID) {
541 char addr_str[BT_ADDR_LE_STR_LEN];
542 bool identified_broadcast = false;
543
544 bt_addr_le_to_str(info->addr, addr_str, sizeof(addr_str));
545
546 if (sr_info.broadcast_id == auto_scan.broadcast_id) {
547 identified_broadcast = true;
548 }
549
550 if ((strlen(auto_scan.broadcast_name) != 0U) &&
551 is_substring(auto_scan.broadcast_name, sr_info.broadcast_name)) {
552 identified_broadcast = true;
553
554 shell_print(ctx_shell, "Found matched broadcast name '%s' with address %s",
555 sr_info.broadcast_name, addr_str);
556 }
557
558 if (identified_broadcast) {
559 shell_print(ctx_shell,
560 "Found BAP broadcast source with address %s and ID 0x%06X\n",
561 addr_str, sr_info.broadcast_id);
562
563 err = bt_le_scan_stop();
564 if (err) {
565 shell_error(ctx_shell, "Failed to stop scan: %d", err);
566 }
567
568 bt_addr_le_copy(¶m.addr, info->addr);
569 param.adv_sid = info->sid;
570 param.pa_interval = info->interval;
571 param.broadcast_id = sr_info.broadcast_id;
572 param.pa_sync = auto_scan.pa_sync;
573 param.num_subgroups = 1;
574 param.subgroups = &auto_scan.subgroup;
575
576 err = bt_bap_broadcast_assistant_add_src(default_conn, ¶m);
577 if (err) {
578 shell_print(ctx_shell, "Failed to add source: %d", err);
579 }
580
581 memset(&auto_scan, 0, sizeof(auto_scan));
582 auto_scan.broadcast_id = BT_BAP_INVALID_BROADCAST_ID;
583 }
584 }
585 }
586
scan_timeout_cb(void)587 static void scan_timeout_cb(void)
588 {
589 shell_print(ctx_shell, "Scan timeout");
590
591 memset(&auto_scan, 0, sizeof(auto_scan));
592 auto_scan.broadcast_id = BT_BAP_INVALID_BROADCAST_ID;
593 }
594
595 static struct bt_le_scan_cb scan_callbacks = {
596 .recv = scan_recv_cb,
597 .timeout = scan_timeout_cb,
598 };
599
cmd_bap_broadcast_assistant_discover(const struct shell * sh,size_t argc,char ** argv)600 static int cmd_bap_broadcast_assistant_discover(const struct shell *sh,
601 size_t argc, char **argv)
602 {
603 static bool registered;
604 int result;
605
606 if (!registered) {
607 static struct bt_le_per_adv_sync_cb cb = {
608 .recv = pa_recv,
609 };
610
611 bt_le_per_adv_sync_cb_register(&cb);
612
613 bt_bap_broadcast_assistant_register_cb(&cbs);
614
615 bt_le_scan_cb_register(&scan_callbacks);
616
617 registered = true;
618 }
619
620 result = bt_bap_broadcast_assistant_discover(default_conn);
621 if (result) {
622 shell_print(sh, "Fail: %d", result);
623 }
624
625 return result;
626 }
627
cmd_bap_broadcast_assistant_add_broadcast_id(const struct shell * sh,size_t argc,char ** argv)628 static int cmd_bap_broadcast_assistant_add_broadcast_id(const struct shell *sh,
629 size_t argc,
630 char **argv)
631 {
632 struct bt_bap_bass_subgroup subgroup = { 0 };
633 unsigned long broadcast_id;
634 int err = 0;
635
636 if (auto_scan.broadcast_id != BT_BAP_INVALID_BROADCAST_ID) {
637 shell_info(sh, "Already scanning, wait for sync or timeout");
638
639 return -ENOEXEC;
640 }
641
642 broadcast_id = shell_strtoul(argv[1], 0, &err);
643 if (err != 0) {
644 shell_error(sh, "failed to parse broadcast_id: %d", err);
645
646 return -ENOEXEC;
647 } else if (broadcast_id > 0xFFFFFF /* 24 bits */) {
648 shell_error(sh, "Broadcast ID maximum 24 bits (was %lu)", broadcast_id);
649
650 return -ENOEXEC;
651 }
652
653 auto_scan.pa_sync = shell_strtobool(argv[2], 0, &err);
654 if (err != 0) {
655 shell_error(sh, "Could not parse pa_sync: %d", err);
656
657 return -ENOEXEC;
658 }
659
660 /* TODO: Support multiple subgroups */
661 if (argc > 3) {
662 const unsigned long bis_sync = shell_strtoul(argv[3], 0, &err);
663
664 if (err != 0) {
665 shell_error(sh, "failed to parse bis_sync: %d", err);
666
667 return -ENOEXEC;
668 } else if (!BT_BAP_BASS_VALID_BIT_BITFIELD(bis_sync)) {
669 shell_error(sh, "Invalid bis_sync: %lu", bis_sync);
670
671 return -ENOEXEC;
672 }
673
674 subgroup.bis_sync = bis_sync;
675 }
676
677 if (argc > 4) {
678 subgroup.metadata_len = hex2bin(argv[4], strlen(argv[4]), subgroup.metadata,
679 sizeof(subgroup.metadata));
680
681 if (subgroup.metadata_len == 0U) {
682 shell_error(sh, "Could not parse metadata");
683
684 return -ENOEXEC;
685 }
686 }
687
688 err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL);
689 if (err) {
690 shell_print(sh, "Fail to start scanning: %d", err);
691
692 return -ENOEXEC;
693 }
694
695 /* Store results in the `auto_scan` struct */
696 auto_scan.broadcast_id = broadcast_id;
697 memcpy(&auto_scan.subgroup, &subgroup, sizeof(subgroup));
698 memset(auto_scan.broadcast_name, 0, sizeof(auto_scan.broadcast_name));
699
700 return 0;
701 }
702
cmd_bap_broadcast_assistant_add_broadcast_name(const struct shell * sh,size_t argc,char ** argv)703 static int cmd_bap_broadcast_assistant_add_broadcast_name(const struct shell *sh,
704 size_t argc, char **argv)
705 {
706 struct bt_bap_bass_subgroup subgroup = { 0 };
707 char *broadcast_name;
708 int err = 0;
709
710 broadcast_name = argv[1];
711 if (!IN_RANGE(strlen(broadcast_name), BT_AUDIO_BROADCAST_NAME_LEN_MIN,
712 BT_AUDIO_BROADCAST_NAME_LEN_MAX)) {
713
714 shell_error(sh, "Broadcast name should be minimum %d "
715 "and maximum %d characters", BT_AUDIO_BROADCAST_NAME_LEN_MIN,
716 BT_AUDIO_BROADCAST_NAME_LEN_MAX);
717
718 return -ENOEXEC;
719 }
720
721 auto_scan.pa_sync = shell_strtobool(argv[2], 0, &err);
722 if (err != 0) {
723 shell_error(sh, "Could not parse pa_sync: %d", err);
724
725 return -ENOEXEC;
726 }
727
728 /* TODO: Support multiple subgroups */
729 if (argc > 3) {
730 const unsigned long bis_sync = shell_strtoul(argv[3], 0, &err);
731
732 if (err != 0) {
733 shell_error(sh, "failed to parse bis_sync: %d", err);
734
735 return -ENOEXEC;
736 } else if (!BT_BAP_BASS_VALID_BIT_BITFIELD(bis_sync)) {
737 shell_error(sh, "Invalid bis_sync: %lu", bis_sync);
738
739 return -ENOEXEC;
740 }
741
742 subgroup.bis_sync = bis_sync;
743 }
744
745 if (argc > 4) {
746 subgroup.metadata_len = hex2bin(argv[4], strlen(argv[4]), subgroup.metadata,
747 sizeof(subgroup.metadata));
748
749 if (subgroup.metadata_len == 0U) {
750 shell_error(sh, "Could not parse metadata");
751
752 return -ENOEXEC;
753 }
754 }
755
756 err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL);
757 if (err) {
758 shell_print(sh, "Fail to start scanning: %d", err);
759
760 return -ENOEXEC;
761 }
762
763 /* Store results in the `auto_scan` struct */
764 utf8_lcpy(auto_scan.broadcast_name, broadcast_name, strlen(broadcast_name) + 1);
765 auto_scan.broadcast_id = BT_BAP_INVALID_BROADCAST_ID;
766 memcpy(&auto_scan.subgroup, &subgroup, sizeof(subgroup));
767
768 return 0;
769 }
770
cmd_bap_broadcast_assistant_mod_src(const struct shell * sh,size_t argc,char ** argv)771 static int cmd_bap_broadcast_assistant_mod_src(const struct shell *sh,
772 size_t argc, char **argv)
773 {
774 struct bt_bap_broadcast_assistant_mod_src_param param = { 0 };
775 struct bt_bap_bass_subgroup subgroup = { 0 };
776 unsigned long src_id;
777 int result = 0;
778
779 src_id = shell_strtoul(argv[1], 0, &result);
780 if (result != 0) {
781 shell_error(sh, "Could not parse src_id: %d", result);
782
783 return -ENOEXEC;
784 }
785
786 if (src_id > UINT8_MAX) {
787 shell_error(sh, "Invalid src_id: %lu", src_id);
788
789 return -ENOEXEC;
790 }
791 param.src_id = src_id;
792
793 param.pa_sync = shell_strtobool(argv[2], 0, &result);
794 if (result != 0) {
795 shell_error(sh, "Could not parse adv_sid: %d", result);
796
797 return -ENOEXEC;
798 }
799
800 if (argc > 3) {
801 if (strcmp(argv[3], "unknown") == 0) {
802 param.pa_interval = BT_BAP_PA_INTERVAL_UNKNOWN;
803 } else {
804 unsigned long pa_interval;
805
806 pa_interval = shell_strtoul(argv[3], 0, &result);
807 if (result) {
808 shell_error(sh, "Could not parse pa_interval: %d", result);
809
810 return -ENOEXEC;
811 }
812
813 if (!IN_RANGE(pa_interval, BT_GAP_PER_ADV_MIN_INTERVAL,
814 BT_GAP_PER_ADV_MAX_INTERVAL)) {
815 shell_error(sh, "Invalid pa_interval: %lu", pa_interval);
816
817 return -ENOEXEC;
818 }
819
820 param.pa_interval = pa_interval;
821 }
822 } else {
823 param.pa_interval = BT_BAP_PA_INTERVAL_UNKNOWN;
824 }
825
826 /* TODO: Support multiple subgroups */
827 if (argc > 4) {
828 unsigned long bis_sync;
829
830 bis_sync = shell_strtoul(argv[4], 0, &result);
831 if (result) {
832 shell_error(sh, "Could not parse bis_sync: %d", result);
833
834 return -ENOEXEC;
835 }
836
837 if (!BT_BAP_BASS_VALID_BIT_BITFIELD(bis_sync)) {
838 shell_error(sh, "Invalid bis_sync: %lu", bis_sync);
839
840 return -ENOEXEC;
841 }
842
843 subgroup.bis_sync = bis_sync;
844 }
845
846 if (argc > 5) {
847 size_t metadata_len;
848
849 metadata_len = hex2bin(argv[5], strlen(argv[5]),
850 subgroup.metadata,
851 sizeof(subgroup.metadata));
852
853 if (metadata_len == 0U) {
854 shell_error(sh, "Could not parse metadata");
855
856 return -ENOEXEC;
857 }
858
859 /* sizeof(subgroup.metadata) can always fit in uint8_t */
860
861 subgroup.metadata_len = metadata_len;
862 }
863
864 param.num_subgroups = 1;
865 param.subgroups = &subgroup;
866
867 result = bt_bap_broadcast_assistant_mod_src(default_conn, ¶m);
868 if (result) {
869 shell_print(sh, "Fail: %d", result);
870 }
871
872 return result;
873 }
874
add_pa_sync_base_subgroup_bis_cb(const struct bt_bap_base_subgroup_bis * bis,void * user_data)875 static inline bool add_pa_sync_base_subgroup_bis_cb(const struct bt_bap_base_subgroup_bis *bis,
876 void *user_data)
877 {
878 struct bt_bap_bass_subgroup *subgroup_param = user_data;
879
880 subgroup_param->bis_sync |= BT_ISO_BIS_INDEX_BIT(bis->index);
881
882 return true;
883 }
884
add_pa_sync_base_subgroup_cb(const struct bt_bap_base_subgroup * subgroup,void * user_data)885 static inline bool add_pa_sync_base_subgroup_cb(const struct bt_bap_base_subgroup *subgroup,
886 void *user_data)
887 {
888 struct bt_bap_broadcast_assistant_add_src_param *param = user_data;
889 struct bt_bap_bass_subgroup *subgroup_param;
890 uint8_t *data;
891 int ret;
892
893 if (param->num_subgroups == CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) {
894 shell_warn(ctx_shell, "Cannot fit all subgroups param with size %d",
895 CONFIG_BT_BAP_BASS_MAX_SUBGROUPS);
896
897 return true; /* return true to avoid returning -ECANCELED as this is OK */
898 }
899
900 ret = bt_bap_base_get_subgroup_codec_meta(subgroup, &data);
901 if (ret < 0) {
902 return false;
903 }
904
905 subgroup_param = ¶m->subgroups[param->num_subgroups];
906
907 if (ret > ARRAY_SIZE(subgroup_param->metadata)) {
908 shell_info(ctx_shell, "Cannot fit %d octets into subgroup param with size %zu", ret,
909 ARRAY_SIZE(subgroup_param->metadata));
910 return false;
911 }
912
913 ret = bt_bap_base_subgroup_foreach_bis(subgroup, add_pa_sync_base_subgroup_bis_cb,
914 subgroup_param);
915
916 if (ret < 0) {
917 return false;
918 }
919
920 param->num_subgroups++;
921
922 return true;
923 }
924
cmd_bap_broadcast_assistant_add_pa_sync(const struct shell * sh,size_t argc,char ** argv)925 static int cmd_bap_broadcast_assistant_add_pa_sync(const struct shell *sh,
926 size_t argc, char **argv)
927 {
928 struct bt_bap_bass_subgroup subgroup_params[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS];
929 struct bt_bap_broadcast_assistant_add_src_param param = { 0 };
930 /* TODO: Add support to select which PA sync to BIG sync to */
931 struct bt_le_per_adv_sync *pa_sync = per_adv_syncs[0];
932 struct bt_le_per_adv_sync_info pa_info;
933 unsigned long broadcast_id;
934 uint32_t bis_bitfield_req;
935 uint32_t subgroups_bis_sync;
936 int err;
937
938 if (pa_sync == NULL) {
939 shell_error(sh, "PA not synced");
940
941 return -ENOEXEC;
942 }
943
944 err = bt_le_per_adv_sync_get_info(pa_sync, &pa_info);
945 if (err != 0) {
946 shell_error(sh, "Could not get PA sync info: %d", err);
947
948 return -ENOEXEC;
949 }
950
951 bt_addr_le_copy(¶m.addr, &pa_info.addr);
952 param.adv_sid = pa_info.sid;
953 param.pa_interval = pa_info.interval;
954
955 memset(&subgroup_params, 0, sizeof(subgroup_params));
956
957 param.pa_sync = shell_strtobool(argv[1], 0, &err);
958 if (err != 0) {
959 shell_error(sh, "Could not parse pa_sync: %d", err);
960
961 return -ENOEXEC;
962 }
963
964 broadcast_id = shell_strtoul(argv[2], 0, &err);
965 if (err != 0) {
966 shell_error(sh, "failed to parse broadcast_id: %d", err);
967
968 return -ENOEXEC;
969 } else if (broadcast_id > BT_AUDIO_BROADCAST_ID_MAX /* 24 bits */) {
970 shell_error(sh, "Invalid Broadcast ID: %x",
971 param.broadcast_id);
972
973 return -ENOEXEC;
974 }
975
976 param.broadcast_id = broadcast_id;
977
978 bis_bitfield_req = 0U;
979 subgroups_bis_sync = 0U;
980 for (size_t i = 3U; i < argc; i++) {
981 const unsigned long index = shell_strtoul(argv[i], 16, &err);
982
983 if (err != 0) {
984 shell_error(sh, "failed to parse index: %d", err);
985
986 return -ENOEXEC;
987 }
988
989 if (index < BT_ISO_BIS_INDEX_MIN ||
990 index > BT_ISO_BIS_INDEX_MAX) {
991 shell_error(sh, "Invalid index: %ld", index);
992
993 return -ENOEXEC;
994 }
995
996 bis_bitfield_req |= BT_ISO_BIS_INDEX_BIT(index);
997 }
998
999 param.subgroups = subgroup_params;
1000 if (received_base_size > 0) {
1001 err = bt_bap_base_foreach_subgroup((const struct bt_bap_base *)received_base,
1002 add_pa_sync_base_subgroup_cb, ¶m);
1003 if (err < 0) {
1004 shell_error(ctx_shell, "Could not add BASE to params %d", err);
1005
1006 return -ENOEXEC;
1007 }
1008 }
1009
1010 /* use the BASE to verify the BIS indexes set by command */
1011 for (size_t j = 0U; j < param.num_subgroups; j++) {
1012 if (bis_bitfield_req == 0) {
1013 /* Request a PA sync without BIS sync */
1014 subgroup_params[j].bis_sync = 0;
1015 } else {
1016 subgroups_bis_sync |= subgroup_params[j].bis_sync;
1017 /* only set the BIS index field as optional parameters */
1018 /* not to whatever is in the BASE */
1019 subgroup_params[j].bis_sync &= bis_bitfield_req;
1020 }
1021 }
1022
1023 if ((subgroups_bis_sync & bis_bitfield_req) != bis_bitfield_req) {
1024 /* bis_sync of all subgroups should contain at least all the bits in request */
1025 /* Otherwise Command will be rejected */
1026 shell_error(ctx_shell, "Cannot set BIS index 0x%06X when BASE subgroups only "
1027 "supports %d", bis_bitfield_req, subgroups_bis_sync);
1028 return -ENOEXEC;
1029 }
1030
1031 err = bt_bap_broadcast_assistant_add_src(default_conn, ¶m);
1032 if (err != 0) {
1033 shell_print(sh, "Fail: %d", err);
1034
1035 return -ENOEXEC;
1036 }
1037
1038 return 0;
1039 }
1040
cmd_bap_broadcast_assistant_broadcast_code(const struct shell * sh,size_t argc,char ** argv)1041 static int cmd_bap_broadcast_assistant_broadcast_code(const struct shell *sh,
1042 size_t argc, char **argv)
1043 {
1044 uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE] = {0};
1045 size_t broadcast_code_len;
1046 unsigned long src_id;
1047 int result = 0;
1048
1049 src_id = shell_strtoul(argv[1], 0, &result);
1050 if (result != 0) {
1051 shell_error(sh, "Could not parse src_id: %d", result);
1052
1053 return -ENOEXEC;
1054 }
1055
1056 if (src_id > UINT8_MAX) {
1057 shell_error(sh, "Invalid src_id: %lu", src_id);
1058
1059 return -ENOEXEC;
1060 }
1061
1062 broadcast_code_len = strlen(argv[2]);
1063 if (!IN_RANGE(broadcast_code_len, 1, BT_ISO_BROADCAST_CODE_SIZE)) {
1064 shell_error(sh, "Invalid broadcast code length: %zu", broadcast_code_len);
1065
1066 return -ENOEXEC;
1067 }
1068
1069 memcpy(broadcast_code, argv[2], broadcast_code_len);
1070
1071 shell_info(sh, "Sending broadcast code:");
1072 shell_hexdump(sh, broadcast_code, sizeof(broadcast_code));
1073
1074 result = bt_bap_broadcast_assistant_set_broadcast_code(default_conn,
1075 src_id,
1076 broadcast_code);
1077 if (result) {
1078 shell_print(sh, "Fail: %d", result);
1079 }
1080
1081 return result;
1082 }
1083
cmd_bap_broadcast_assistant_rem_src(const struct shell * sh,size_t argc,char ** argv)1084 static int cmd_bap_broadcast_assistant_rem_src(const struct shell *sh,
1085 size_t argc, char **argv)
1086 {
1087 unsigned long src_id;
1088 int result = 0;
1089
1090 src_id = shell_strtoul(argv[1], 0, &result);
1091 if (result != 0) {
1092 shell_error(sh, "Could not parse src_id: %d", result);
1093
1094 return -ENOEXEC;
1095 }
1096
1097 if (src_id > UINT8_MAX) {
1098 shell_error(sh, "Invalid src_id: %lu", src_id);
1099
1100 return -ENOEXEC;
1101 }
1102
1103 result = bt_bap_broadcast_assistant_rem_src(default_conn, src_id);
1104 if (result) {
1105 shell_print(sh, "Fail: %d", result);
1106 }
1107
1108 return result;
1109 }
1110
cmd_bap_broadcast_assistant_read_recv_state(const struct shell * sh,size_t argc,char ** argv)1111 static int cmd_bap_broadcast_assistant_read_recv_state(const struct shell *sh,
1112 size_t argc, char **argv)
1113 {
1114 unsigned long idx;
1115 int result = 0;
1116
1117 idx = shell_strtoul(argv[1], 0, &result);
1118 if (result != 0) {
1119 shell_error(sh, "Could not parse idx: %d", result);
1120
1121 return -ENOEXEC;
1122 }
1123
1124 if (idx > UINT8_MAX) {
1125 shell_error(sh, "Invalid idx: %lu", idx);
1126
1127 return -ENOEXEC;
1128 }
1129
1130 result = bt_bap_broadcast_assistant_read_recv_state(default_conn, idx);
1131 if (result) {
1132 shell_print(sh, "Fail: %d", result);
1133 }
1134
1135 return result;
1136 }
1137
cmd_bap_broadcast_assistant(const struct shell * sh,size_t argc,char ** argv)1138 static int cmd_bap_broadcast_assistant(const struct shell *sh, size_t argc,
1139 char **argv)
1140 {
1141 if (argc > 1) {
1142 shell_error(sh, "%s unknown parameter: %s",
1143 argv[0], argv[1]);
1144 } else {
1145 shell_error(sh, "%s Missing subcommand", argv[0]);
1146 }
1147
1148 return -ENOEXEC;
1149 }
1150
1151 SHELL_STATIC_SUBCMD_SET_CREATE(
1152 bap_broadcast_assistant_cmds,
1153 SHELL_CMD_ARG(discover, NULL, "Discover BASS on the server",
1154 cmd_bap_broadcast_assistant_discover, 1, 0),
1155 SHELL_CMD_ARG(scan_start, NULL, "Start scanning for broadcasters",
1156 cmd_bap_broadcast_assistant_scan_start, 1, 1),
1157 SHELL_CMD_ARG(scan_stop, NULL, "Stop scanning for BISs",
1158 cmd_bap_broadcast_assistant_scan_stop, 1, 0),
1159 SHELL_CMD_ARG(add_src, NULL,
1160 "Add a source <address: XX:XX:XX:XX:XX:XX> "
1161 "<type: public/random> <adv_sid> <sync_pa> "
1162 "<broadcast_id> [<pa_interval>] [<sync_bis>] "
1163 "[<metadata>]",
1164 cmd_bap_broadcast_assistant_add_src, 6, 3),
1165 SHELL_CMD_ARG(add_broadcast_id, NULL,
1166 "Add a source by broadcast ID <broadcast_id> <sync_pa> "
1167 "[<sync_bis>] [<metadata>]",
1168 cmd_bap_broadcast_assistant_add_broadcast_id, 3, 2),
1169 SHELL_CMD_ARG(add_broadcast_name, NULL,
1170 "Add a source by broadcast name <broadcast_name> <sync_pa> "
1171 "[<sync_bis>] [<metadata>]",
1172 cmd_bap_broadcast_assistant_add_broadcast_name, 3, 2),
1173 SHELL_CMD_ARG(add_pa_sync, NULL,
1174 "Add a PA sync as a source <sync_pa> <broadcast_id> "
1175 "[bis_index [bis_index [bix_index [...]]]]>",
1176 cmd_bap_broadcast_assistant_add_pa_sync, 3, BT_ISO_MAX_GROUP_ISO_COUNT),
1177 SHELL_CMD_ARG(mod_src, NULL,
1178 "Set sync <src_id> <sync_pa> [<pa_interval> | \"unknown\"] "
1179 "[<sync_bis>] [<metadata>]",
1180 cmd_bap_broadcast_assistant_mod_src, 3, 2),
1181 SHELL_CMD_ARG(broadcast_code, NULL,
1182 "Send a string-based broadcast code of up to 16 bytes "
1183 "<src_id> <broadcast code>",
1184 cmd_bap_broadcast_assistant_broadcast_code, 3, 0),
1185 SHELL_CMD_ARG(rem_src, NULL, "Remove a source <src_id>",
1186 cmd_bap_broadcast_assistant_rem_src, 2, 0),
1187 SHELL_CMD_ARG(read_state, NULL, "Remove a source <index>",
1188 cmd_bap_broadcast_assistant_read_recv_state, 2, 0),
1189 SHELL_SUBCMD_SET_END);
1190
1191 SHELL_CMD_ARG_REGISTER(bap_broadcast_assistant, &bap_broadcast_assistant_cmds,
1192 "Bluetooth BAP broadcast assistant client shell commands",
1193 cmd_bap_broadcast_assistant, 1, 1);
1194