/* * Copyright (c) 2021-2022 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bstests.h" #include "common.h" #ifdef CONFIG_BT_BAP_SCAN_DELEGATOR extern enum bst_result_t bst_result; #define PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO 20 /* Set the timeout relative to interval */ #define PA_SYNC_SKIP 5 CREATE_FLAG(flag_pa_synced); CREATE_FLAG(flag_pa_terminated); CREATE_FLAG(flag_broadcast_code_received); CREATE_FLAG(flag_recv_state_updated); CREATE_FLAG(flag_bis_sync_requested); CREATE_FLAG(flag_bis_sync_term_requested); static volatile uint32_t g_broadcast_id; struct sync_state { uint8_t src_id; const struct bt_bap_scan_delegator_recv_state *recv_state; bool pa_syncing; struct k_work_delayable pa_timer; struct bt_le_per_adv_sync *pa_sync; uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE]; uint32_t bis_sync_req[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS]; } sync_states[CONFIG_BT_BAP_SCAN_DELEGATOR_RECV_STATE_COUNT]; static struct sync_state *sync_state_get(const struct bt_bap_scan_delegator_recv_state *recv_state) { for (size_t i = 0U; i < ARRAY_SIZE(sync_states); i++) { if (sync_states[i].recv_state == recv_state) { return &sync_states[i]; } } return NULL; } static struct sync_state *sync_state_get_or_new( const struct bt_bap_scan_delegator_recv_state *recv_state) { struct sync_state *free_state = NULL; for (size_t i = 0U; i < ARRAY_SIZE(sync_states); i++) { if (sync_states[i].recv_state == NULL && free_state == NULL) { free_state = &sync_states[i]; if (recv_state == NULL) { return free_state; } } if (sync_states[i].recv_state == recv_state) { return &sync_states[i]; } } return free_state; } static struct sync_state *sync_state_get_by_pa(struct bt_le_per_adv_sync *sync) { for (size_t i = 0U; i < ARRAY_SIZE(sync_states); i++) { if (sync_states[i].pa_sync == sync) { return &sync_states[i]; } } return NULL; } static struct sync_state *sync_state_get_by_src_id(uint8_t src_id) { for (size_t i = 0U; i < ARRAY_SIZE(sync_states); i++) { if (sync_states[i].src_id == src_id) { return &sync_states[i]; } } return NULL; } static void pa_timer_handler(struct k_work *work) { struct k_work_delayable *dwork = k_work_delayable_from_work(work); struct sync_state *state = CONTAINER_OF(dwork, struct sync_state, pa_timer); state->pa_syncing = false; if (state->recv_state != NULL) { enum bt_bap_pa_state pa_state; if (state->recv_state->pa_sync_state == BT_BAP_PA_STATE_INFO_REQ) { pa_state = BT_BAP_PA_STATE_NO_PAST; } else { pa_state = BT_BAP_PA_STATE_FAILED; } bt_bap_scan_delegator_set_pa_state(state->recv_state->src_id, pa_state); } FAIL("PA timeout\n"); } static int pa_sync_past(struct bt_conn *conn, struct sync_state *state, uint16_t pa_interval) { struct bt_le_per_adv_sync_transfer_param param = { 0 }; int err; param.options = BT_LE_PER_ADV_SYNC_TRANSFER_OPT_FILTER_DUPLICATES; param.skip = PA_SYNC_SKIP; param.timeout = interval_to_sync_timeout(pa_interval); err = bt_le_per_adv_sync_transfer_subscribe(conn, ¶m); if (err != 0) { printk("Could not do PAST subscribe: %d\n", err); } else { printk("Syncing with PAST: %d\n", err); state->pa_syncing = true; k_work_init_delayable(&state->pa_timer, pa_timer_handler); (void)k_work_reschedule(&state->pa_timer, K_MSEC(param.timeout * 10)); } return err; } static int pa_sync_no_past(struct sync_state *state, uint16_t pa_interval) { const struct bt_bap_scan_delegator_recv_state *recv_state; struct bt_le_per_adv_sync_param param = { 0 }; int err; recv_state = state->recv_state; state->src_id = recv_state->src_id; bt_addr_le_copy(¶m.addr, &recv_state->addr); param.sid = recv_state->adv_sid; param.skip = PA_SYNC_SKIP; param.timeout = interval_to_sync_timeout(pa_interval); /* TODO: Validate that the advertise is broadcasting the same * broadcast_id that the receive state has */ err = bt_le_per_adv_sync_create(¶m, &state->pa_sync); if (err != 0) { printk("Could not sync per adv: %d\n", err); } else { char addr_str[BT_ADDR_LE_STR_LEN]; bt_addr_le_to_str(&recv_state->addr, addr_str, sizeof(addr_str)); printk("PA sync pending for addr %s\n", addr_str); state->pa_syncing = true; k_work_init_delayable(&state->pa_timer, pa_timer_handler); (void)k_work_reschedule(&state->pa_timer, K_MSEC(param.timeout * 10)); } return err; } static int pa_sync_term(struct sync_state *state) { int err; (void)k_work_cancel_delayable(&state->pa_timer); if (state->pa_sync == NULL) { return -1; } printk("Deleting PA sync\n"); err = bt_le_per_adv_sync_delete(state->pa_sync); if (err != 0) { FAIL("Could not delete per adv sync: %d\n", err); } else { state->pa_syncing = false; state->pa_sync = NULL; } return err; } static void recv_state_updated_cb(struct bt_conn *conn, const struct bt_bap_scan_delegator_recv_state *recv_state) { struct sync_state *state; printk("Receive state with ID %u updated\n", recv_state->src_id); state = sync_state_get_by_src_id(recv_state->src_id); if (state == NULL) { FAIL("Could not get state"); return; } if (state->recv_state != NULL) { if (state->recv_state != recv_state) { FAIL("Sync state receive state mismatch: %p - %p", state->recv_state, recv_state); return; } } else { state->recv_state = recv_state; } SET_FLAG(flag_recv_state_updated); } static int pa_sync_req_cb(struct bt_conn *conn, const struct bt_bap_scan_delegator_recv_state *recv_state, bool past_avail, uint16_t pa_interval) { struct sync_state *state; int err; printk("PA Sync request: past_avail %u, pa_interval 0x%04x\n: %p", past_avail, pa_interval, recv_state); state = sync_state_get_or_new(recv_state); if (state == NULL) { FAIL("Could not get state"); return -1; } state->recv_state = recv_state; state->src_id = recv_state->src_id; if (recv_state->pa_sync_state == BT_BAP_PA_STATE_SYNCED || recv_state->pa_sync_state == BT_BAP_PA_STATE_INFO_REQ) { /* Already syncing */ /* TODO: Terminate existing sync and then sync to new?*/ return -1; } if (past_avail) { err = pa_sync_past(conn, state, pa_interval); if (err == 0) { err = bt_bap_scan_delegator_set_pa_state(state->recv_state->src_id, BT_BAP_PA_STATE_INFO_REQ); if (err != 0) { printk("Failed to set INFO_REQ state: %d", err); } } } else { err = pa_sync_no_past(state, pa_interval); } return err; } static int pa_sync_term_req_cb(struct bt_conn *conn, const struct bt_bap_scan_delegator_recv_state *recv_state) { struct sync_state *state; printk("PA Sync term request for %p\n", recv_state); state = sync_state_get(recv_state); if (state == NULL) { FAIL("Could not get state\n"); return -1; } return pa_sync_term(state); } static void broadcast_code_cb(struct bt_conn *conn, const struct bt_bap_scan_delegator_recv_state *recv_state, const uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE]) { struct sync_state *state; printk("Broadcast code received for %p\n", recv_state); state = sync_state_get(recv_state); if (state == NULL) { FAIL("Could not get state\n"); return; } (void)memcpy(state->broadcast_code, broadcast_code, BT_ISO_BROADCAST_CODE_SIZE); SET_FLAG(flag_broadcast_code_received); } static int bis_sync_req_cb(struct bt_conn *conn, const struct bt_bap_scan_delegator_recv_state *recv_state, const uint32_t bis_sync_req[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS]) { struct sync_state *state; bool sync_bis; printk("BIS sync request received for %p\n", recv_state); for (int i = 0; i < CONFIG_BT_BAP_BASS_MAX_SUBGROUPS; i++) { if (bis_sync_req[i]) { sync_bis = true; } printk(" [%d]: 0x%08x\n", i, bis_sync_req[i]); } state = sync_state_get(recv_state); if (state == NULL) { FAIL("Could not get state\n"); return -1; } (void)memcpy(state->bis_sync_req, bis_sync_req, sizeof(state->bis_sync_req)); if (sync_bis) { SET_FLAG(flag_bis_sync_requested); } else { SET_FLAG(flag_bis_sync_term_requested); } return 0; } static struct bt_bap_scan_delegator_cb scan_delegator_cb = { .recv_state_updated = recv_state_updated_cb, .pa_sync_req = pa_sync_req_cb, .pa_sync_term_req = pa_sync_term_req_cb, .broadcast_code = broadcast_code_cb, .bis_sync_req = bis_sync_req_cb, }; static void pa_synced_cb(struct bt_le_per_adv_sync *sync, struct bt_le_per_adv_sync_synced_info *info) { struct sync_state *state; printk("PA %p synced\n", sync); if (info->conn) { /* if from PAST */ for (size_t i = 0U; i < ARRAY_SIZE(sync_states); i++) { if (!sync_states[i].pa_sync) { sync_states[i].pa_sync = sync; } } } state = sync_state_get_by_pa(sync); if (state == NULL) { FAIL("Could not get sync state from PA sync %p\n", sync); return; } k_work_cancel_delayable(&state->pa_timer); SET_FLAG(flag_pa_synced); } static void pa_term_cb(struct bt_le_per_adv_sync *sync, const struct bt_le_per_adv_sync_term_info *info) { struct sync_state *state; printk("PA %p sync terminated\n", sync); state = sync_state_get_by_pa(sync); if (state == NULL) { FAIL("Could not get sync state from PA sync %p\n", sync); return; } k_work_cancel_delayable(&state->pa_timer); SET_FLAG(flag_pa_terminated); } static struct bt_le_per_adv_sync_cb pa_sync_cb = { .synced = pa_synced_cb, .term = pa_term_cb, }; static bool broadcast_source_found(struct bt_data *data, void *user_data) { struct bt_le_per_adv_sync_param sync_create_param = { 0 }; const struct bt_le_scan_recv_info *info = user_data; char addr_str[BT_ADDR_LE_STR_LEN]; struct bt_uuid_16 adv_uuid; struct sync_state *state; int err; if (data->type != BT_DATA_SVC_DATA16) { return true; } if (data->data_len < BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE) { return true; } if (!bt_uuid_create(&adv_uuid.uuid, data->data, BT_UUID_SIZE_16)) { return true; } if (bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_BROADCAST_AUDIO) != 0) { return true; } g_broadcast_id = sys_get_le24(data->data + BT_UUID_SIZE_16); bt_addr_le_to_str(info->addr, addr_str, sizeof(addr_str)); printk("Found BAP broadcast source with address %s and ID 0x%06X\n", addr_str, g_broadcast_id); state = sync_state_get_or_new(NULL); if (state == NULL) { FAIL("Failed to get sync state"); return true; } printk("Creating Periodic Advertising Sync\n"); bt_addr_le_copy(&sync_create_param.addr, info->addr); sync_create_param.sid = info->sid; sync_create_param.timeout = 0xa; err = bt_le_per_adv_sync_create(&sync_create_param, &state->pa_sync); if (err != 0) { FAIL("Could not create PA sync (err %d)\n", err); return true; } state->pa_syncing = true; return false; } static void scan_recv_cb(const struct bt_le_scan_recv_info *info, struct net_buf_simple *ad) { /* We are only interested in non-connectable periodic advertisers */ if ((info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) != 0 || info->interval == 0) { return; } bt_data_parse(ad, broadcast_source_found, (void *)info); } static struct bt_le_scan_cb scan_cb = { .recv = scan_recv_cb }; static int add_source(struct sync_state *state) { struct bt_bap_scan_delegator_add_src_param param; struct bt_le_per_adv_sync_info sync_info; int res; UNSET_FLAG(flag_recv_state_updated); res = bt_le_per_adv_sync_get_info(state->pa_sync, &sync_info); if (res != 0) { FAIL("Failed to get PA sync info: %d)\n", res); return true; } bt_addr_le_copy(¶m.addr, &sync_info.addr); param.sid = sync_info.sid; param.encrypt_state = BT_BAP_BIG_ENC_STATE_NO_ENC; param.broadcast_id = g_broadcast_id; param.num_subgroups = 1U; for (uint8_t i = 0U; i < param.num_subgroups; i++) { struct bt_bap_bass_subgroup *subgroup_param = ¶m.subgroups[i]; subgroup_param->bis_sync = BT_BAP_BIS_SYNC_NO_PREF; subgroup_param->metadata_len = 0U; (void)memset(subgroup_param->metadata, 0, sizeof(subgroup_param->metadata)); } res = bt_bap_scan_delegator_add_src(¶m); if (res < 0) { return res; } state->src_id = (uint8_t)res; WAIT_FOR_FLAG(flag_recv_state_updated); return res; } static void add_all_sources(void) { for (size_t i = 0U; i < ARRAY_SIZE(sync_states); i++) { struct sync_state *state = &sync_states[i]; if (state->pa_sync != NULL) { int res; printk("[%zu]: Adding source\n", i); res = add_source(state); if (res < 0) { FAIL("[%zu]: Add source failed (err %d)\n", i, res); return; } printk("[%zu]: Source added with id %u\n", i, state->src_id); } } } static int mod_source(struct sync_state *state) { const uint16_t pref_context = BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL; struct bt_bap_scan_delegator_mod_src_param param; const uint8_t pref_context_metadata[] = { BT_AUDIO_CODEC_DATA(BT_AUDIO_METADATA_TYPE_PREF_CONTEXT, BT_BYTES_LIST_LE16(pref_context)), }; int err; UNSET_FLAG(flag_recv_state_updated); param.src_id = state->src_id; param.encrypt_state = BT_BAP_BIG_ENC_STATE_NO_ENC; param.broadcast_id = g_broadcast_id; param.num_subgroups = 1U; for (uint8_t i = 0U; i < param.num_subgroups; i++) { struct bt_bap_bass_subgroup *subgroup_param = ¶m.subgroups[i]; subgroup_param->bis_sync = 0U; subgroup_param->metadata_len = sizeof(pref_context_metadata); (void)memcpy(subgroup_param->metadata, pref_context_metadata, subgroup_param->metadata_len); } err = bt_bap_scan_delegator_mod_src(¶m); if (err < 0) { return err; } WAIT_FOR_FLAG(flag_recv_state_updated); return 0; } static void mod_all_sources(void) { for (size_t i = 0U; i < ARRAY_SIZE(sync_states); i++) { struct sync_state *state = &sync_states[i]; if (state->pa_sync != NULL) { int err; printk("[%zu]: Modifying source\n", i); err = mod_source(state); if (err < 0) { FAIL("[%zu]: Modify source failed (err %d)\n", i, err); return; } printk("[%zu]: Source id modifed %u\n", i, state->src_id); } } } static int remove_source(struct sync_state *state) { int err; UNSET_FLAG(flag_recv_state_updated); /* We don't actually need to sync to the BIG/BISes */ err = bt_bap_scan_delegator_rem_src(state->src_id); if (err) { return err; } WAIT_FOR_FLAG(flag_recv_state_updated); return 0; } static void remove_all_sources(void) { for (size_t i = 0U; i < ARRAY_SIZE(sync_states); i++) { struct sync_state *state = &sync_states[i]; if (state->recv_state != NULL) { int err; printk("[%zu]: Removing source\n", i); err = remove_source(state); if (err) { FAIL("[%zu]: Remove source failed (err %d)\n", err); return; } printk("[%zu]: Source removed with id %u\n", i, state->src_id); printk("Terminating PA sync\n"); err = pa_sync_term(state); if (err) { FAIL("[%zu]: PA sync term failed (err %d)\n", err); return; } } } } static int sync_broadcast(struct sync_state *state) { int err; uint32_t bis_sync[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS]; UNSET_FLAG(flag_recv_state_updated); for (size_t i = 0U; i < CONFIG_BT_BAP_BASS_MAX_SUBGROUPS; i++) { bis_sync[i] = BT_ISO_BIS_INDEX_BIT(i + 1); } /* We don't actually need to sync to the BIG/BISes */ err = bt_bap_scan_delegator_set_bis_sync_state(state->src_id, bis_sync); if (err) { return err; } WAIT_FOR_FLAG(flag_recv_state_updated); return 0; } static void sync_all_broadcasts(void) { for (size_t i = 0U; i < ARRAY_SIZE(sync_states); i++) { struct sync_state *state = &sync_states[i]; if (state->pa_sync != NULL) { int res; printk("[%zu]: Setting broadcast sync state\n", i); res = sync_broadcast(state); if (res < 0) { FAIL("[%zu]: Broadcast sync state set failed (err %d)\n", i, res); return; } printk("[%zu]: Broadcast sync state set\n", i); } } } static int common_init(void) { int err; err = bt_enable(NULL); if (err) { FAIL("Bluetooth init failed (err %d)\n", err); return err; } printk("Bluetooth initialized\n"); err = bt_bap_scan_delegator_register(&scan_delegator_cb); if (err) { FAIL("Scan delegator register failed (err %d)\n", err); return err; } bt_le_per_adv_sync_cb_register(&pa_sync_cb); err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, AD_SIZE, NULL, 0); if (err) { FAIL("Advertising failed to start (err %d)\n", err); return err; } printk("Advertising successfully started\n"); WAIT_FOR_FLAG(flag_connected); return 0; } static void test_main_client_sync(void) { int err; err = common_init(); if (err) { FAIL("common init failed (err %d)\n", err); return; } /* Wait for broadcast assistant to request us to sync to PA */ WAIT_FOR_FLAG(flag_pa_synced); /* Wait for broadcast assistant to send us broadcast code */ WAIT_FOR_FLAG(flag_broadcast_code_received); /* Mod all sources by modifying the metadata */ mod_all_sources(); /* Wait for broadcast assistant to tell us to BIS sync */ WAIT_FOR_FLAG(flag_bis_sync_requested); /* Set the BIS sync state */ sync_all_broadcasts(); /* Wait for broadcast assistant to remove source and terminate PA sync */ WAIT_FOR_FLAG(flag_pa_terminated); PASS("BAP Scan Delegator Client Sync passed\n"); } static void test_main_server_sync_client_rem(void) { int err; err = common_init(); if (err) { FAIL("common init failed (err %d)\n", err); return; } bt_le_scan_cb_register(&scan_cb); err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL); if (err != 0) { FAIL("Could not start scan (%d)", err); return; } /* Wait for PA to sync */ WAIT_FOR_FLAG(flag_pa_synced); /* Add PAs as receive state sources */ add_all_sources(); /* Wait for broadcast assistant to send us broadcast code */ WAIT_FOR_FLAG(flag_broadcast_code_received); /* Mod all sources by modifying the metadata */ mod_all_sources(); /* Set the BIS sync state */ sync_all_broadcasts(); /* For for client to remove source and thus terminate the PA */ WAIT_FOR_FLAG(flag_pa_terminated); PASS("BAP Scan Delegator Server Sync Client Remove passed\n"); } static void test_main_server_sync_server_rem(void) { int err; err = common_init(); if (err) { FAIL("common init failed (err %d)\n", err); return; } bt_le_scan_cb_register(&scan_cb); err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL); if (err != 0) { FAIL("Could not start scan (%d)", err); return; } /* Wait for PA to sync */ WAIT_FOR_FLAG(flag_pa_synced); /* Add PAs as receive state sources */ add_all_sources(); /* Wait for broadcast assistant to send us broadcast code */ WAIT_FOR_FLAG(flag_broadcast_code_received); /* Mod all sources by modifying the metadata */ mod_all_sources(); /* Set the BIS sync state */ sync_all_broadcasts(); /* Remote all sources, causing PA sync term request to trigger */ remove_all_sources(); /* Wait for PA sync to be terminated */ WAIT_FOR_FLAG(flag_pa_terminated); PASS("BAP Scan Delegator Server Sync Server Remove passed\n"); } static const struct bst_test_instance test_scan_delegator[] = { { .test_id = "bap_scan_delegator_client_sync", .test_pre_init_f = test_init, .test_tick_f = test_tick, .test_main_f = test_main_client_sync, }, { .test_id = "bap_scan_delegator_server_sync_client_rem", .test_pre_init_f = test_init, .test_tick_f = test_tick, .test_main_f = test_main_server_sync_client_rem, }, { .test_id = "bap_scan_delegator_server_sync_server_rem", .test_pre_init_f = test_init, .test_tick_f = test_tick, .test_main_f = test_main_server_sync_server_rem, }, BSTEST_END_MARKER }; struct bst_test_list *test_scan_delegator_install(struct bst_test_list *tests) { return bst_add_tests(tests, test_scan_delegator); } #else struct bst_test_list *test_scan_delegator_install(struct bst_test_list *tests) { return tests; } #endif /* CONFIG_BT_BAP_SCAN_DELEGATOR */