From c9aec5438095701cbeeef6993d0db0b41d45554f Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Wed, 3 Sep 2025 17:00:07 +0200 Subject: [PATCH 1/9] [nrf fromtree] Bluetooth: BAP: SD: Remove address lookups Remove lookups in the Scan Delegator that relates to the advertiser address. The reason for this, is that the address is not considered a unique value for receive states, since the address may change over time in the case of (N)RPAs. Instead we shall rely exclusively on the address type, the sid and the broadcast ID. The implementation of the Scan Delegator and Broadcast Sink has been updated to not use addresses for lookups anymore, and there has been a minor API modification to set the PA sync state as part of bt_bap_scan_delegator_add_src as the higher layers are better suited to handle the PA Sync state. Signed-off-by: Emil Gydesen (cherry picked from commit 64ea5334e491d7ee5db5c0d78fec051b91bab9d2) --- include/zephyr/bluetooth/audio/bap.h | 10 +- subsys/bluetooth/audio/bap_broadcast_sink.c | 69 ++++-- subsys/bluetooth/audio/bap_scan_delegator.c | 257 ++++++-------------- 3 files changed, 137 insertions(+), 199 deletions(-) diff --git a/include/zephyr/bluetooth/audio/bap.h b/include/zephyr/bluetooth/audio/bap.h index 27f42432e227..d480837c9713 100644 --- a/include/zephyr/bluetooth/audio/bap.h +++ b/include/zephyr/bluetooth/audio/bap.h @@ -2661,8 +2661,6 @@ int bt_bap_scan_delegator_unregister(void); * * @param src_id The source id used to identify the receive state. * @param pa_state The Periodic Advertising sync state to set. - * BT_BAP_PA_STATE_NOT_SYNCED and BT_BAP_PA_STATE_SYNCED is - * not necessary to provide, as they are handled internally. * * @return int Error value. 0 on success, errno on fail. */ @@ -2688,6 +2686,14 @@ struct bt_bap_scan_delegator_add_src_param { /** Advertiser SID */ uint8_t sid; + /** + * @brief Periodic Advertising sync state + * + * This will typically be either @ref BT_BAP_PA_STATE_NOT_SYNCED or + * @ref BT_BAP_PA_STATE_SYNCED. + */ + enum bt_bap_pa_state pa_state; + /** The broadcast isochronous group encryption state */ enum bt_bap_big_enc_state encrypt_state; diff --git a/subsys/bluetooth/audio/bap_broadcast_sink.c b/subsys/bluetooth/audio/bap_broadcast_sink.c index 82106a9dfae5..cfe02efd01e6 100644 --- a/subsys/bluetooth/audio/bap_broadcast_sink.c +++ b/subsys/bluetooth/audio/bap_broadcast_sink.c @@ -70,8 +70,8 @@ static struct bt_bap_scan_delegator_mod_src_param mod_src_param; static void broadcast_sink_cleanup(struct bt_bap_broadcast_sink *sink); -static bool find_recv_state_by_sink_cb(const struct bt_bap_scan_delegator_recv_state *recv_state, - void *user_data) +static bool find_recv_state_by_src_id_cb(const struct bt_bap_scan_delegator_recv_state *recv_state, + void *user_data) { const struct bt_bap_broadcast_sink *sink = user_data; @@ -83,26 +83,27 @@ static bool find_recv_state_by_sink_cb(const struct bt_bap_scan_delegator_recv_s return false; } -static bool find_recv_state_by_pa_sync_cb(const struct bt_bap_scan_delegator_recv_state *recv_state, - void *user_data) +static bool +find_recv_state_by_sink_fields_cb(const struct bt_bap_scan_delegator_recv_state *recv_state, + void *user_data) { - struct bt_le_per_adv_sync *sync = user_data; + const struct bt_bap_broadcast_sink *sink = user_data; struct bt_le_per_adv_sync_info sync_info; int err; - err = bt_le_per_adv_sync_get_info(sync, &sync_info); + err = bt_le_per_adv_sync_get_info(sink->pa_sync, &sync_info); if (err != 0) { LOG_DBG("Failed to get sync info: %d", err); return false; } - if (bt_addr_le_eq(&recv_state->addr, &sync_info.addr) && - recv_state->adv_sid == sync_info.sid) { - return true; - } - - return false; + /* BAP 6.5.4 states that the combined Source_Address_Type, Source_Adv_SID, and Broadcast_ID + * fields are what makes a receive state unique. + */ + return recv_state->addr.type == sync_info.addr.type && + recv_state->adv_sid == sync_info.sid && + recv_state->broadcast_id == sink->broadcast_id; }; static void update_recv_state_big_synced(const struct bt_bap_broadcast_sink *sink) @@ -110,7 +111,7 @@ static void update_recv_state_big_synced(const struct bt_bap_broadcast_sink *sin const struct bt_bap_scan_delegator_recv_state *recv_state; int err; - recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_sink_cb, (void *)sink); + recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_src_id_cb, (void *)sink); if (recv_state == NULL) { LOG_WRN("Failed to find receive state for sink %p", sink); @@ -156,7 +157,7 @@ static void update_recv_state_big_cleared(const struct bt_bap_broadcast_sink *si bool sink_is_streaming = false; int err; - recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_sink_cb, (void *)sink); + recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_src_id_cb, (void *)sink); if (recv_state == NULL) { /* This is likely due to the receive state being removed while we are BIG synced */ LOG_DBG("Could not find receive state for sink %p", sink); @@ -489,6 +490,8 @@ static void broadcast_sink_add_src(struct bt_bap_broadcast_sink *sink) bt_addr_le_copy(&add_src_param.addr, &sync_info.addr); add_src_param.sid = sync_info.sid; + /* When a broadcast sink is created we always assume the PA sync provided is synced */ + add_src_param.pa_state = BT_BAP_PA_STATE_SYNCED; add_src_param.broadcast_id = sink->broadcast_id; /* Will be updated when we receive the BASE */ add_src_param.encrypt_state = BT_BAP_BIG_ENC_STATE_NO_ENC; @@ -542,7 +545,7 @@ static void update_recv_state_base(const struct bt_bap_broadcast_sink *sink, const struct bt_bap_scan_delegator_recv_state *recv_state; int err; - recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_sink_cb, (void *)sink); + recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_src_id_cb, (void *)sink); if (recv_state == NULL) { LOG_WRN("Failed to find receive state for sink %p", sink); @@ -745,12 +748,23 @@ static void pa_recv(struct bt_le_per_adv_sync *sync, bt_data_parse(buf, pa_decode_base, (void *)sink); } +static void pa_synced_cb(struct bt_le_per_adv_sync *sync, + struct bt_le_per_adv_sync_synced_info *info) +{ + struct bt_bap_broadcast_sink *sink = broadcast_sink_get_by_pa(sync); + + if (sink != NULL) { + bt_bap_scan_delegator_set_pa_state(sink->bass_src_id, BT_BAP_PA_STATE_SYNCED); + } +} + static void pa_term_cb(struct bt_le_per_adv_sync *sync, const struct bt_le_per_adv_sync_term_info *info) { struct bt_bap_broadcast_sink *sink = broadcast_sink_get_by_pa(sync); if (sink != NULL) { + bt_bap_scan_delegator_set_pa_state(sink->bass_src_id, BT_BAP_PA_STATE_NOT_SYNCED); sink->pa_sync = NULL; sink->base_size = 0U; } @@ -763,7 +777,7 @@ static void update_recv_state_encryption(const struct bt_bap_broadcast_sink *sin __ASSERT(sink->big == NULL, "Encryption state shall not be updated while synced"); - recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_sink_cb, (void *)sink); + recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_src_id_cb, (void *)sink); if (recv_state == NULL) { LOG_WRN("Failed to find receive state for sink %p", sink); @@ -1073,8 +1087,8 @@ int bt_bap_broadcast_sink_create(struct bt_le_per_adv_sync *pa_sync, uint32_t br sink->broadcast_id = broadcast_id; sink->pa_sync = pa_sync; - recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_pa_sync_cb, - (void *)pa_sync); + recv_state = + bt_bap_scan_delegator_find_state(find_recv_state_by_sink_fields_cb, (void *)sink); if (recv_state == NULL) { broadcast_sink_add_src(sink); } else { @@ -1088,8 +1102,26 @@ int bt_bap_broadcast_sink_create(struct bt_le_per_adv_sync *pa_sync, uint32_t br } sink->bass_src_id = recv_state->src_id; + if (recv_state->pa_sync_state != BT_BAP_PA_STATE_SYNCED) { + int err; + + /* When a broadcast sink is created we always assume the PA sync provided is + * synced + */ + err = bt_bap_scan_delegator_set_pa_state(sink->bass_src_id, + BT_BAP_PA_STATE_SYNCED); + if (err != 0) { + LOG_DBG("Failed to set PA state: %d", err); + + broadcast_sink_cleanup(sink); + + return err; + } + } + atomic_set_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID); } + atomic_set_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_INITIALIZED); *out_sink = sink; @@ -1416,6 +1448,7 @@ int bt_bap_broadcast_sink_delete(struct bt_bap_broadcast_sink *sink) static int broadcast_sink_init(void) { static struct bt_le_per_adv_sync_cb cb = { + .synced = pa_synced_cb, .recv = pa_recv, .biginfo = biginfo_recv, .term = pa_term_cb, diff --git a/subsys/bluetooth/audio/bap_scan_delegator.c b/subsys/bluetooth/audio/bap_scan_delegator.c index 1be1eec56a82..6e3316c28443 100644 --- a/subsys/bluetooth/audio/bap_scan_delegator.c +++ b/subsys/bluetooth/audio/bap_scan_delegator.c @@ -65,7 +65,7 @@ struct bass_recv_state_internal { uint8_t index; struct bt_bap_scan_delegator_recv_state state; uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE]; - struct bt_le_per_adv_sync *pa_sync; + /** Requested BIS sync bitfield for each subgroup */ uint32_t requested_bis_sync[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS]; @@ -85,7 +85,6 @@ struct bt_bap_scan_delegator_inst { enum scan_delegator_flag { SCAN_DELEGATOR_FLAG_REGISTERED_CONN_CB, SCAN_DELEGATOR_FLAG_REGISTERED_SCAN_DELIGATOR, - SCAN_DELEGATOR_FLAG_REGISTERED_PA_SYNC_CB, SCAN_DELEGATOR_FLAG_NUM, }; @@ -395,27 +394,43 @@ static struct bass_recv_state_internal *bass_lookup_src_id(uint8_t src_id) return NULL; } -static struct bass_recv_state_internal *bass_lookup_pa_sync(struct bt_le_per_adv_sync *sync) +/* BAP 6.5.4 states that the combined Source_Address_Type, Source_Adv_SID, and Broadcast_ID fields + * are what makes a receive state unique. + */ +static struct bass_recv_state_internal *bass_lookup_state(uint8_t addr_type, uint8_t adv_sid, + uint32_t broadcast_id) { - for (size_t i = 0; i < ARRAY_SIZE(scan_delegator.recv_states); i++) { - if (scan_delegator.recv_states[i].pa_sync == sync) { - return &scan_delegator.recv_states[i]; + struct bass_recv_state_internal *res = NULL; + + ARRAY_FOR_EACH_PTR(scan_delegator.recv_states, recv_state_internal) { + int err; + + if (!recv_state_internal->active) { + continue; } - } - return NULL; -} + err = k_mutex_lock(&recv_state_internal->mutex, SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + if (err != 0) { + LOG_DBG("Failed to lock mutex: %d", err); -static struct bass_recv_state_internal *bass_lookup_addr(const bt_addr_le_t *addr) -{ - for (size_t i = 0; i < ARRAY_SIZE(scan_delegator.recv_states); i++) { - if (bt_addr_le_eq(&scan_delegator.recv_states[i].state.addr, - addr)) { - return &scan_delegator.recv_states[i]; + return NULL; + } + + if (recv_state_internal->state.addr.type == addr_type && + recv_state_internal->state.adv_sid == adv_sid && + recv_state_internal->state.broadcast_id == broadcast_id) { + res = recv_state_internal; + } + + err = k_mutex_unlock(&recv_state_internal->mutex); + __ASSERT(err == 0, "Failed to unlock mutex: %d", err); + + if (res != NULL) { + break; } } - return NULL; + return res; } static struct bass_recv_state_internal *get_free_recv_state(void) @@ -432,85 +447,6 @@ static struct bass_recv_state_internal *get_free_recv_state(void) return NULL; } -static void pa_synced(struct bt_le_per_adv_sync *sync, - struct bt_le_per_adv_sync_synced_info *info) -{ - struct bass_recv_state_internal *internal_state; - bool state_changed = false; - int err; - - LOG_DBG("Synced%s", info->conn ? " via PAST" : ""); - - internal_state = bass_lookup_addr(info->addr); - if (internal_state == NULL) { - LOG_DBG("BASS receive state not found"); - return; - } - - err = k_mutex_lock(&internal_state->mutex, SCAN_DELEGATOR_BUF_SEM_TIMEOUT); - if (err != 0) { - LOG_DBG("Failed to lock mutex: %d", err); - return; - } - - internal_state->pa_sync = sync; - - if (internal_state->state.pa_sync_state != BT_BAP_PA_STATE_SYNCED) { - internal_state->state.pa_sync_state = BT_BAP_PA_STATE_SYNCED; - set_receive_state_changed(internal_state); - state_changed = true; - } - - err = k_mutex_unlock(&internal_state->mutex); - __ASSERT(err == 0, "Failed to unlock mutex: %d", err); - - if (state_changed) { - /* app callback */ - receive_state_updated(info->conn, internal_state); - } -} - -static void pa_terminated(struct bt_le_per_adv_sync *sync, - const struct bt_le_per_adv_sync_term_info *info) -{ - struct bass_recv_state_internal *internal_state = bass_lookup_pa_sync(sync); - bool state_changed = false; - int err; - - LOG_DBG("Terminated"); - if (internal_state == NULL) { - LOG_DBG("BASS receive state not found"); - return; - } - - err = k_mutex_lock(&internal_state->mutex, SCAN_DELEGATOR_BUF_SEM_TIMEOUT); - if (err != 0) { - LOG_DBG("Failed to lock mutex: %d", err); - return; - } - - internal_state->pa_sync = NULL; - - if (internal_state->state.pa_sync_state != BT_BAP_PA_STATE_NOT_SYNCED) { - internal_state->state.pa_sync_state = BT_BAP_PA_STATE_NOT_SYNCED; - set_receive_state_changed(internal_state); - state_changed = true; - } - - err = k_mutex_unlock(&internal_state->mutex); - __ASSERT(err == 0, "Failed to unlock mutex: %d", err); - - if (state_changed) { - /* app callback */ - receive_state_updated(NULL, internal_state); - } -} - -static struct bt_le_per_adv_sync_cb pa_sync_cb = { - .synced = pa_synced, - .term = pa_terminated, -}; - static bool supports_past(struct bt_conn *conn, uint8_t pa_sync_val) { if (IS_ENABLED(CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER)) { @@ -580,35 +516,12 @@ static int pa_sync_term_request(struct bt_conn *conn, return err; } -/* BAP 6.5.4 states that the Broadcast Assistant shall not initiate the Add Source operation - * if the operation would result in duplicate values for the combined Source_Address_Type, - * Source_Adv_SID, and Broadcast_ID fields of any Broadcast Receive State characteristic exposed - * by the Scan Delegator. - */ -static bool bass_source_is_duplicate(uint32_t broadcast_id, uint8_t adv_sid, uint8_t addr_type) -{ - struct bass_recv_state_internal *state; - - for (size_t i = 0; i < ARRAY_SIZE(scan_delegator.recv_states); i++) { - state = &scan_delegator.recv_states[i]; - - if (state != NULL && state->state.broadcast_id == broadcast_id && - state->state.adv_sid == adv_sid && state->state.addr.type == addr_type) { - LOG_DBG("recv_state already exists at src_id=0x%02X", state->state.src_id); - - return true; - } - } - - return false; -} - static int scan_delegator_add_src(struct bt_conn *conn, struct net_buf_simple *buf) { struct bass_recv_state_internal *internal_state = NULL; struct bt_bap_scan_delegator_recv_state *state; - bt_addr_t *addr; + bt_addr_le_t *addr; uint8_t pa_sync; uint16_t pa_interval; uint32_t aggregated_bis_syncs = 0; @@ -617,6 +530,7 @@ static int scan_delegator_add_src(struct bt_conn *conn, uint16_t total_len; struct bt_bap_bass_cp_add_src *add_src; int ret = BT_GATT_ERR(BT_ATT_ERR_SUCCESS); + uint8_t adv_sid; int err; /* subtract 1 as the opcode has already been pulled */ @@ -653,6 +567,29 @@ static int scan_delegator_add_src(struct bt_conn *conn, return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); } + addr = net_buf_simple_pull_mem(buf, sizeof(*addr)); + if (addr->type > BT_ADDR_LE_RANDOM) { + LOG_DBG("Invalid address type %u", addr->type); + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + adv_sid = net_buf_simple_pull_u8(buf); + if (adv_sid > BT_GAP_SID_MAX) { + LOG_DBG("Invalid adv SID %u", adv_sid); + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + broadcast_id = net_buf_simple_pull_le24(buf); + + internal_state = bass_lookup_state(addr->type, adv_sid, broadcast_id); + if (internal_state != NULL) { + LOG_DBG("Adding addr type=0x%02X adv_sid=0x%02X and broadcast_id=0x%06X would " + "result in duplication", + addr->type, adv_sid, broadcast_id); + + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + internal_state = get_free_recv_state(); if (internal_state == NULL) { LOG_DBG("Could not get free receive state"); @@ -670,33 +607,8 @@ static int scan_delegator_add_src(struct bt_conn *conn, state = &internal_state->state; state->src_id = next_src_id(); - state->addr.type = net_buf_simple_pull_u8(buf); - if (state->addr.type > BT_ADDR_LE_RANDOM) { - LOG_DBG("Invalid address type %u", state->addr.type); - ret = BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); - goto unlock_return; - } - - addr = net_buf_simple_pull_mem(buf, sizeof(*addr)); - bt_addr_copy(&state->addr.a, addr); - - state->adv_sid = net_buf_simple_pull_u8(buf); - if (state->adv_sid > BT_GAP_SID_MAX) { - LOG_DBG("Invalid adv SID %u", state->adv_sid); - ret = BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); - goto unlock_return; - } - - broadcast_id = net_buf_simple_pull_le24(buf); - - if (bass_source_is_duplicate(broadcast_id, state->adv_sid, state->addr.type)) { - LOG_DBG("Adding broadcast_id=0x%06X, adv_sid=0x%02X, and addr.type=0x%02X would " - "result in duplication", state->broadcast_id, state->adv_sid, - state->addr.type); - ret = BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); - goto unlock_return; - } - + bt_addr_le_copy(&state->addr, addr); + state->adv_sid = adv_sid; state->broadcast_id = broadcast_id; pa_sync = net_buf_simple_pull_u8(buf); @@ -1205,7 +1117,6 @@ static int scan_delegator_rem_src(struct bt_conn *conn, internal_state->index, src_id); internal_state->active = false; - internal_state->pa_sync = NULL; (void)memset(&internal_state->state, 0, sizeof(internal_state->state)); (void)memset(internal_state->broadcast_code, 0, sizeof(internal_state->broadcast_code)); @@ -1454,18 +1365,6 @@ int bt_bap_scan_delegator_register(struct bt_bap_scan_delegator_cb *cb) #endif /* CONFIG_BT_BAP_SCAN_DELEGATOR_RECV_STATE_COUNT > 2 */ #endif /* CONFIG_BT_BAP_SCAN_DELEGATOR_RECV_STATE_COUNT > 1 */ - if (!atomic_test_and_set_bit(scan_delegator_flags, - SCAN_DELEGATOR_FLAG_REGISTERED_PA_SYNC_CB)) { - err = bt_le_per_adv_sync_cb_register(&pa_sync_cb); - if (err) { - atomic_clear_bit(scan_delegator_flags, - SCAN_DELEGATOR_FLAG_REGISTERED_PA_SYNC_CB); - atomic_clear_bit(scan_delegator_flags, - SCAN_DELEGATOR_FLAG_REGISTERED_SCAN_DELIGATOR); - return err; - } - } - for (size_t i = 0; i < ARRAY_SIZE(scan_delegator.recv_states); i++) { struct bass_recv_state_internal *internal_state = &scan_delegator.recv_states[i]; @@ -1652,6 +1551,12 @@ static bool valid_bt_bap_scan_delegator_add_src_param( return false; } + if (!IN_RANGE(param->pa_state, BT_BAP_PA_STATE_NOT_SYNCED, BT_BAP_PA_STATE_NO_PAST)) { + LOG_DBG("Invalid PA state: %d", param->pa_state); + + return false; + } + for (uint8_t i = 0U; i < param->num_subgroups; i++) { const struct bt_bap_bass_subgroup *subgroup = ¶m->subgroups[i]; @@ -1677,23 +1582,19 @@ int bt_bap_scan_delegator_add_src(const struct bt_bap_scan_delegator_add_src_par { struct bass_recv_state_internal *internal_state = NULL; struct bt_bap_scan_delegator_recv_state *state; - struct bt_le_per_adv_sync *pa_sync; int err; CHECKIF(!valid_bt_bap_scan_delegator_add_src_param(param)) { return -EINVAL; } - pa_sync = bt_le_per_adv_sync_lookup_addr(¶m->addr, param->sid); - - if (pa_sync != NULL) { - internal_state = bass_lookup_pa_sync(pa_sync); - if (internal_state != NULL) { - LOG_DBG("PA Sync already in a receive state with src_id %u", - internal_state->state.src_id); + internal_state = bass_lookup_state(param->addr.type, param->sid, param->broadcast_id); + if (internal_state != NULL) { + LOG_DBG("Adding addr.type=0x%02X adv_sid=0x%02X and broadcast_id=0x%06X would " + "result in duplication", + param->addr.type, param->sid, param->broadcast_id); - return -EALREADY; - } + return -EALREADY; } internal_state = get_free_recv_state(); @@ -1703,14 +1604,20 @@ int bt_bap_scan_delegator_add_src(const struct bt_bap_scan_delegator_add_src_par return -ENOMEM; } + err = k_mutex_lock(&internal_state->mutex, SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + if (err != 0) { + LOG_DBG("Failed to lock mutex: %d", err); + + return -EBUSY; + } + state = &internal_state->state; state->src_id = next_src_id(); bt_addr_le_copy(&state->addr, ¶m->addr); state->adv_sid = param->sid; state->broadcast_id = param->broadcast_id; - state->pa_sync_state = - pa_sync == NULL ? BT_BAP_PA_STATE_NOT_SYNCED : BT_BAP_PA_STATE_SYNCED; + state->pa_sync_state = param->pa_state; state->num_subgroups = param->num_subgroups; if (state->num_subgroups > 0U) { (void)memcpy(state->subgroups, param->subgroups, @@ -1719,15 +1626,7 @@ int bt_bap_scan_delegator_add_src(const struct bt_bap_scan_delegator_add_src_par (void)memset(state->subgroups, 0, sizeof(state->subgroups)); } - err = k_mutex_lock(&internal_state->mutex, SCAN_DELEGATOR_BUF_SEM_TIMEOUT); - if (err != 0) { - LOG_DBG("Failed to lock mutex: %d", err); - - return -EBUSY; - } - internal_state->active = true; - internal_state->pa_sync = pa_sync; /* Set all requested_bis_sync to BT_BAP_BIS_SYNC_NO_PREF, as no * Broadcast Assistant has set any requests yet From 7ed3b5c75bcc6229a62b09ca384bdd22ed94212c Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Thu, 4 Sep 2025 10:34:07 +0200 Subject: [PATCH 2/9] [nrf fromtree] tests: Bluetooth: BAP: SD: Update test to set PA sync state The Scan Delegator has been modified to not set the PA sync state automatically anymore, so the test needed to be updated. Signed-off-by: Emil Gydesen (cherry picked from commit 397c8ee54c43d0495859e8c403545525f60c71c3) --- .../audio/src/bap_scan_delegator_test.c | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/tests/bsim/bluetooth/audio/src/bap_scan_delegator_test.c b/tests/bsim/bluetooth/audio/src/bap_scan_delegator_test.c index c8f728fa96af..ca0e38ec600f 100644 --- a/tests/bsim/bluetooth/audio/src/bap_scan_delegator_test.c +++ b/tests/bsim/bluetooth/audio/src/bap_scan_delegator_test.c @@ -123,6 +123,7 @@ static void pa_timer_handler(struct k_work *work) if (state->recv_state != NULL) { enum bt_bap_pa_state pa_state; + int err; if (state->recv_state->pa_sync_state == BT_BAP_PA_STATE_INFO_REQ) { pa_state = BT_BAP_PA_STATE_NO_PAST; @@ -130,8 +131,11 @@ static void pa_timer_handler(struct k_work *work) pa_state = BT_BAP_PA_STATE_FAILED; } - bt_bap_scan_delegator_set_pa_state(state->recv_state->src_id, - pa_state); + err = bt_bap_scan_delegator_set_pa_state(state->recv_state->src_id, pa_state); + if (err != 0) { + FAIL("Could not set PA sync state: %d\n", err); + return; + } } FAIL("PA timeout\n"); @@ -286,7 +290,8 @@ static int pa_sync_req_cb(struct bt_conn *conn, 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); + FAIL("Could not set PA sync state: %d\n", err); + return err; } } } else { @@ -426,6 +431,16 @@ static void pa_synced_cb(struct bt_le_per_adv_sync *sync, return; } + if (state->recv_state != NULL) { + int err; + + err = bt_bap_scan_delegator_set_pa_state(state->src_id, BT_BAP_PA_STATE_SYNCED); + if (err != 0) { + FAIL("Could not set PA sync state: %d\n", err); + return; + } + } + k_work_cancel_delayable(&state->pa_timer); SET_FLAG(flag_pa_synced); @@ -444,6 +459,16 @@ static void pa_term_cb(struct bt_le_per_adv_sync *sync, return; } + if (state->recv_state != NULL) { + int err; + + err = bt_bap_scan_delegator_set_pa_state(state->src_id, BT_BAP_PA_STATE_NOT_SYNCED); + if (err != 0) { + FAIL("Could not set PA sync state: %d\n", err); + return; + } + } + k_work_cancel_delayable(&state->pa_timer); SET_FLAG(flag_pa_terminated); @@ -541,6 +566,7 @@ static int add_source(struct sync_state *state) bt_addr_le_copy(¶m.addr, &sync_info.addr); param.sid = sync_info.sid; + param.pa_state = BT_BAP_PA_STATE_SYNCED; param.encrypt_state = BT_BAP_BIG_ENC_STATE_NO_ENC; param.broadcast_id = g_broadcast_id; param.num_subgroups = 1U; @@ -657,6 +683,8 @@ static int remove_source(struct sync_state *state) return err; } + state->recv_state = NULL; + WAIT_FOR_FLAG(flag_recv_state_updated); return 0; From e37a9adf7ed309f47e9687c991f8c4eeec48f8c7 Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Thu, 4 Sep 2025 10:35:10 +0200 Subject: [PATCH 3/9] [nrf fromtree] doc: migration guide: Add entry for recent change to BT BAP SD The Bluetooth BAP Scan Delegator had a minor behavioral change that may affect a small subset of devices. Signed-off-by: Emil Gydesen (cherry picked from commit 15abc32ca09f836ce167958019d5b4057c90b8d7) --- doc/releases/migration-guide-4.3.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/releases/migration-guide-4.3.rst b/doc/releases/migration-guide-4.3.rst index 53e6b64ab72d..e895207d03c3 100644 --- a/doc/releases/migration-guide-4.3.rst +++ b/doc/releases/migration-guide-4.3.rst @@ -114,6 +114,12 @@ Bluetooth Audio * Setting the BGS role for GMAP now requires also supporting and implementing the :kconfig:option:`CONFIG_BT_BAP_BROADCAST_ASSISTANT`. See the :zephyr:code-sample:`bluetooth_bap_broadcast_assistant` sample as a reference. +* The BAP Scan Delegator will no longer automatically update the PA sync state, and + :c:func:`bt_bap_scan_delegator_set_pa_state` must be used to update the state. If the + BAP Scan Delegator is used together with the BAP Broadcast Sink, then the PA state of the + receive state of a :c:struct:`bt_bap_broadcast_sink` will still be automatically updated when the + PA state changes. (:github:`95453``) + .. zephyr-keep-sorted-stop From 114512cf0cf6f6bc0f99b3978258afb1dc98b31a Mon Sep 17 00:00:00 2001 From: Alexander Svensen Date: Wed, 19 Nov 2025 11:53:19 +0100 Subject: [PATCH 4/9] [nrf fromtree] bluetooth: audio: scan_delegator: Reject remove when PA synced - BASS mandates that a remove_source operation is not done if we are synced to PA or BIS. This PR fixes that - Update BabbleSim tests to reflect this behavior - Fixes BASS/SR/SPE/BI-05-C Signed-off-by: Alexander Svensen (cherry picked from commit 87997afeb2b881fefd871aaabc6637626d3304e4) --- subsys/bluetooth/audio/bap_scan_delegator.c | 61 +++++++++---------- .../audio/src/bap_broadcast_assistant_test.c | 43 ++++++------- .../audio/src/bap_scan_delegator_test.c | 58 +++++++++++++++--- 3 files changed, 102 insertions(+), 60 deletions(-) diff --git a/subsys/bluetooth/audio/bap_scan_delegator.c b/subsys/bluetooth/audio/bap_scan_delegator.c index 6e3316c28443..5e6f8384ef75 100644 --- a/subsys/bluetooth/audio/bap_scan_delegator.c +++ b/subsys/bluetooth/audio/bap_scan_delegator.c @@ -1051,7 +1051,6 @@ static int scan_delegator_rem_src(struct bt_conn *conn, { struct bass_recv_state_internal *internal_state; struct bt_bap_scan_delegator_recv_state *state; - bool bis_sync_was_requested = false; uint8_t src_id; int err; @@ -1078,38 +1077,40 @@ static int scan_delegator_rem_src(struct bt_conn *conn, state = &internal_state->state; - /* If conn == NULL then it's a local operation and we do not need to ask the application */ - if (conn != NULL) { - - if (scan_delegator_cbs != NULL && scan_delegator_cbs->remove_source != NULL) { - err = scan_delegator_cbs->remove_source(conn, src_id); - if (err != 0) { - LOG_DBG("Remove Source rejected with reason 0x%02x", err); - err = k_mutex_unlock(&internal_state->mutex); - __ASSERT(err == 0, "Failed to unlock mutex: %d", err); - return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); - } - } - - if (state->pa_sync_state == BT_BAP_PA_STATE_INFO_REQ || - state->pa_sync_state == BT_BAP_PA_STATE_SYNCED) { - /* Terminate PA sync */ - err = pa_sync_term_request(conn, &internal_state->state); - if (err != 0) { - LOG_DBG("PA sync term from %p was rejected with reason %d", - (void *)conn, err); - err = k_mutex_unlock(&internal_state->mutex); - __ASSERT(err == 0, "Failed to unlock mutex: %d", err); - return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); - } - } + if (state->pa_sync_state == BT_BAP_PA_STATE_SYNCED) { + LOG_DBG("Cannot remove source ID 0x%02x while PA is synced", state->src_id); + err = k_mutex_unlock(&internal_state->mutex); + __ASSERT(err == 0, "Failed to unlock mutex: %d", err); + /* We shouldn't return a success here, but the Test Spec requires it at the moment, + * Errata to fix this: https://bluetooth.atlassian.net/browse/ES-28445 + */ + return BT_GATT_ERR(BT_ATT_ERR_SUCCESS); } for (uint8_t i = 0U; i < state->num_subgroups; i++) { if (internal_state->requested_bis_sync[i] != 0U && internal_state->state.subgroups[i].bis_sync != 0U) { - bis_sync_was_requested = true; - break; + LOG_DBG("Cannot remove source ID 0x%02x while BIS is synced", + state->src_id); + err = k_mutex_unlock(&internal_state->mutex); + __ASSERT(err == 0, "Failed to unlock mutex: %d", err); + /* We shouldn't return a success here, but the Test Spec requires it at the + * moment, Errata to fix this: + * https://bluetooth.atlassian.net/browse/ES-28445 + */ + return BT_GATT_ERR(BT_ATT_ERR_SUCCESS); + } + } + + /* If conn == NULL then it's a local operation and we do not need to ask the application */ + if (conn != NULL && scan_delegator_cbs != NULL && + scan_delegator_cbs->remove_source != NULL) { + err = scan_delegator_cbs->remove_source(conn, src_id); + if (err != 0) { + LOG_DBG("Remove Source rejected with reason 0x%02x", err); + err = k_mutex_unlock(&internal_state->mutex); + __ASSERT(err == 0, "Failed to unlock mutex: %d", err); + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); } } @@ -1128,10 +1129,6 @@ static int scan_delegator_rem_src(struct bt_conn *conn, err = k_mutex_unlock(&internal_state->mutex); __ASSERT(err == 0, "Failed to unlock mutex: %d", err); - if (bis_sync_was_requested) { - bis_sync_request_updated(conn, internal_state); - } - /* app callback */ receive_state_updated(conn, internal_state); diff --git a/tests/bsim/bluetooth/audio/src/bap_broadcast_assistant_test.c b/tests/bsim/bluetooth/audio/src/bap_broadcast_assistant_test.c index 5d7d3ab7db8e..e9a35e0773c2 100644 --- a/tests/bsim/bluetooth/audio/src/bap_broadcast_assistant_test.c +++ b/tests/bsim/bluetooth/audio/src/bap_broadcast_assistant_test.c @@ -502,7 +502,7 @@ static void test_bass_add_source(void) printk("Source added\n"); } -static void test_bass_mod_source(uint32_t bis_sync) +static void test_bass_mod_source(bool pa_sync, uint32_t bis_sync) { int err; struct bt_bap_broadcast_assistant_mod_src_param mod_src_param = { 0 }; @@ -515,7 +515,7 @@ static void test_bass_mod_source(uint32_t bis_sync) UNSET_FLAG(flag_recv_state_updated); mod_src_param.src_id = recv_state.src_id; mod_src_param.num_subgroups = 1; - mod_src_param.pa_sync = true; + mod_src_param.pa_sync = pa_sync; mod_src_param.subgroups = &subgroup; mod_src_param.pa_interval = g_broadcaster_info.interval; subgroup.bis_sync = bis_sync; @@ -542,14 +542,16 @@ static void test_bass_mod_source(uint32_t bis_sync) WAIT_FOR_AND_CLEAR_FLAG(flag_recv_state_updated); } - if (recv_state.pa_sync_state != BT_BAP_PA_STATE_SYNCED) { - FAIL("Unexpected PA sync state: %d\n", recv_state.pa_sync_state); - return; - } + if (pa_sync) { + if (recv_state.pa_sync_state != BT_BAP_PA_STATE_SYNCED) { + FAIL("Unexpected PA sync state: %d\n", recv_state.pa_sync_state); + return; + } - if (recv_state.encrypt_state != BT_BAP_BIG_ENC_STATE_NO_ENC) { - FAIL("Unexpected BIG encryption state: %d\n", recv_state.pa_sync_state); - return; + if (recv_state.encrypt_state != BT_BAP_BIG_ENC_STATE_NO_ENC) { + FAIL("Unexpected BIG encryption state: %d\n", recv_state.pa_sync_state); + return; + } } if (recv_state.num_subgroups != mod_src_param.num_subgroups) { @@ -563,19 +565,15 @@ static void test_bass_mod_source(uint32_t bis_sync) WAIT_FOR_AND_CLEAR_FLAG(flag_recv_state_updated); } - remote_bis_sync = recv_state.subgroups[0].bis_sync; - if (subgroup.bis_sync == 0) { - if (remote_bis_sync != 0U) { - FAIL("Unexpected BIS sync value: %u\n", remote_bis_sync); - return; - } - } else { + if (bis_sync != 0) { + remote_bis_sync = recv_state.subgroups[0].bis_sync; printk("Waiting for BIS sync\n"); if (remote_bis_sync == 0U && recv_state.encrypt_state == BT_BAP_BIG_ENC_STATE_NO_ENC) { - /* Wait for another notification, which will either request a broadcast code - * for encrypted broadcasts, or have the BIS sync values set + /* Wait for another notification, which will either request a + * broadcast code for encrypted broadcasts, or have the BIS sync + * values set */ printk("Waiting for another receive state update with BIS sync\n"); WAIT_FOR_AND_CLEAR_FLAG(flag_recv_state_updated); @@ -770,14 +768,15 @@ static void test_main_client_sync(void) test_bass_scan_stop(); test_bass_create_pa_sync(); test_bass_add_source(); - test_bass_mod_source(0); + test_bass_mod_source(true, 0); test_bass_mod_source_long_meta(); - test_bass_mod_source(BT_ISO_BIS_INDEX_BIT(1) | BT_ISO_BIS_INDEX_BIT(2)); + test_bass_mod_source(true, BT_ISO_BIS_INDEX_BIT(1) | BT_ISO_BIS_INDEX_BIT(2)); test_bass_broadcast_code(BROADCAST_CODE); printk("Waiting for receive state with BIS sync\n"); WAIT_FOR_FLAG(flag_recv_state_updated_with_bis_sync); + test_bass_mod_source(false, 0); test_bass_remove_source(); PASS("BAP Broadcast Assistant Client Sync Passed\n"); @@ -797,11 +796,12 @@ static void test_main_client_sync_incorrect_code(void) test_bass_scan_stop(); test_bass_create_pa_sync(); test_bass_add_source(); - test_bass_mod_source(BT_ISO_BIS_INDEX_BIT(1)); + test_bass_mod_source(true, BT_ISO_BIS_INDEX_BIT(1)); WAIT_FOR_FLAG(flag_broadcast_code_requested); test_bass_broadcast_code(INCORRECT_BROADCAST_CODE); WAIT_FOR_FLAG(flag_incorrect_broadcast_code); + test_bass_mod_source(false, 0); test_bass_remove_source(); PASS("BAP Broadcast Assistant Client Sync Passed\n"); @@ -825,6 +825,7 @@ static void test_main_server_sync_client_rem(void) WAIT_FOR_FLAG(flag_recv_state_updated_with_bis_sync); printk("Attempting to remove source for the first time\n"); + test_bass_mod_source(false, 0); test_bass_remove_source(); WAIT_FOR_FLAG(flag_remove_source_rejected); diff --git a/tests/bsim/bluetooth/audio/src/bap_scan_delegator_test.c b/tests/bsim/bluetooth/audio/src/bap_scan_delegator_test.c index ca0e38ec600f..b979464a39a5 100644 --- a/tests/bsim/bluetooth/audio/src/bap_scan_delegator_test.c +++ b/tests/bsim/bluetooth/audio/src/bap_scan_delegator_test.c @@ -213,6 +213,8 @@ static int pa_sync_term(struct sync_state *state) printk("Deleting PA sync\n"); + UNSET_FLAG(flag_pa_terminated); + err = bt_le_per_adv_sync_delete(state->pa_sync); if (err != 0) { FAIL("Could not delete per adv sync: %d\n", err); @@ -221,6 +223,8 @@ static int pa_sync_term(struct sync_state *state) state->pa_sync = NULL; } + WAIT_FOR_FLAG(flag_pa_terminated); + return err; } @@ -708,13 +712,48 @@ static void remove_all_sources(void) 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 void terminate_all_pa(void) +{ + for (size_t i = 0U; i < ARRAY_SIZE(sync_states); i++) { + int err; + struct sync_state *state = &sync_states[i]; + + if (state->pa_sync == NULL) { + continue; + } + + err = pa_sync_term(state); + if (err != 0) { + FAIL("[%zu]: PA sync term failed (err %d)\n", i, err); + return; + } + } +} + +static void set_bis_sync_state(struct sync_state *state, + uint32_t bis_sync_req[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS]) +{ + int err; + + err = bt_bap_scan_delegator_set_bis_sync_state(state->src_id, bis_sync_req); + if (err != 0) { + FAIL("Could not set BIS sync state: %d\n", err); + return; + } +} + +static void set_all_bis_sync_states(uint32_t bis_sync_req[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS]) +{ + for (size_t i = 0U; i < ARRAY_SIZE(sync_states); i++) { + struct sync_state *state = &sync_states[i]; + + if (state->recv_state != NULL) { + printk("[%zu]: Setting BIS sync state\n", i); + set_bis_sync_state(state, bis_sync_req); } } } @@ -914,7 +953,12 @@ static void test_main_server_sync_server_rem(void) /* Set the BIS sync state */ sync_all_broadcasts(); - /* Remote all sources, causing PA sync term request to trigger */ + uint32_t bis_sync_req[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS] = {0}; + + set_all_bis_sync_states(bis_sync_req); + terminate_all_pa(); + + /* Remove all sources, causing PA sync term request to trigger */ remove_all_sources(); /* Wait for PA sync to be terminated */ From de07396fd89cd66a937ce8571da0826c302b55b8 Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Tue, 18 Nov 2025 23:04:44 +0100 Subject: [PATCH 5/9] [nrf fromtree] Bluetooth: BAP: Fix bad check for ASE state for CIS connect A CIS may be connected in either the QoS Configured state or the enabling state. The QoS Configured state is the earliest state it is allowed, due to it being the first state where the CIS_ID and ASE_ID are paired. The enabling state is the "last" state it is allowed, as if the ASE is in the streaming, disabling or releasing state we should not allow/expect a CIS connection to happen. Signed-off-by: Emil Gydesen (cherry picked from commit 4c54eef0d0f00c32d075a7498ad33d41b2764cb8) --- subsys/bluetooth/audio/bap_unicast_client.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/subsys/bluetooth/audio/bap_unicast_client.c b/subsys/bluetooth/audio/bap_unicast_client.c index 429431ada433..d0d9001548dc 100644 --- a/subsys/bluetooth/audio/bap_unicast_client.c +++ b/subsys/bluetooth/audio/bap_unicast_client.c @@ -327,8 +327,8 @@ static void unicast_client_ep_iso_connected(struct bt_bap_ep *ep) ep->unicast_group->has_been_connected = true; } - if (ep->state != BT_BAP_EP_STATE_ENABLING) { - LOG_DBG("endpoint not in enabling state: %s", bt_bap_ep_state_str(ep->state)); + if (ep->state != BT_BAP_EP_STATE_QOS_CONFIGURED && ep->state != BT_BAP_EP_STATE_ENABLING) { + LOG_DBG("endpoint in invalid state: %s", bt_bap_ep_state_str(ep->state)); return; } From 2847e59fe6e894a892beb323cf4bc9ed1ff7c01e Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Tue, 18 Nov 2025 16:33:30 +0100 Subject: [PATCH 6/9] [nrf fromtree] tests: Bluetooth: Add ASCS CIS connection events Add support for the ASCS CIS connection state events. Signed-off-by: Emil Gydesen (cherry picked from commit 29f4f24019ac5463d539364cc7c65961e28d71c0) --- .../bluetooth/tester/src/audio/btp/btp_ascs.h | 15 +++++++ .../tester/src/audio/btp_bap_unicast.c | 44 +++++++++++++++++++ tests/bsim/bluetooth/tester/src/bsim_btp.c | 4 ++ 3 files changed, 63 insertions(+) diff --git a/tests/bluetooth/tester/src/audio/btp/btp_ascs.h b/tests/bluetooth/tester/src/audio/btp/btp_ascs.h index 50d3291f76e1..706e6cad1875 100644 --- a/tests/bluetooth/tester/src/audio/btp/btp_ascs.h +++ b/tests/bluetooth/tester/src/audio/btp/btp_ascs.h @@ -117,5 +117,20 @@ struct btp_ascs_ase_state_changed_ev { uint8_t state; } __packed; +#define BTP_ASCS_EV_CIS_CONNECTED 0x83 +struct btp_ascs_cis_connected_ev { + bt_addr_le_t address; + uint8_t ase_id; + uint8_t cis_id; +} __packed; + +#define BTP_ASCS_EV_CIS_DISCONNECTED 0x84 +struct btp_ascs_cis_disconnected_ev { + bt_addr_le_t address; + uint8_t ase_id; + uint8_t cis_id; + uint8_t reason; +} __packed; + #define BTP_ASCS_STATUS_SUCCESS 0x00 #define BTP_ASCS_STATUS_FAILED 0x01 diff --git a/tests/bluetooth/tester/src/audio/btp_bap_unicast.c b/tests/bluetooth/tester/src/audio/btp_bap_unicast.c index c06795e8267a..f620c5e418e7 100644 --- a/tests/bluetooth/tester/src/audio/btp_bap_unicast.c +++ b/tests/bluetooth/tester/src/audio/btp_bap_unicast.c @@ -7,12 +7,15 @@ */ #include +#include #include +#include #include #include #include #include +#include #include #include #include @@ -168,6 +171,37 @@ static void btp_send_ascs_ase_state_changed_ev(struct bt_conn *conn, uint8_t ase tester_event(BTP_SERVICE_ID_ASCS, BTP_ASCS_EV_ASE_STATE_CHANGED, &ev, sizeof(ev)); } +static void btp_send_ascs_cis_connected_ev(const struct bt_bap_stream *stream) +{ + struct btp_bap_unicast_stream *u_stream; + struct btp_ascs_cis_connected_ev ev; + + u_stream = stream_bap_to_unicast(stream); + __ASSERT(u_stream != NULL, "Failed to get unicast_stream from %p", stream); + + bt_addr_le_copy(&ev.address, bt_conn_get_dst(stream->conn)); + ev.ase_id = u_stream->ase_id; + ev.cis_id = u_stream->cis_id; + + tester_event(BTP_SERVICE_ID_ASCS, BTP_ASCS_EV_CIS_CONNECTED, &ev, sizeof(ev)); +} + +static void btp_send_ascs_cis_disconnected_ev(const struct bt_bap_stream *stream, uint8_t reason) +{ + struct btp_bap_unicast_stream *u_stream; + struct btp_ascs_cis_disconnected_ev ev; + + u_stream = stream_bap_to_unicast(stream); + __ASSERT(u_stream != NULL, "Failed to get unicast_stream from %p", stream); + + bt_addr_le_copy(&ev.address, bt_conn_get_dst(stream->conn)); + ev.ase_id = u_stream->ase_id; + ev.cis_id = u_stream->cis_id; + ev.reason = reason; + + tester_event(BTP_SERVICE_ID_ASCS, BTP_ASCS_EV_CIS_DISCONNECTED, &ev, sizeof(ev)); +} + static void btp_send_ascs_operation_completed_ev(struct bt_conn *conn, uint8_t ase_id, uint8_t opcode, uint8_t status) { @@ -669,6 +703,15 @@ static void stream_connected_cb(struct bt_bap_stream *stream) BTP_ASCS_STATUS_SUCCESS); } } + + btp_send_ascs_cis_connected_ev(stream); +} + +static void stream_disconnected_cb(struct bt_bap_stream *stream, uint8_t reason) +{ + LOG_DBG("Disconnected stream %p: 0x%02X", stream, reason); + + btp_send_ascs_cis_disconnected_ev(stream, reason); } static void stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason) @@ -749,6 +792,7 @@ static struct bt_bap_stream_ops stream_ops = { .recv = stream_recv_cb, .sent = btp_bap_audio_stream_sent_cb, .connected = stream_connected_cb, + .disconnected = stream_disconnected_cb, }; struct btp_bap_unicast_stream *btp_bap_unicast_stream_alloc( diff --git a/tests/bsim/bluetooth/tester/src/bsim_btp.c b/tests/bsim/bluetooth/tester/src/bsim_btp.c index 19b196d1fe62..eaeaa427a70e 100644 --- a/tests/bsim/bluetooth/tester/src/bsim_btp.c +++ b/tests/bsim/bluetooth/tester/src/bsim_btp.c @@ -1041,6 +1041,10 @@ static bool is_valid_ascs_packet_len(const struct btp_hdr *hdr, struct net_buf_s return buf_simple->len == 0U; case BTP_ASCS_EV_ASE_STATE_CHANGED: return buf_simple->len == sizeof(struct btp_ascs_ase_state_changed_ev); + case BTP_ASCS_EV_CIS_CONNECTED: + return buf_simple->len == sizeof(struct btp_ascs_cis_connected_ev); + case BTP_ASCS_EV_CIS_DISCONNECTED: + return buf_simple->len == sizeof(struct btp_ascs_cis_disconnected_ev); default: LOG_ERR("Unhandled opcode 0x%02X", hdr->opcode); return false; From 6adc7df677ee60196475ec939b35fbcbf9db416a Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Tue, 18 Nov 2025 22:58:02 +0100 Subject: [PATCH 7/9] [nrf fromtree] tests: Bluetooth: Tester: Allow connecting CIS in QoS state Add support for connecting the CIS in QoS state. To support this some checks were added to check the CIS state while entering the enabling state, as well as the ASE state when the CIS was connected to determine when to send the ASE start opcode. Signed-off-by: Emil Gydesen (cherry picked from commit b8e71e6098e48f5f2f95291825bcdc28f5b3c1e9) --- .../tester/src/audio/btp_bap_unicast.c | 59 +++++++++++++------ 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/tests/bluetooth/tester/src/audio/btp_bap_unicast.c b/tests/bluetooth/tester/src/audio/btp_bap_unicast.c index f620c5e418e7..449ac8076cae 100644 --- a/tests/bluetooth/tester/src/audio/btp_bap_unicast.c +++ b/tests/bluetooth/tester/src/audio/btp_bap_unicast.c @@ -6,6 +6,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include #include #include #include @@ -16,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -558,22 +560,41 @@ static void stream_qos_set_cb(struct bt_bap_stream *stream) static void stream_enabled_cb(struct bt_bap_stream *stream) { - struct bt_bap_ep_info info; - struct bt_conn_info conn_info; + const bool iso_connected = + stream->iso == NULL ? false : stream->iso->state == BT_ISO_STATE_CONNECTED; int err; LOG_DBG("Enabled stream %p", stream); - (void)bt_bap_ep_get_info(stream->ep, &info); - (void)bt_conn_get_info(stream->conn, &conn_info); - if (conn_info.role == BT_HCI_ROLE_PERIPHERAL && info.dir == BT_AUDIO_DIR_SINK) { - /* Automatically do the receiver start ready operation */ - /* TODO: This should ideally be done by the upper tester */ - err = bt_bap_stream_start(stream); - if (err != 0) { - LOG_DBG("Failed to start stream %p", stream); + if (iso_connected) { + struct bt_bap_ep_info ep_info; + struct bt_conn_info conn_info; - return; + err = bt_bap_ep_get_info(stream->ep, &ep_info); + __ASSERT(err == 0, "Failed to get ISO chan info: %d", err); + err = bt_conn_get_info(stream->conn, &conn_info); + __ASSERT(err == 0, "Failed to get ISO chan info: %d", err); + + /* If we are central and the endpoint is a source or if we are a peripheral and the + * endpoint is a sink, then we can start it, else the remote device needs to start + * the endpoint + */ + const bool start_stream = + (IS_ENABLED(CONFIG_BT_CENTRAL) && conn_info.role == BT_HCI_ROLE_CENTRAL && + ep_info.dir == BT_AUDIO_DIR_SOURCE) || + (IS_ENABLED(CONFIG_BT_PERIPHERAL) && + conn_info.role == BT_HCI_ROLE_PERIPHERAL && + ep_info.dir == BT_AUDIO_DIR_SINK); + + if (start_stream) { + /* Automatically do the receiver start ready operation */ + /* TODO: This should ideally be done by the upper tester */ + err = bt_bap_stream_start(stream); + if (err != 0) { + LOG_DBG("Failed to start stream %p", stream); + + return; + } } } @@ -688,12 +709,16 @@ static void stream_connected_cb(struct bt_bap_stream *stream) } if (ep_info.dir == BT_AUDIO_DIR_SOURCE) { - /* Automatically do the receiver start ready operation for source ASEs as - * the client - */ - err = bt_bap_stream_start(stream); - if (err != 0) { - LOG_ERR("Failed to start stream %p", stream); + if (ep_info.state == BT_BAP_EP_STATE_ENABLING) { + /* Automatically do the receiver start ready operation for source + * ASEs as the client when in the enabling state. + * The CIS may be connected in the QoS Configured state as well, in + * which case we should wait until we enter the enabling state. + */ + err = bt_bap_stream_start(stream); + if (err != 0) { + LOG_ERR("Failed to start stream %p", stream); + } } } else { struct btp_bap_unicast_stream *u_stream = stream_bap_to_unicast(stream); From 1318d2a88f263a8c7f9ae1b380bc04334cf6ca29 Mon Sep 17 00:00:00 2001 From: Omkar Kulkarni Date: Mon, 24 Nov 2025 10:50:15 +0100 Subject: [PATCH 8/9] [nrf fromtree] bluetooth: audio: Update encrypt state value Fixes a bug where encrypt state value was not updated based on given parameters. Signed-off-by: Omkar Kulkarni (cherry picked from commit b6e5d03ff4f74b02df05f70cc5c355b721e8beb6) --- subsys/bluetooth/audio/bap_scan_delegator.c | 1 + 1 file changed, 1 insertion(+) diff --git a/subsys/bluetooth/audio/bap_scan_delegator.c b/subsys/bluetooth/audio/bap_scan_delegator.c index 5e6f8384ef75..90520017209f 100644 --- a/subsys/bluetooth/audio/bap_scan_delegator.c +++ b/subsys/bluetooth/audio/bap_scan_delegator.c @@ -1615,6 +1615,7 @@ int bt_bap_scan_delegator_add_src(const struct bt_bap_scan_delegator_add_src_par state->adv_sid = param->sid; state->broadcast_id = param->broadcast_id; state->pa_sync_state = param->pa_state; + state->encrypt_state = param->encrypt_state; state->num_subgroups = param->num_subgroups; if (state->num_subgroups > 0U) { (void)memcpy(state->subgroups, param->subgroups, From 4cd53d177badd11c6a484e7f7c71a43469cd05b9 Mon Sep 17 00:00:00 2001 From: Omkar Kulkarni Date: Thu, 20 Nov 2025 16:36:12 +0100 Subject: [PATCH 9/9] [nrf fromtree] tests: Bluetooth: Tester: Audio: Adds BRS This commit adds API to tester to allow exposing of Broadcast Receive State. Signed-off-by: Omkar Kulkarni (cherry picked from commit d8ed0f8e3de02642e6ccabbb144037688d107ad7) --- subsys/bluetooth/audio/bap_scan_delegator.c | 1 + .../bluetooth/tester/src/audio/btp/btp_bap.h | 14 ++++ tests/bluetooth/tester/src/audio/btp_bap.c | 5 ++ .../tester/src/audio/btp_bap_broadcast.c | 65 +++++++++++++++++++ .../tester/src/audio/btp_bap_broadcast.h | 2 + 5 files changed, 87 insertions(+) diff --git a/subsys/bluetooth/audio/bap_scan_delegator.c b/subsys/bluetooth/audio/bap_scan_delegator.c index 90520017209f..b672640c2b44 100644 --- a/subsys/bluetooth/audio/bap_scan_delegator.c +++ b/subsys/bluetooth/audio/bap_scan_delegator.c @@ -1617,6 +1617,7 @@ int bt_bap_scan_delegator_add_src(const struct bt_bap_scan_delegator_add_src_par state->pa_sync_state = param->pa_state; state->encrypt_state = param->encrypt_state; state->num_subgroups = param->num_subgroups; + if (state->num_subgroups > 0U) { (void)memcpy(state->subgroups, param->subgroups, sizeof(state->subgroups)); diff --git a/tests/bluetooth/tester/src/audio/btp/btp_bap.h b/tests/bluetooth/tester/src/audio/btp/btp_bap.h index 90544cdcbd0d..a9f045b02975 100644 --- a/tests/bluetooth/tester/src/audio/btp/btp_bap.h +++ b/tests/bluetooth/tester/src/audio/btp/btp_bap.h @@ -202,6 +202,20 @@ struct btp_bap_broadcast_source_setup_v2_rp { uint32_t gap_settings; } __packed; +#define BTP_BAP_SCAN_DELEGATOR_ADD_SRC 0x1a +struct btp_bap_scan_delegator_add_src_cmd { + bt_addr_le_t broadcaster_address; + uint8_t advertiser_sid; + uint8_t broadcast_id[BT_AUDIO_BROADCAST_ID_SIZE]; + uint8_t pa_sync_state; + uint8_t big_encryption; + uint8_t num_subgroups; + uint8_t subgroups[]; +} __packed; +struct btp_bap_scan_delegator_add_src_rp { + uint8_t src_id; +} __packed; + /* BAP events */ #define BTP_BAP_EV_DISCOVERY_COMPLETED 0x80 struct btp_bap_discovery_completed_ev { diff --git a/tests/bluetooth/tester/src/audio/btp_bap.c b/tests/bluetooth/tester/src/audio/btp_bap.c index cf9baeec5c3f..c860d7341c6f 100644 --- a/tests/bluetooth/tester/src/audio/btp_bap.c +++ b/tests/bluetooth/tester/src/audio/btp_bap.c @@ -474,6 +474,11 @@ static const struct btp_handler bap_handlers[] = { .expect_len = sizeof(struct btp_bap_send_past_cmd), .func = btp_bap_broadcast_assistant_send_past, }, + { + .opcode = BTP_BAP_SCAN_DELEGATOR_ADD_SRC, + .expect_len = BTP_HANDLER_LENGTH_VARIABLE, + .func = btp_bap_scan_delegator_add_src, + }, #endif /* CONFIG_BT_BAP_BROADCAST_SOURCE || CONFIG_BT_BAP_BROADCAST_SINK */ }; diff --git a/tests/bluetooth/tester/src/audio/btp_bap_broadcast.c b/tests/bluetooth/tester/src/audio/btp_bap_broadcast.c index 3733ab36440a..d301fac6c31f 100644 --- a/tests/bluetooth/tester/src/audio/btp_bap_broadcast.c +++ b/tests/bluetooth/tester/src/audio/btp_bap_broadcast.c @@ -1833,6 +1833,71 @@ uint8_t btp_bap_broadcast_assistant_send_past(const void *cmd, uint16_t cmd_len, return BTP_STATUS_SUCCESS; } +uint8_t btp_bap_scan_delegator_add_src(const void *cmd, uint16_t cmd_len, void *rsp, + uint16_t *rsp_len) +{ + const struct btp_bap_scan_delegator_add_src_cmd *cp = cmd; + struct btp_bap_scan_delegator_add_src_rp *rp = rsp; + struct bt_bap_scan_delegator_add_src_param param = {0}; + struct net_buf_simple buf; + int err; + + if (cmd_len < sizeof(*cp)) { + return BTP_STATUS_FAILED; + } + + if (cp->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) { + return BTP_STATUS_FAILED; + } + + if (cp->big_encryption > BT_BAP_BIG_ENC_STATE_BAD_CODE) { + return BTP_STATUS_FAILED; + } + + bt_addr_le_copy(¶m.addr, &cp->broadcaster_address); + param.sid = cp->advertiser_sid; + param.pa_state = (enum bt_bap_pa_state)cp->pa_sync_state; + param.encrypt_state = (enum bt_bap_big_enc_state)cp->big_encryption; + param.broadcast_id = sys_get_le24(cp->broadcast_id); + param.num_subgroups = cp->num_subgroups; + + net_buf_simple_init_with_data(&buf, (void *)cp->subgroups, cmd_len - sizeof(*cp)); + + for (uint8_t i = 0; i < param.num_subgroups; i++) { + struct bt_bap_bass_subgroup *subgroup = ¶m.subgroups[i]; + + /* If remaining data is less than the necessary subgroup fields, return failed */ + if (buf.len < sizeof(subgroup->bis_sync) + sizeof(subgroup->metadata_len)) { + return BTP_STATUS_FAILED; + } + + subgroup->bis_sync = net_buf_simple_pull_le32(&buf); + subgroup->metadata_len = net_buf_simple_pull_u8(&buf); + + if (subgroup->metadata_len > sizeof(subgroup->metadata) || + subgroup->metadata_len > buf.len) { + return BTP_STATUS_FAILED; + } + + memcpy(subgroup->metadata, net_buf_simple_pull_mem(&buf, subgroup->metadata_len), + subgroup->metadata_len); + } + + if (buf.len != 0U) { + return BTP_STATUS_FAILED; + } + + err = bt_bap_scan_delegator_add_src(¶m); + if (err < 0) { + return BTP_STATUS_VAL(err); + } + + rp->src_id = (uint8_t)err; + *rsp_len = sizeof(*rp); + + return BTP_STATUS_SUCCESS; +} + static bool broadcast_inited; int btp_bap_broadcast_init(void) diff --git a/tests/bluetooth/tester/src/audio/btp_bap_broadcast.h b/tests/bluetooth/tester/src/audio/btp_bap_broadcast.h index 3f4ef82b256c..a5fea384c45d 100644 --- a/tests/bluetooth/tester/src/audio/btp_bap_broadcast.h +++ b/tests/bluetooth/tester/src/audio/btp_bap_broadcast.h @@ -120,3 +120,5 @@ uint8_t btp_bap_broadcast_assistant_set_broadcast_code(const void *cmd, uint16_t void *rsp, uint16_t *rsp_len); uint8_t btp_bap_broadcast_assistant_send_past(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len); +uint8_t btp_bap_scan_delegator_add_src(const void *cmd, uint16_t cmd_len, + void *rsp, uint16_t *rsp_len);