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