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