1 /**
2 * @file
3 * @brief Shell APIs for Bluetooth CSIP set coordinator
4 *
5 * Copyright (c) 2020 Bose Corporation
6 * Copyright (c) 2021-2022 Nordic Semiconductor ASA
7 *
8 * SPDX-License-Identifier: Apache-2.0
9 */
10
11 #include <errno.h>
12 #include <stdbool.h>
13 #include <stddef.h>
14 #include <stdint.h>
15 #include <stdlib.h>
16 #include <string.h>
17
18 #include <zephyr/autoconf.h>
19 #include <zephyr/bluetooth/audio/csip.h>
20 #include <zephyr/bluetooth/addr.h>
21 #include <zephyr/bluetooth/conn.h>
22 #include <zephyr/bluetooth/gap.h>
23 #include <zephyr/bluetooth/gatt.h>
24 #include <zephyr/bluetooth/bluetooth.h>
25 #include <zephyr/net_buf.h>
26 #include <zephyr/shell/shell_string_conv.h>
27 #include <zephyr/sys/printk.h>
28 #include <zephyr/sys/util.h>
29 #include <zephyr/types.h>
30 #include <zephyr/kernel.h>
31 #include <zephyr/types.h>
32 #include <zephyr/shell/shell.h>
33
34 #include "host/shell/bt.h"
35 #include "common/bt_shell_private.h"
36
37 static uint8_t members_found;
38 static struct k_work_delayable discover_members_timer;
39 static struct bt_conn *conns[CONFIG_BT_MAX_CONN];
40 static const struct bt_csip_set_coordinator_set_member *set_members[CONFIG_BT_MAX_CONN];
41 static struct bt_csip_set_coordinator_csis_inst *cur_inst;
42 static bt_addr_le_t addr_found[CONFIG_BT_MAX_CONN];
43 static const struct bt_csip_set_coordinator_set_member *locked_members[CONFIG_BT_MAX_CONN];
44
is_discovered(const bt_addr_le_t * addr)45 static bool is_discovered(const bt_addr_le_t *addr)
46 {
47 for (int i = 0; i < members_found; i++) {
48 if (bt_addr_le_eq(addr, &addr_found[i])) {
49 return true;
50 }
51 }
52 return false;
53 }
54
connected_cb(struct bt_conn * conn,uint8_t err)55 static void connected_cb(struct bt_conn *conn, uint8_t err)
56 {
57 char addr[BT_ADDR_LE_STR_LEN];
58 uint8_t conn_index;
59
60 bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
61
62 if (err != 0) {
63 bt_shell_error("Failed to connect to %s (%u)", addr, err);
64 return;
65 }
66
67 conn_index = bt_conn_index(conn);
68
69 bt_shell_print("[%u]: Connected to %s", conn_index, addr);
70
71 /* TODO: Handle RPAs */
72
73 conns[conn_index] = bt_conn_ref(conn);
74 bt_shell_print("Member[%u] connected", conn_index);
75 }
76
disconnected_cb(struct bt_conn * conn,uint8_t reason)77 static void disconnected_cb(struct bt_conn *conn, uint8_t reason)
78 {
79 uint8_t conn_index = bt_conn_index(conn);
80
81 bt_conn_unref(conns[conn_index]);
82 conns[conn_index] = NULL;
83 }
84
85 BT_CONN_CB_DEFINE(conn_callbacks) = {
86 .connected = connected_cb,
87 .disconnected = disconnected_cb
88 };
89
csip_discover_cb(struct bt_conn * conn,const struct bt_csip_set_coordinator_set_member * member,int err,size_t set_count)90 static void csip_discover_cb(struct bt_conn *conn,
91 const struct bt_csip_set_coordinator_set_member *member,
92 int err, size_t set_count)
93 {
94 uint8_t conn_index;
95
96 if (err != 0) {
97 bt_shell_error("discover failed (%d)", err);
98 return;
99 }
100
101 if (set_count == 0) {
102 bt_shell_warn("Device has no sets");
103 return;
104 }
105
106 conn_index = bt_conn_index(conn);
107
108 bt_shell_print("Found %zu sets on member[%u]", set_count, conn_index);
109
110 for (size_t i = 0U; i < set_count; i++) {
111 bt_shell_print("CSIS[%zu]: %p", i, &member->insts[i]);
112 bt_shell_print("\tRank: %u", member->insts[i].info.rank);
113 bt_shell_print("\tSet Size: %u", member->insts[i].info.set_size);
114 bt_shell_print("\tLockable: %u", member->insts[i].info.lockable);
115 }
116
117 set_members[conn_index] = member;
118 }
119
csip_set_coordinator_lock_set_cb(int err)120 static void csip_set_coordinator_lock_set_cb(int err)
121 {
122 if (err != 0) {
123 bt_shell_error("Lock sets failed (%d)", err);
124 return;
125 }
126
127 bt_shell_print("Set locked");
128 }
129
csip_set_coordinator_release_set_cb(int err)130 static void csip_set_coordinator_release_set_cb(int err)
131 {
132 if (err != 0) {
133 bt_shell_error("Lock sets failed (%d)", err);
134 return;
135 }
136
137 bt_shell_print("Set released");
138 }
139
csip_set_coordinator_ordered_access_cb(const struct bt_csip_set_coordinator_set_info * set_info,int err,bool locked,struct bt_csip_set_coordinator_set_member * member)140 static void csip_set_coordinator_ordered_access_cb(
141 const struct bt_csip_set_coordinator_set_info *set_info, int err,
142 bool locked, struct bt_csip_set_coordinator_set_member *member)
143 {
144 if (err) {
145 printk("Ordered access failed with err %d\n", err);
146 } else if (locked) {
147 printk("Cannot do ordered access as member %p is locked\n",
148 member);
149 } else {
150 printk("Ordered access procedure finished\n");
151 }
152 }
153
csip_set_coordinator_lock_changed_cb(struct bt_csip_set_coordinator_csis_inst * inst,bool locked)154 static void csip_set_coordinator_lock_changed_cb(struct bt_csip_set_coordinator_csis_inst *inst,
155 bool locked)
156 {
157 bt_shell_print("Inst %p %s\n", inst, locked ? "locked" : "released");
158 }
159
csip_set_coordinator_sirk_changed_cb(struct bt_csip_set_coordinator_csis_inst * inst)160 static void csip_set_coordinator_sirk_changed_cb(struct bt_csip_set_coordinator_csis_inst *inst)
161 {
162 bt_shell_print("Inst %p SIRK changed\n", inst);
163 bt_shell_hexdump(inst->info.sirk, BT_CSIP_SIRK_SIZE);
164 }
165
166 static void
csip_set_coordinator_size_changed_cb(struct bt_conn * conn,const struct bt_csip_set_coordinator_csis_inst * inst)167 csip_set_coordinator_size_changed_cb(struct bt_conn *conn,
168 const struct bt_csip_set_coordinator_csis_inst *inst)
169 {
170 bt_shell_print("Inst %p size changed: %u\n", inst, inst->info.set_size);
171 }
172
173 static struct bt_csip_set_coordinator_cb cbs = {
174 .lock_set = csip_set_coordinator_lock_set_cb,
175 .release_set = csip_set_coordinator_release_set_cb,
176 .discover = csip_discover_cb,
177 .ordered_access = csip_set_coordinator_ordered_access_cb,
178 .lock_changed = csip_set_coordinator_lock_changed_cb,
179 .sirk_changed = csip_set_coordinator_sirk_changed_cb,
180 .size_changed = csip_set_coordinator_size_changed_cb,
181 };
182
csip_set_coordinator_oap_cb(const struct bt_csip_set_coordinator_set_info * set_info,struct bt_csip_set_coordinator_set_member * members[],size_t count)183 static bool csip_set_coordinator_oap_cb(const struct bt_csip_set_coordinator_set_info *set_info,
184 struct bt_csip_set_coordinator_set_member *members[],
185 size_t count)
186 {
187 for (size_t i = 0; i < count; i++) {
188 printk("Ordered access for members[%zu]: %p\n", i, members[i]);
189 }
190
191 return true;
192 }
193
194 static bool csip_found(struct bt_data *data, void *user_data);
csip_set_coordinator_scan_recv(const struct bt_le_scan_recv_info * info,struct net_buf_simple * ad)195 static void csip_set_coordinator_scan_recv(const struct bt_le_scan_recv_info *info,
196 struct net_buf_simple *ad)
197 {
198 /* We're only interested in connectable events */
199 if (info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) {
200 if (!passes_scan_filter(info, ad)) {
201 return;
202 }
203
204 if (cur_inst != NULL) {
205 bt_data_parse(ad, csip_found, (void *)info->addr);
206 }
207 }
208 }
209
210 static struct bt_le_scan_cb csip_set_coordinator_scan_callbacks = {
211 .recv = csip_set_coordinator_scan_recv
212 };
213
csip_found(struct bt_data * data,void * user_data)214 static bool csip_found(struct bt_data *data, void *user_data)
215 {
216 if (bt_csip_set_coordinator_is_set_member(cur_inst->info.sirk, data)) {
217 bt_addr_le_t *addr = user_data;
218 char addr_str[BT_ADDR_LE_STR_LEN];
219
220 bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
221 bt_shell_print("Found CSIP advertiser with address %s", addr_str);
222
223 if (is_discovered(addr)) {
224 bt_shell_print("Set member already found");
225 /* Stop parsing */
226 return false;
227 }
228
229 bt_addr_le_copy(&addr_found[members_found++], addr);
230
231 bt_shell_print("Found member (%u / %u)",
232 members_found, cur_inst->info.set_size);
233
234 if (members_found == cur_inst->info.set_size) {
235 int err;
236
237 (void)k_work_cancel_delayable(&discover_members_timer);
238
239 bt_le_scan_cb_unregister(&csip_set_coordinator_scan_callbacks);
240
241 err = bt_le_scan_stop();
242 if (err != 0) {
243 bt_shell_error("Failed to stop scan: %d", err);
244 }
245 }
246
247 /* Stop parsing */
248 return false;
249 }
250 /* Continue parsing */
251 return true;
252 }
253
discover_members_timer_handler(struct k_work * work)254 static void discover_members_timer_handler(struct k_work *work)
255 {
256 int err;
257
258 bt_shell_error("Could not find all members (%u / %u)",
259 members_found, cur_inst->info.set_size);
260
261 bt_le_scan_cb_unregister(&csip_set_coordinator_scan_callbacks);
262
263 err = bt_le_scan_stop();
264 if (err != 0) {
265 bt_shell_error("Failed to stop scan: %d", err);
266 }
267 }
268
cmd_csip_set_coordinator_discover(const struct shell * sh,size_t argc,char * argv[])269 static int cmd_csip_set_coordinator_discover(const struct shell *sh,
270 size_t argc, char *argv[])
271 {
272 unsigned long member_index = 0U;
273 char addr[BT_ADDR_LE_STR_LEN];
274 static bool initialized;
275 struct bt_conn *conn;
276 int err = 0;
277
278 if (!initialized) {
279 k_work_init_delayable(&discover_members_timer,
280 discover_members_timer_handler);
281 bt_csip_set_coordinator_register_cb(&cbs);
282 initialized = true;
283 }
284
285 if (argc > 1) {
286 member_index = shell_strtoul(argv[1], 0, &err);
287 if (err != 0) {
288 shell_error(sh, "Could not parse member_index: %d",
289 err);
290
291 return -ENOEXEC;
292 }
293
294 if (member_index > ARRAY_SIZE(conns)) {
295 shell_error(sh, "Invalid member_index: %lu",
296 member_index);
297
298 return -ENOEXEC;
299 }
300 }
301
302 conn = conns[member_index];
303
304 bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
305
306 shell_print(sh, "Discovering for member[%u] (%s)",
307 (uint8_t)member_index, addr);
308 err = bt_csip_set_coordinator_discover(conn);
309 if (err != 0) {
310 shell_error(sh, "Fail: %d", err);
311 }
312
313 return err;
314 }
315
cmd_csip_set_coordinator_discover_members(const struct shell * sh,size_t argc,char * argv[])316 static int cmd_csip_set_coordinator_discover_members(const struct shell *sh,
317 size_t argc, char *argv[])
318 {
319 int err;
320
321 cur_inst = (struct bt_csip_set_coordinator_csis_inst *)strtol(argv[1], NULL, 0);
322
323 if (cur_inst == NULL) {
324 shell_error(sh, "NULL set");
325 return -EINVAL;
326 }
327
328 if (cur_inst->info.set_size > ARRAY_SIZE(set_members)) {
329 /*
330 * TODO Handle case where set size is larger than
331 * number of possible connections
332 */
333 shell_error(sh,
334 "Set size (%u) larger than max connections (%u)",
335 cur_inst->info.set_size, ARRAY_SIZE(set_members));
336 return -EINVAL;
337 }
338
339 /* Reset and populate based on current connections */
340 memset(addr_found, 0, sizeof(addr_found));
341 members_found = 0;
342 for (size_t i = 0U; i < ARRAY_SIZE(set_members); i++) {
343 const struct bt_csip_set_coordinator_set_member *set_member = set_members[i];
344
345 if (set_member == NULL) {
346 continue;
347 }
348
349 for (size_t j = 0U; j < ARRAY_SIZE(set_members[i]->insts); j++) {
350 const struct bt_csip_set_coordinator_csis_inst *inst =
351 &set_members[i]->insts[j];
352
353 if (memcmp(inst->info.sirk, cur_inst->info.sirk, BT_CSIP_SIRK_SIZE) == 0) {
354 bt_addr_le_copy(&addr_found[members_found++],
355 bt_conn_get_dst(conns[i]));
356 break;
357 }
358 }
359 }
360
361 if (cur_inst->info.set_size > 0) {
362 if (members_found == cur_inst->info.set_size) {
363 shell_print(sh, "All members already known");
364
365 return 0;
366 } else if (members_found > cur_inst->info.set_size) {
367 shell_error(sh, "Found %u members but set size is %u", members_found,
368 cur_inst->info.set_size);
369
370 return -ENOEXEC;
371 }
372 }
373
374 shell_print(sh, "Already know %u/%u members, start scanning for remaining", members_found,
375 cur_inst->info.set_size);
376
377 err = k_work_reschedule(&discover_members_timer,
378 BT_CSIP_SET_COORDINATOR_DISCOVER_TIMER_VALUE);
379 if (err < 0) { /* Can return 0, 1 and 2 for success */
380 shell_error(sh,
381 "Could not schedule discover_members_timer %d",
382 err);
383 return err;
384 }
385
386 bt_le_scan_cb_register(&csip_set_coordinator_scan_callbacks);
387
388 err = bt_le_scan_start(BT_LE_SCAN_ACTIVE, NULL);
389 if (err != 0) {
390 shell_error(sh, "Could not start scan: %d", err);
391 }
392
393 return err;
394 }
395
396 #if defined(CONFIG_BT_BONDABLE)
cmd_csip_set_coordinator_lock_set(const struct shell * sh,size_t argc,char * argv[])397 static int cmd_csip_set_coordinator_lock_set(const struct shell *sh,
398 size_t argc, char *argv[])
399 {
400 int err;
401 int conn_count = 0;
402
403 if (cur_inst == NULL) {
404 shell_error(sh, "No set selected");
405 return -ENOEXEC;
406 }
407
408 for (size_t i = 0; i < ARRAY_SIZE(locked_members); i++) {
409 if (set_members[i] != NULL) {
410 locked_members[conn_count++] = set_members[i];
411 }
412 }
413
414 err = bt_csip_set_coordinator_lock(locked_members, conn_count,
415 &cur_inst->info);
416 if (err != 0) {
417 shell_error(sh, "Fail: %d", err);
418 }
419
420 return err;
421 }
422
cmd_csip_set_coordinator_release_set(const struct shell * sh,size_t argc,char * argv[])423 static int cmd_csip_set_coordinator_release_set(const struct shell *sh,
424 size_t argc, char *argv[])
425 {
426 int err;
427 int conn_count = 0;
428
429 if (cur_inst == NULL) {
430 shell_error(sh, "No set selected");
431 return -ENOEXEC;
432 }
433
434 for (size_t i = 0; i < ARRAY_SIZE(locked_members); i++) {
435 if (set_members[i] != NULL) {
436 locked_members[conn_count++] = set_members[i];
437 }
438 }
439
440 err = bt_csip_set_coordinator_release(locked_members, conn_count,
441 &cur_inst->info);
442 if (err != 0) {
443 shell_error(sh, "Fail: %d", err);
444 }
445
446 return err;
447 }
448
cmd_csip_set_coordinator_lock(const struct shell * sh,size_t argc,char * argv[])449 static int cmd_csip_set_coordinator_lock(const struct shell *sh, size_t argc,
450 char *argv[])
451 {
452 int err;
453 unsigned long member_index = 0;
454 const struct bt_csip_set_coordinator_set_member *lock_member[1];
455
456 if (cur_inst == NULL) {
457 shell_error(sh, "No set selected");
458 return -ENOEXEC;
459 }
460
461 if (argc > 1) {
462 member_index = shell_strtoul(argv[1], 0, &err);
463 if (err != 0) {
464 shell_error(sh, "Could not parse member_index: %d",
465 err);
466
467 return -ENOEXEC;
468 }
469
470 if (member_index > ARRAY_SIZE(set_members)) {
471 shell_error(sh, "Invalid member_index: %lu",
472 member_index);
473
474 return -ENOEXEC;
475 }
476 }
477
478 lock_member[0] = set_members[member_index];
479
480 err = bt_csip_set_coordinator_lock(lock_member, 1, &cur_inst->info);
481 if (err != 0) {
482 shell_error(sh, "Fail: %d", err);
483 }
484
485 return err;
486 }
487
cmd_csip_set_coordinator_release(const struct shell * sh,size_t argc,char * argv[])488 static int cmd_csip_set_coordinator_release(const struct shell *sh, size_t argc,
489 char *argv[])
490 {
491 int err;
492 unsigned long member_index = 0;
493 const struct bt_csip_set_coordinator_set_member *lock_member[1];
494
495 if (cur_inst == NULL) {
496 shell_error(sh, "No set selected");
497 return -ENOEXEC;
498 }
499
500 if (argc > 1) {
501 member_index = shell_strtoul(argv[1], 0, &err);
502 if (err != 0) {
503 shell_error(sh, "Could not parse member_index: %d",
504 err);
505
506 return -ENOEXEC;
507 }
508
509 if (member_index > ARRAY_SIZE(set_members)) {
510 shell_error(sh, "Invalid member_index: %lu",
511 member_index);
512
513 return -ENOEXEC;
514 }
515 }
516
517 lock_member[0] = set_members[member_index];
518
519 err = bt_csip_set_coordinator_release(lock_member, 1, &cur_inst->info);
520 if (err != 0) {
521 shell_error(sh, "Fail: %d", err);
522 }
523
524 return err;
525 }
526 #endif /* CONFIG_BT_BONDABLE */
527
cmd_csip_set_coordinator_ordered_access(const struct shell * sh,size_t argc,char * argv[])528 static int cmd_csip_set_coordinator_ordered_access(const struct shell *sh,
529 size_t argc, char *argv[])
530 {
531 int err;
532 unsigned long member_count = (unsigned long)ARRAY_SIZE(set_members);
533 const struct bt_csip_set_coordinator_set_member *members[ARRAY_SIZE(set_members)];
534
535 if (argc > 1) {
536 member_count = shell_strtoul(argv[1], 0, &err);
537 if (err != 0) {
538 shell_error(sh, "Could not parse member_count: %d",
539 err);
540
541 return -ENOEXEC;
542 }
543
544 if (member_count > ARRAY_SIZE(members)) {
545 shell_error(sh, "Invalid member_count: %lu",
546 member_count);
547
548 return -ENOEXEC;
549 }
550 }
551
552 for (size_t i = 0; i < (size_t)member_count; i++) {
553 members[i] = set_members[i];
554 }
555
556 err = bt_csip_set_coordinator_ordered_access(members,
557 ARRAY_SIZE(members),
558 &cur_inst->info,
559 csip_set_coordinator_oap_cb);
560 if (err != 0) {
561 shell_error(sh, "Fail: %d", err);
562 }
563
564 return err;
565 }
566
cmd_csip_set_coordinator(const struct shell * sh,size_t argc,char ** argv)567 static int cmd_csip_set_coordinator(const struct shell *sh, size_t argc,
568 char **argv)
569 {
570 if (argc > 1) {
571 shell_error(sh, "%s unknown parameter: %s",
572 argv[0], argv[1]);
573 } else {
574 shell_error(sh, "%s Missing subcommand", argv[0]);
575 }
576
577 return -ENOEXEC;
578 }
579
580 SHELL_STATIC_SUBCMD_SET_CREATE(csip_set_coordinator_cmds,
581 SHELL_CMD_ARG(discover, NULL,
582 "Run discover for CSIS on peer device [member_index]",
583 cmd_csip_set_coordinator_discover, 1, 1),
584 SHELL_CMD_ARG(discover_members, NULL,
585 "Scan for set members <set_pointer>",
586 cmd_csip_set_coordinator_discover_members, 2, 0),
587 #if defined(CONFIG_BT_BONDABLE)
588 SHELL_CMD_ARG(lock_set, NULL,
589 "Lock set",
590 cmd_csip_set_coordinator_lock_set, 1, 0),
591 SHELL_CMD_ARG(release_set, NULL,
592 "Release set",
593 cmd_csip_set_coordinator_release_set, 1, 0),
594 SHELL_CMD_ARG(lock, NULL,
595 "Lock specific member [member_index]",
596 cmd_csip_set_coordinator_lock, 1, 1),
597 SHELL_CMD_ARG(release, NULL,
598 "Release specific member [member_index]",
599 cmd_csip_set_coordinator_release, 1, 1),
600 #endif /* CONFIG_BT_BONDABLE */
601 SHELL_CMD_ARG(ordered_access, NULL,
602 "Perform dummy ordered access procedure [member_count]",
603 cmd_csip_set_coordinator_ordered_access, 1, 1),
604 SHELL_SUBCMD_SET_END
605 );
606
607 SHELL_CMD_ARG_REGISTER(csip_set_coordinator, &csip_set_coordinator_cmds,
608 "Bluetooth csip_set_coordinator shell commands",
609 cmd_csip_set_coordinator, 1, 1);
610