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