| /* |
| * Copyright (c) 2019 Bose Corporation |
| * Copyright (c) 2020-2022 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #ifdef CONFIG_BT_CSIP_SET_COORDINATOR |
| #include <zephyr/bluetooth/addr.h> |
| #include <zephyr/bluetooth/audio/csip.h> |
| #include "common.h" |
| |
| static bool expect_rank = true; |
| static bool expect_set_size = true; |
| static bool expect_lockable = true; |
| |
| extern enum bst_result_t bst_result; |
| static volatile bool discovered; |
| static volatile bool members_discovered; |
| static volatile bool discover_timed_out; |
| static volatile bool set_locked; |
| static volatile bool set_unlocked; |
| static volatile bool ordered_access_locked; |
| static volatile bool ordered_access_unlocked; |
| static const struct bt_csip_set_coordinator_csis_inst *inst; |
| |
| static uint8_t members_found; |
| static struct k_work_delayable discover_members_timer; |
| static bt_addr_le_t addr_found[CONFIG_BT_MAX_CONN]; |
| static struct bt_conn *conns[CONFIG_BT_MAX_CONN]; |
| static const struct bt_csip_set_coordinator_set_member *set_members[CONFIG_BT_MAX_CONN]; |
| |
| static void csip_set_coordinator_lock_set_cb(int err); |
| |
| static void csip_set_coordinator_lock_release_cb(int err) |
| { |
| printk("%s\n", __func__); |
| |
| if (err != 0) { |
| FAIL("Release sets failed (%d)\n", err); |
| return; |
| } |
| |
| set_unlocked = true; |
| } |
| |
| static void csip_set_coordinator_lock_set_cb(int err) |
| { |
| printk("%s\n", __func__); |
| |
| if (err != 0) { |
| FAIL("Lock sets failed (%d)\n", err); |
| return; |
| } |
| |
| set_locked = true; |
| } |
| |
| static void csip_discover_cb(struct bt_conn *conn, |
| const struct bt_csip_set_coordinator_set_member *member, |
| int err, size_t set_count) |
| { |
| uint8_t conn_index; |
| |
| printk("%s\n", __func__); |
| |
| if (err != 0 || set_count == 0U) { |
| FAIL("Discover failed (%d)\n", err); |
| return; |
| } |
| |
| conn_index = bt_conn_index(conn); |
| |
| for (size_t i = 0U; i < set_count; i++) { |
| const uint8_t rank = member->insts[i].info.rank; |
| const uint8_t set_size = member->insts[i].info.set_size; |
| const uint8_t lockable = member->insts[i].info.lockable; |
| |
| printk("CSIS[%zu]: %p\n", i, &member->insts[i]); |
| printk("\tRank: %u\n", rank); |
| printk("\tSet Size: %u\n", set_size); |
| printk("\tLockable: %u\n", lockable); |
| |
| if ((expect_rank && rank == 0U) || (!expect_rank && rank != 0U)) { |
| FAIL("Unexpected rank: %u %u", expect_rank, rank); |
| |
| return; |
| } |
| |
| if ((expect_set_size && set_size == 0U) || (!expect_set_size && set_size != 0U)) { |
| FAIL("Unexpected set_size: %u %u", expect_set_size, set_size); |
| |
| return; |
| } |
| |
| if (expect_lockable != lockable) { |
| FAIL("Unexpected lockable: %u %u", expect_lockable, lockable); |
| |
| return; |
| } |
| } |
| |
| inst = &member->insts[0]; |
| set_members[conn_index] = member; |
| discovered = true; |
| } |
| |
| static void csip_lock_changed_cb(struct bt_csip_set_coordinator_csis_inst *inst, |
| bool locked) |
| { |
| printk("Inst %p %s\n", inst, locked ? "locked" : "released"); |
| } |
| |
| static void 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) |
| { |
| if (err) { |
| FAIL("Ordered access failed with err %d\n", err); |
| } else if (locked) { |
| printk("Ordered access procedure locked member %p\n", member); |
| ordered_access_locked = true; |
| } else { |
| printk("Ordered access procedure finished\n"); |
| ordered_access_unlocked = true; |
| } |
| } |
| |
| static struct bt_csip_set_coordinator_cb cbs = { |
| .lock_set = csip_set_coordinator_lock_set_cb, |
| .release_set = csip_set_coordinator_lock_release_cb, |
| .discover = csip_discover_cb, |
| .lock_changed = csip_lock_changed_cb, |
| .ordered_access = csip_set_coordinator_ordered_access_cb |
| }; |
| |
| static bool 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) |
| { |
| for (size_t i = 0; i < count; i++) { |
| printk("Ordered access for members[%zu]: %p\n", i, members[i]); |
| } |
| |
| return true; |
| } |
| |
| static bool is_discovered(const bt_addr_le_t *addr) |
| { |
| for (int i = 0; i < members_found; i++) { |
| if (bt_addr_le_eq(addr, &addr_found[i])) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static bool csip_found(struct bt_data *data, void *user_data) |
| { |
| if (bt_csip_set_coordinator_is_set_member(inst->info.set_sirk, data)) { |
| const bt_addr_le_t *addr = user_data; |
| char addr_str[BT_ADDR_LE_STR_LEN]; |
| |
| bt_addr_le_to_str(addr, addr_str, sizeof(addr_str)); |
| printk("Found CSIP advertiser with address %s\n", addr_str); |
| |
| if (is_discovered(addr)) { |
| printk("Set member already found\n"); |
| /* Stop parsing */ |
| return false; |
| } |
| |
| bt_addr_le_copy(&addr_found[members_found++], addr); |
| |
| if (inst->info.set_size == 0) { |
| printk("Found member %u\n", members_found); |
| } else { |
| printk("Found member (%u / %u)\n", members_found, inst->info.set_size); |
| } |
| |
| /* Stop parsing */ |
| return false; |
| } |
| /* Continue parsing */ |
| return true; |
| } |
| |
| static void csip_set_coordinator_scan_recv(const struct bt_le_scan_recv_info *info, |
| struct net_buf_simple *ad) |
| { |
| /* We're only interested in connectable events */ |
| if (info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) { |
| if (inst == NULL) { |
| /* Scanning for the first device */ |
| if (members_found == 0) { |
| bt_addr_le_copy(&addr_found[members_found++], |
| info->addr); |
| } |
| } else { /* Scanning for set members */ |
| bt_data_parse(ad, csip_found, (void *)info->addr); |
| } |
| } |
| } |
| |
| static struct bt_le_scan_cb csip_set_coordinator_scan_callbacks = { |
| .recv = csip_set_coordinator_scan_recv |
| }; |
| |
| static void discover_members_timer_handler(struct k_work *work) |
| { |
| if (inst->info.set_size > 0) { |
| FAIL("Could not find all members (%u / %u)\n", members_found, inst->info.set_size); |
| } else { |
| discover_timed_out = true; |
| } |
| } |
| |
| static void ordered_access(const struct bt_csip_set_coordinator_set_member **members, |
| size_t count, bool expect_locked) |
| { |
| int err; |
| |
| printk("Performing ordered access, expecting %s\n", |
| expect_locked ? "locked" : "unlocked"); |
| |
| if (expect_locked) { |
| ordered_access_locked = false; |
| } else { |
| ordered_access_unlocked = false; |
| } |
| |
| err = bt_csip_set_coordinator_ordered_access(members, count, |
| &inst->info, |
| csip_set_coordinator_oap_cb); |
| if (err != 0) { |
| FAIL("Failed to do CSIP set coordinator ordered access (%d)", |
| err); |
| return; |
| } |
| |
| if (expect_locked) { |
| WAIT_FOR_COND(ordered_access_locked); |
| } else { |
| WAIT_FOR_COND(ordered_access_unlocked); |
| } |
| } |
| |
| static void test_main(void) |
| { |
| int err; |
| char addr[BT_ADDR_LE_STR_LEN]; |
| const struct bt_csip_set_coordinator_set_member *locked_members[CONFIG_BT_MAX_CONN]; |
| uint8_t connected_member_count = 0; |
| |
| err = bt_enable(NULL); |
| if (err != 0) { |
| FAIL("Bluetooth init failed (err %d)\n", err); |
| return; |
| } |
| |
| printk("Audio Client: Bluetooth initialized\n"); |
| |
| bt_csip_set_coordinator_register_cb(&cbs); |
| k_work_init_delayable(&discover_members_timer, |
| discover_members_timer_handler); |
| bt_le_scan_cb_register(&csip_set_coordinator_scan_callbacks); |
| |
| err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL); |
| if (err != 0) { |
| FAIL("Scanning failed to start (err %d)\n", err); |
| return; |
| } |
| |
| printk("Scanning successfully started\n"); |
| |
| WAIT_FOR_COND(members_found == 1); |
| |
| printk("Stopping scan\n"); |
| err = bt_le_scan_stop(); |
| if (err != 0) { |
| FAIL("Could not stop scan"); |
| return; |
| } |
| |
| bt_addr_le_to_str(&addr_found[0], addr, sizeof(addr)); |
| err = bt_conn_le_create(&addr_found[0], BT_CONN_LE_CREATE_CONN, |
| BT_LE_CONN_PARAM_DEFAULT, &conns[0]); |
| if (err != 0) { |
| FAIL("Failed to connect to %s: %d\n", err); |
| return; |
| } |
| printk("Connecting to %s\n", addr); |
| |
| WAIT_FOR_FLAG(flag_connected); |
| connected_member_count++; |
| |
| err = bt_csip_set_coordinator_discover(conns[0]); |
| if (err != 0) { |
| FAIL("Failed to initialize set coordinator for connection %d\n", |
| err); |
| return; |
| } |
| |
| WAIT_FOR_COND(discovered); |
| |
| err = bt_le_scan_start(BT_LE_SCAN_ACTIVE, NULL); |
| if (err != 0) { |
| FAIL("Could not start scan: %d", err); |
| return; |
| } |
| |
| err = k_work_reschedule(&discover_members_timer, |
| BT_CSIP_SET_COORDINATOR_DISCOVER_TIMER_VALUE); |
| if (err < 0) { /* Can return 0, 1 and 2 for success */ |
| FAIL("Could not schedule discover_members_timer %d", err); |
| return; |
| } |
| |
| if (inst->info.set_size > 0) { |
| WAIT_FOR_COND(members_found == inst->info.set_size); |
| |
| (void)k_work_cancel_delayable(&discover_members_timer); |
| } else { |
| WAIT_FOR_COND(discover_timed_out); |
| } |
| |
| err = bt_le_scan_stop(); |
| if (err != 0) { |
| FAIL("Scanning failed to stop (err %d)\n", err); |
| return; |
| } |
| |
| for (uint8_t i = 1; i < members_found; i++) { |
| bt_addr_le_to_str(&addr_found[i], addr, sizeof(addr)); |
| |
| UNSET_FLAG(flag_connected); |
| printk("Connecting to member[%d] (%s)", i, addr); |
| err = bt_conn_le_create(&addr_found[i], |
| BT_CONN_LE_CREATE_CONN, |
| BT_LE_CONN_PARAM_DEFAULT, |
| &conns[i]); |
| if (err != 0) { |
| FAIL("Failed to connect to %s: %d\n", addr, err); |
| return; |
| } |
| |
| printk("Connected to %s\n", addr); |
| WAIT_FOR_FLAG(flag_connected); |
| connected_member_count++; |
| |
| discovered = false; |
| printk("Doing discovery on member[%u]", i); |
| err = bt_csip_set_coordinator_discover(conns[i]); |
| if (err != 0) { |
| FAIL("Failed to initialize set coordinator for connection %d\n", |
| err); |
| return; |
| } |
| |
| WAIT_FOR_COND(discovered); |
| } |
| |
| for (size_t i = 0; i < ARRAY_SIZE(locked_members); i++) { |
| locked_members[i] = set_members[i]; |
| } |
| |
| if (inst->info.rank != 0U) { |
| ordered_access(locked_members, connected_member_count, false); |
| } |
| |
| if (inst->info.lockable) { |
| printk("Locking set\n"); |
| err = bt_csip_set_coordinator_lock(locked_members, connected_member_count, |
| &inst->info); |
| if (err != 0) { |
| FAIL("Failed to do set coordinator lock (%d)", err); |
| return; |
| } |
| |
| WAIT_FOR_COND(set_locked); |
| } |
| |
| if (inst->info.rank != 0U) { |
| ordered_access(locked_members, connected_member_count, inst->info.lockable); |
| } |
| |
| k_sleep(K_MSEC(1000)); /* Simulate doing stuff */ |
| |
| if (inst->info.lockable) { |
| printk("Releasing set\n"); |
| err = bt_csip_set_coordinator_release(locked_members, connected_member_count, |
| &inst->info); |
| if (err != 0) { |
| FAIL("Failed to do set coordinator release (%d)", err); |
| return; |
| } |
| |
| WAIT_FOR_COND(set_unlocked); |
| } |
| |
| if (inst->info.rank != 0U) { |
| ordered_access(locked_members, connected_member_count, false); |
| } |
| |
| if (inst->info.lockable) { |
| /* Lock and unlock again */ |
| set_locked = false; |
| set_unlocked = false; |
| |
| printk("Locking set\n"); |
| err = bt_csip_set_coordinator_lock(locked_members, connected_member_count, |
| &inst->info); |
| if (err != 0) { |
| FAIL("Failed to do set coordinator lock (%d)", err); |
| return; |
| } |
| |
| WAIT_FOR_COND(set_locked); |
| } |
| |
| k_sleep(K_MSEC(1000)); /* Simulate doing stuff */ |
| |
| if (inst->info.lockable) { |
| printk("Releasing set\n"); |
| err = bt_csip_set_coordinator_release(locked_members, connected_member_count, |
| &inst->info); |
| if (err != 0) { |
| FAIL("Failed to do set coordinator release (%d)", err); |
| return; |
| } |
| |
| WAIT_FOR_COND(set_unlocked); |
| } |
| |
| for (uint8_t i = 0; i < members_found; i++) { |
| printk("Disconnecting member[%u] (%s)", i, addr); |
| err = bt_conn_disconnect(conns[i], |
| BT_HCI_ERR_REMOTE_USER_TERM_CONN); |
| (void)memset(&set_members[i], 0, sizeof(set_members[i])); |
| if (err != 0) { |
| FAIL("Failed to do disconnect\n", err); |
| return; |
| } |
| } |
| |
| PASS("All members disconnected\n"); |
| } |
| |
| static void test_args(int argc, char *argv[]) |
| { |
| for (int argn = 0; argn < argc; argn++) { |
| const char *arg = argv[argn]; |
| |
| if (strcmp(arg, "no-size") == 0) { |
| expect_set_size = false; |
| } else if (strcmp(arg, "no-rank") == 0) { |
| expect_rank = false; |
| } else if (strcmp(arg, "no-lock") == 0) { |
| expect_lockable = false; |
| } else { |
| FAIL("Invalid arg: %s", arg); |
| } |
| } |
| } |
| |
| static const struct bst_test_instance test_connect[] = { |
| |
| { |
| .test_id = "csip_set_coordinator", |
| .test_post_init_f = test_init, |
| .test_tick_f = test_tick, |
| .test_main_f = test_main, |
| .test_args_f = test_args, |
| }, |
| |
| BSTEST_END_MARKER}; |
| |
| struct bst_test_list *test_csip_set_coordinator_install(struct bst_test_list *tests) |
| { |
| return bst_add_tests(tests, test_connect); |
| } |
| #else |
| struct bst_test_list *test_csip_set_coordinator_install(struct bst_test_list *tests) |
| { |
| return tests; |
| } |
| |
| #endif /* CONFIG_BT_CSIP_SET_COORDINATOR */ |