diff --git a/include/zephyr/bluetooth/audio/cap.h b/include/zephyr/bluetooth/audio/cap.h index 4417477860d..a2023f0e983 100644 --- a/include/zephyr/bluetooth/audio/cap.h +++ b/include/zephyr/bluetooth/audio/cap.h @@ -826,6 +826,17 @@ struct bt_cap_commander_cb { * by bt_cap_commander_cancel(). */ void (*broadcast_reception_start)(struct bt_conn *conn, int err); + /** + * @brief Callback for bt_cap_commander_broadcast_reception_stop(). + * + * @param conn Pointer to the connection where the error + * occurred. NULL if @p err is 0 or if cancelled by + * bt_cap_commander_cancel() + * @param err 0 on success, BT_GATT_ERR() with a + * specific ATT (BT_ATT_ERR_*) error code or -ECANCELED if cancelled + * by bt_cap_commander_cancel(). + */ + void (*broadcast_reception_stop)(struct bt_conn *conn, int err); #endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */ }; @@ -954,14 +965,26 @@ int bt_cap_commander_broadcast_reception_start( const struct bt_cap_commander_broadcast_reception_start_param *param); /** Parameters for stopping broadcast reception */ + +struct bt_cap_commander_broadcast_reception_stop_member_param { + /** Coordinated or ad-hoc set member. */ + union bt_cap_set_member member; + + /** Source ID of the receive state. */ + uint8_t src_id; + + /** Number of subgroups */ + size_t num_subgroups; +}; + struct bt_cap_commander_broadcast_reception_stop_param { /** The type of the set. */ enum bt_cap_set_type type; - /** Coordinated or ad-hoc set member. */ - union bt_cap_set_member *members; + /** The set of devices for this procedure */ + struct bt_cap_commander_broadcast_reception_stop_member_param *param; - /** The number of members in @p members */ + /** The number of parameters in @p param */ size_t count; }; diff --git a/subsys/bluetooth/audio/cap_commander.c b/subsys/bluetooth/audio/cap_commander.c index 70dea22f24b..d8e40007eb7 100644 --- a/subsys/bluetooth/audio/cap_commander.c +++ b/subsys/bluetooth/audio/cap_commander.c @@ -100,7 +100,7 @@ int bt_cap_commander_discover(struct bt_conn *conn) #if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT) static struct bt_bap_broadcast_assistant_cb broadcast_assistant_cb; -static bool ba_cb_registered; +static bool broadcast_assistant_cb_registered; static void copy_broadcast_reception_start_param(struct bt_bap_broadcast_assistant_add_src_param *add_src_param, @@ -115,7 +115,7 @@ copy_broadcast_reception_start_param(struct bt_bap_broadcast_assistant_add_src_p add_src_param->subgroups = start_param->subgroups; } -static void cap_commander_ba_add_src_cb(struct bt_conn *conn, int err) +static void cap_commander_broadcast_assistant_add_src_cb(struct bt_conn *conn, int err) { struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); struct bt_bap_broadcast_assistant_add_src_param add_src_param = {0}; @@ -170,7 +170,7 @@ static void cap_commander_ba_add_src_cb(struct bt_conn *conn, int err) } } -static int cap_commander_register_ba_cb(void) +static int cap_commander_register_broadcast_assistant_cb(void) { int err; @@ -181,7 +181,7 @@ static int cap_commander_register_ba_cb(void) return -ENOEXEC; } - ba_cb_registered = true; + broadcast_assistant_cb_registered = true; return 0; } @@ -197,7 +197,7 @@ static bool valid_broadcast_reception_start_param( } CHECKIF(param->count == 0) { - LOG_DBG("Invalid param->count: %u", param->count); + LOG_DBG("Invalid param->count: %zu", param->count); return false; } @@ -345,8 +345,9 @@ int bt_cap_commander_broadcast_reception_start( bt_cap_common_start_proc(BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START, param->count); - broadcast_assistant_cb.add_src = cap_commander_ba_add_src_cb; - if (!ba_cb_registered && cap_commander_register_ba_cb() != 0) { + broadcast_assistant_cb.add_src = cap_commander_broadcast_assistant_add_src_cb; + if (!broadcast_assistant_cb_registered && + cap_commander_register_broadcast_assistant_cb() != 0) { LOG_DBG("Failed to register broadcast assistant callbacks"); return -ENOEXEC; @@ -400,14 +401,278 @@ int bt_cap_commander_broadcast_reception_start( return 0; } -#endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */ + +static void +copy_broadcast_reception_stop_param(struct bt_bap_broadcast_assistant_mod_src_param *mod_src_param, + struct cap_broadcast_reception_stop *stop_param) +{ + mod_src_param->src_id = stop_param->src_id; + mod_src_param->pa_sync = false; + mod_src_param->pa_interval = BT_BAP_PA_INTERVAL_UNKNOWN; + mod_src_param->num_subgroups = stop_param->num_subgroups; + + mod_src_param->subgroups = stop_param->subgroups; +} + +static void cap_commander_broadcast_assistant_recv_state_cb( + struct bt_conn *conn, int err, const struct bt_bap_scan_delegator_recv_state *state) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + + if (state == NULL) { + /* Empty receive state, indicating that the source has been removed + */ + return; + } + + if (bt_cap_common_conn_in_active_proc(conn) && + active_proc->proc_type == BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP) { + + LOG_DBG("BASS recv state: conn %p, src_id %u", (void *)conn, state->src_id); + + for (uint8_t i = 0; i < state->num_subgroups; i++) { + const struct bt_bap_bass_subgroup *subgroup = &state->subgroups[i]; + + /* if bis_sync not equals 0 we can not remove the source (yet) + * and we need to wait for another notification + */ + if (subgroup->bis_sync != 0) { + return; + } + } + + LOG_DBG("Removing source for conn %p", (void *)conn); + err = bt_bap_broadcast_assistant_rem_src(conn, state->src_id); + if (err != 0) { + LOG_DBG("Failed to rem_src for conn %p: %d", (void *)conn, err); + bt_cap_common_abort_proc(conn, err); + cap_commander_proc_complete(); + } + } +} + +static void cap_commander_broadcast_assistant_rem_src_cb(struct bt_conn *conn, int err) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + struct bt_bap_broadcast_assistant_mod_src_param mod_src_param = {0}; + + if (!bt_cap_common_conn_in_active_proc(conn)) { + /* State change happened outside of a procedure; ignore */ + return; + } + + if (err != 0) { + LOG_DBG("Failed removing source: %d", err); + LOG_DBG("Aborting the proc %d %d", active_proc->proc_done_cnt, + active_proc->proc_initiated_cnt); + + bt_cap_common_abort_proc(conn, err); + } else { + active_proc->proc_done_cnt++; + + LOG_DBG("Conn %p broadcast source removed (%zu/%zu streams done)", (void *)conn, + active_proc->proc_done_cnt, active_proc->proc_cnt); + } + + if (bt_cap_common_proc_is_aborted()) { + if (bt_cap_common_proc_all_handled()) { + cap_commander_proc_complete(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) { + struct bt_cap_commander_proc_param *proc_param; + + proc_param = &active_proc->proc_param.commander[active_proc->proc_done_cnt]; + conn = proc_param->conn; + copy_broadcast_reception_stop_param(&mod_src_param, + &proc_param->broadcast_reception_stop); + active_proc->proc_initiated_cnt++; + err = bt_bap_broadcast_assistant_mod_src(conn, &mod_src_param); + if (err != 0) { + LOG_DBG("Failed to mod_src for conn %p: %d", (void *)conn, err); + bt_cap_common_abort_proc(conn, err); + cap_commander_proc_complete(); + } + } else { + cap_commander_proc_complete(); + } +} + +static void cap_commander_broadcast_assistant_mod_src_cb(struct bt_conn *conn, int err) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + + if (!bt_cap_common_conn_in_active_proc(conn)) { + /* State change happened outside of a procedure; ignore */ + return; + } + + if (err != 0) { + LOG_DBG("Failed modifying source: %d", err); + LOG_DBG("Aborting the proc %d %d", active_proc->proc_done_cnt, + active_proc->proc_initiated_cnt); + + bt_cap_common_abort_proc(conn, err); + } else { + LOG_DBG("Conn %p broadcast source modifified (%zu/%zu streams done)", (void *)conn, + active_proc->proc_done_cnt, active_proc->proc_cnt); + } + + if (bt_cap_common_proc_is_aborted()) { + if (bt_cap_common_proc_all_handled()) { + cap_commander_proc_complete(); + } + } +} + +static bool valid_broadcast_reception_stop_param( + const struct bt_cap_commander_broadcast_reception_stop_param *param) +{ + CHECKIF(param == NULL) { + LOG_DBG("param is NULL"); + return false; + } + + CHECKIF(param->count == 0) { + LOG_DBG("Invalid param->count: %zu", param->count); + return false; + } + + CHECKIF(param->count > CONFIG_BT_MAX_CONN) { + LOG_DBG("param->count (%zu) is larger than CONFIG_BT_MAX_CONN (%d)", param->count, + CONFIG_BT_MAX_CONN); + return false; + } + + CHECKIF(param->param == NULL) { + LOG_DBG("param->param is NULL"); + return false; + } + + for (size_t i = 0; i < param->count; i++) { + const struct bt_cap_commander_broadcast_reception_stop_member_param *stop_param = + ¶m->param[i]; + const union bt_cap_set_member *member = ¶m->param[i].member; + const struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, member); + + if (member == NULL) { + LOG_DBG("param->param[%zu].member is NULL", i); + return false; + } + + if (member_conn == NULL) { + LOG_DBG("Invalid param->param[%zu].member", i); + return false; + } + + CHECKIF(stop_param->num_subgroups == 0) { + LOG_DBG("param->param[%zu]->num_subgroups is 0", i); + return false; + } + + CHECKIF(stop_param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) { + LOG_DBG("Too many subgroups %u/%u", stop_param->num_subgroups, + CONFIG_BT_BAP_BASS_MAX_SUBGROUPS); + return false; + } + + for (size_t j = 0U; j < i; j++) { + const union bt_cap_set_member *other = ¶m->param[j].member; + uint8_t other_src_id = param->param[j].src_id; + + if (other == member && stop_param->src_id == other_src_id) { + LOG_DBG("param->members[%zu], src_id %d (%p) is duplicated by " + "param->members[%zu], src_id %d (%p)", + j, other_src_id, other, i, stop_param->src_id, member); + return false; + } + } + } + + return true; +} int bt_cap_commander_broadcast_reception_stop( const struct bt_cap_commander_broadcast_reception_stop_param *param) { - return -ENOSYS; + struct bt_bap_broadcast_assistant_mod_src_param mod_src_param = {0}; + struct bt_cap_commander_proc_param *proc_param; + struct bt_cap_common_proc *active_proc; + struct bt_conn *conn; + int err; + + if (bt_cap_common_proc_is_active()) { + LOG_DBG("A CAP procedure is already in progress"); + + return -EBUSY; + } + + if (!valid_broadcast_reception_stop_param(param)) { + return -EINVAL; + } + + bt_cap_common_start_proc(BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP, param->count); + + broadcast_assistant_cb.mod_src = cap_commander_broadcast_assistant_mod_src_cb; + broadcast_assistant_cb.rem_src = cap_commander_broadcast_assistant_rem_src_cb; + broadcast_assistant_cb.recv_state = cap_commander_broadcast_assistant_recv_state_cb; + if (!broadcast_assistant_cb_registered && + cap_commander_register_broadcast_assistant_cb() != 0) { + LOG_DBG("Failed to register broadcast assistant callbacks"); + + return -ENOEXEC; + } + + active_proc = bt_cap_common_get_active_proc(); + + for (size_t i = 0U; i < param->count; i++) { + const struct bt_cap_commander_broadcast_reception_stop_member_param *member_param = + ¶m->param[i]; + struct bt_cap_commander_proc_param *stored_param; + struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, &member_param->member); + + if (member_conn == NULL) { + LOG_DBG("Invalid param->member[%zu]", i); + + return -EINVAL; + } + /* Store the necessary parameters as we cannot assume that the supplied + * parameters are kept valid + */ + stored_param = &active_proc->proc_param.commander[i]; + stored_param->conn = member_conn; + stored_param->broadcast_reception_stop.src_id = member_param->src_id; + stored_param->broadcast_reception_stop.num_subgroups = member_param->num_subgroups; + for (size_t j = 0U; j < CONFIG_BT_BAP_BASS_MAX_SUBGROUPS; j++) { + stored_param->broadcast_reception_stop.subgroups[j].bis_sync = 0; + stored_param->broadcast_reception_stop.subgroups[j].metadata_len = 0; + } + } + + proc_param = &active_proc->proc_param.commander[0]; + + conn = proc_param->conn; + copy_broadcast_reception_stop_param(&mod_src_param, &proc_param->broadcast_reception_stop); + + active_proc->proc_initiated_cnt++; + + err = bt_bap_broadcast_assistant_mod_src(conn, &mod_src_param); + if (err != 0) { + LOG_DBG("Failed to stop broadcast reception for conn %p: %d", (void *)conn, err); + + return -ENOEXEC; + } + + return 0; } +#endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */ + static void cap_commander_proc_complete(void) { struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); @@ -464,6 +729,11 @@ static void cap_commander_proc_complete(void) cap_cb->broadcast_reception_start(failed_conn, err); } break; + case BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP: + if (cap_cb->broadcast_reception_stop != NULL) { + cap_cb->broadcast_reception_stop(failed_conn, err); + } + break; #endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */ case BT_CAP_COMMON_PROC_TYPE_NONE: default: diff --git a/subsys/bluetooth/audio/cap_common.c b/subsys/bluetooth/audio/cap_common.c index 8780a3e357a..5d277bbf19a 100644 --- a/subsys/bluetooth/audio/cap_common.c +++ b/subsys/bluetooth/audio/cap_common.c @@ -163,6 +163,7 @@ static bool active_proc_is_commander(void) case BT_CAP_COMMON_PROC_TYPE_MICROPHONE_GAIN_CHANGE: case BT_CAP_COMMON_PROC_TYPE_MICROPHONE_MUTE_CHANGE: case BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START: + case BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP: return true; default: return false; @@ -190,7 +191,7 @@ bool bt_cap_common_conn_in_active_proc(const struct bt_conn *conn) return true; } } -#endif /* CONFIG_BT_CAP_INITIATOR_UNICAST */ +#endif /* CONFIG_BT_CAP_COMMANDER */ } return false; diff --git a/subsys/bluetooth/audio/cap_internal.h b/subsys/bluetooth/audio/cap_internal.h index 3543b35c387..c063f8c8341 100644 --- a/subsys/bluetooth/audio/cap_internal.h +++ b/subsys/bluetooth/audio/cap_internal.h @@ -48,6 +48,7 @@ enum bt_cap_common_proc_type { BT_CAP_COMMON_PROC_TYPE_UPDATE, BT_CAP_COMMON_PROC_TYPE_STOP, BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START, + BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP, BT_CAP_COMMON_PROC_TYPE_VOLUME_CHANGE, BT_CAP_COMMON_PROC_TYPE_VOLUME_OFFSET_CHANGE, BT_CAP_COMMON_PROC_TYPE_VOLUME_MUTE_CHANGE, @@ -99,6 +100,12 @@ struct cap_broadcast_reception_start { uint8_t num_subgroups; struct bt_bap_bass_subgroup subgroups[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS]; }; + +struct cap_broadcast_reception_stop { + uint8_t src_id; + uint8_t num_subgroups; + struct bt_bap_bass_subgroup subgroups[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS]; +}; #endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */ struct bt_cap_commander_proc_param { @@ -120,6 +127,7 @@ struct bt_cap_commander_proc_param { #endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ #if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT) struct cap_broadcast_reception_start broadcast_reception_start; + struct cap_broadcast_reception_stop broadcast_reception_stop; #endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */ #if defined(CONFIG_BT_MICP_MIC_CTLR) struct { diff --git a/tests/bluetooth/audio/cap_commander/include/cap_commander.h b/tests/bluetooth/audio/cap_commander/include/cap_commander.h index 3f1f8691fd9..68690073d6e 100644 --- a/tests/bluetooth/audio/cap_commander/include/cap_commander.h +++ b/tests/bluetooth/audio/cap_commander/include/cap_commander.h @@ -24,5 +24,6 @@ DECLARE_FAKE_VOID_FUNC(mock_cap_commander_volume_offset_changed_cb, struct bt_co DECLARE_FAKE_VOID_FUNC(mock_cap_commander_microphone_mute_changed_cb, struct bt_conn *, int); DECLARE_FAKE_VOID_FUNC(mock_cap_commander_microphone_gain_changed_cb, struct bt_conn *, int); DECLARE_FAKE_VOID_FUNC(mock_cap_commander_broadcast_reception_start_cb, struct bt_conn *, int); +DECLARE_FAKE_VOID_FUNC(mock_cap_commander_broadcast_reception_stop_cb, struct bt_conn *, int); #endif /* MOCKS_CAP_COMMANDER_H_ */ diff --git a/tests/bluetooth/audio/cap_commander/src/test_broadcast_reception.c b/tests/bluetooth/audio/cap_commander/src/test_broadcast_reception.c index ee6c82761f2..7da6150d081 100644 --- a/tests/bluetooth/audio/cap_commander/src/test_broadcast_reception.c +++ b/tests/bluetooth/audio/cap_commander/src/test_broadcast_reception.c @@ -32,13 +32,17 @@ LOG_MODULE_REGISTER(bt_broadcast_reception_test, CONFIG_BT_CAP_COMMANDER_LOG_LEV struct cap_commander_test_broadcast_reception_fixture { struct bt_conn conns[CONFIG_BT_MAX_CONN]; - struct bt_bap_bass_subgroup start_subgroups[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS]; + struct bt_bap_bass_subgroup subgroups[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS]; struct bt_cap_commander_broadcast_reception_start_member_param start_member_params[CONFIG_BT_MAX_CONN]; struct bt_cap_commander_broadcast_reception_start_param start_param; + struct bt_cap_commander_broadcast_reception_stop_member_param + stop_member_params[CONFIG_BT_MAX_CONN]; + struct bt_cap_commander_broadcast_reception_stop_param stop_param; }; static void test_start_param_init(void *f); +static void test_stop_param_init(void *f); static void cap_commander_test_broadcast_reception_fixture_init( struct cap_commander_test_broadcast_reception_fixture *fixture) @@ -47,6 +51,7 @@ static void cap_commander_test_broadcast_reception_fixture_init( test_conn_init(&fixture->conns[i]); } test_start_param_init(fixture); + test_stop_param_init(fixture); } static void *cap_commander_test_broadcast_reception_setup(void) @@ -91,9 +96,9 @@ static void test_start_param_init(void *f) fixture->start_param.count = ARRAY_SIZE(fixture->start_member_params); - for (size_t i = 0; i < ARRAY_SIZE(fixture->start_subgroups); i++) { - fixture->start_subgroups[i].bis_sync = i; - fixture->start_subgroups[i].metadata_len = 0; + for (size_t i = 0; i < ARRAY_SIZE(fixture->subgroups); i++) { + fixture->subgroups[i].bis_sync = 1 << i; + fixture->subgroups[i].metadata_len = 0; } for (size_t i = 0U; i < ARRAY_SIZE(fixture->start_member_params); i++) { @@ -102,7 +107,7 @@ static void test_start_param_init(void *f) fixture->start_member_params[i].adv_sid = SID; fixture->start_member_params[i].pa_interval = ADV_INTERVAL; fixture->start_member_params[i].broadcast_id = BROADCAST_ID; - memcpy(fixture->start_member_params[i].subgroups, &fixture->start_subgroups[0], + memcpy(fixture->start_member_params[i].subgroups, &fixture->subgroups[0], sizeof(struct bt_bap_bass_subgroup) * CONFIG_BT_BAP_BASS_MAX_SUBGROUPS); fixture->start_member_params[i].num_subgroups = CONFIG_BT_BAP_BASS_MAX_SUBGROUPS; } @@ -117,6 +122,28 @@ static void test_start_param_init(void *f) } } +static void test_stop_param_init(void *f) +{ + struct cap_commander_test_broadcast_reception_fixture *fixture = f; + int err; + + fixture->stop_param.type = BT_CAP_SET_TYPE_AD_HOC; + fixture->stop_param.param = fixture->stop_member_params; + + fixture->stop_param.count = ARRAY_SIZE(fixture->stop_member_params); + + for (size_t i = 0U; i < ARRAY_SIZE(fixture->stop_member_params); i++) { + fixture->stop_member_params[i].member.member = &fixture->conns[i]; + fixture->stop_member_params[i].src_id = SID; + fixture->stop_member_params[i].num_subgroups = CONFIG_BT_BAP_BASS_MAX_SUBGROUPS; + } + + for (size_t i = 0; i < ARRAY_SIZE(fixture->conns); i++) { + err = bt_cap_commander_discover(&fixture->conns[i]); + zassert_equal(0, err, "Unexpected return value %d", err); + } +} + ZTEST_SUITE(cap_commander_test_broadcast_reception, NULL, cap_commander_test_broadcast_reception_setup, cap_commander_test_broadcast_reception_before, @@ -371,3 +398,186 @@ ZTEST_F(cap_commander_test_broadcast_reception, test_commander_reception_start_i zexpect_call_count("bt_cap_commander_cb.broadcast_reception_start", 0, mock_cap_commander_broadcast_reception_start_cb_fake.call_count); } + +ZTEST_F(cap_commander_test_broadcast_reception, test_commander_reception_stop_default_subgroups) +{ + int err; + + err = bt_cap_commander_register_cb(&mock_cap_commander_cb); + zassert_equal(0, err, "Unexpected return value %d", err); + + err = bt_cap_commander_broadcast_reception_start(&fixture->start_param); + zassert_equal(0, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_cap_commander_cb.broadcast_reception_start", 1, + mock_cap_commander_broadcast_reception_start_cb_fake.call_count); + zassert_equal_ptr(NULL, + mock_cap_commander_broadcast_reception_start_cb_fake.arg0_history[0]); + zassert_equal(0, mock_cap_commander_broadcast_reception_start_cb_fake.arg1_history[0]); + + err = bt_cap_commander_broadcast_reception_stop(&fixture->stop_param); + zassert_equal(0, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_cap_commander_cb.broadcast_reception_stop", 1, + mock_cap_commander_broadcast_reception_stop_cb_fake.call_count); + zassert_equal_ptr(NULL, + mock_cap_commander_broadcast_reception_stop_cb_fake.arg0_history[0]); + zassert_equal(0, mock_cap_commander_broadcast_reception_stop_cb_fake.arg1_history[0]); +} + +ZTEST_F(cap_commander_test_broadcast_reception, test_commander_reception_stop_one_subgroup) +{ + int err; + + err = bt_cap_commander_register_cb(&mock_cap_commander_cb); + zassert_equal(0, err, "Unexpected return value %d", err); + + err = bt_cap_commander_broadcast_reception_start(&fixture->start_param); + zassert_equal(0, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_cap_commander_cb.broadcast_reception_start", 1, + mock_cap_commander_broadcast_reception_start_cb_fake.call_count); + zassert_equal_ptr(NULL, + mock_cap_commander_broadcast_reception_start_cb_fake.arg0_history[0]); + zassert_equal(0, mock_cap_commander_broadcast_reception_start_cb_fake.arg1_history[0]); + + /* We test with one subgroup, instead of CONFIG_BT_BAP_BASS_MAX_SUBGROUPS subgroups */ + for (size_t i = 0U; i < CONFIG_BT_MAX_CONN; i++) { + printk("Source ID %d: %d\n", i, fixture->stop_param.param[i].src_id); + + fixture->stop_param.param[i].num_subgroups = 1; + } + + err = bt_cap_commander_broadcast_reception_stop(&fixture->stop_param); + zassert_equal(0, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_cap_commander_cb.broadcast_reception_stop", 1, + mock_cap_commander_broadcast_reception_stop_cb_fake.call_count); + zassert_equal_ptr(NULL, + mock_cap_commander_broadcast_reception_stop_cb_fake.arg0_history[0]); + zassert_equal(0, mock_cap_commander_broadcast_reception_stop_cb_fake.arg1_history[0]); +} + +ZTEST_F(cap_commander_test_broadcast_reception, test_commander_reception_stop_double) +{ + int err; + + err = bt_cap_commander_register_cb(&mock_cap_commander_cb); + zassert_equal(0, err, "Unexpected return value %d", err); + printk("Source ID: %d\n", fixture->stop_param.param[0].src_id); + err = bt_cap_commander_broadcast_reception_stop(&fixture->stop_param); + zassert_equal(0, err, "Unexpected return value %d", err); + + err = bt_cap_commander_broadcast_reception_stop(&fixture->stop_param); + zassert_equal(0, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_cap_commander_cb.broadcast_reception_stop", 2, + mock_cap_commander_broadcast_reception_stop_cb_fake.call_count); +} + +ZTEST_F(cap_commander_test_broadcast_reception, test_commander_reception_stop_inval_param_null) +{ + int err; + + err = bt_cap_commander_broadcast_reception_stop(NULL); + zassert_equal(-EINVAL, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_cap_commander_cb.broadcast_reception_stop", 0, + mock_cap_commander_broadcast_reception_stop_cb_fake.call_count); +} + +ZTEST_F(cap_commander_test_broadcast_reception, + test_commander_reception_stop_inval_param_zero_count) +{ + int err; + + fixture->stop_param.count = 0; + + err = bt_cap_commander_broadcast_reception_stop(&fixture->stop_param); + zassert_equal(-EINVAL, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_cap_commander_cb.broadcast_reception_stop", 0, + mock_cap_commander_broadcast_reception_stop_cb_fake.call_count); +} + +ZTEST_F(cap_commander_test_broadcast_reception, + test_commander_reception_stop_inval_param_high_count) +{ + int err; + + fixture->stop_param.count = CONFIG_BT_MAX_CONN + 1; + + err = bt_cap_commander_broadcast_reception_stop(&fixture->stop_param); + zassert_equal(-EINVAL, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_cap_commander_cb.broadcast_reception_stop", 0, + mock_cap_commander_broadcast_reception_stop_cb_fake.call_count); +} + +ZTEST_F(cap_commander_test_broadcast_reception, + test_commander_reception_stop_inval_param_null_param) +{ + int err; + + fixture->stop_param.type = BT_CAP_SET_TYPE_AD_HOC; + fixture->stop_param.param = NULL; + fixture->stop_param.count = ARRAY_SIZE(fixture->conns); + + err = bt_cap_commander_broadcast_reception_stop(&fixture->stop_param); + zassert_equal(-EINVAL, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_cap_commander_cb.broadcast_reception_stop", 0, + mock_cap_commander_broadcast_reception_stop_cb_fake.call_count); +} + +ZTEST_F(cap_commander_test_broadcast_reception, test_commander_reception_stop_inval_null_member) +{ + int err; + + fixture->stop_param.param[0].member.member = NULL; + + err = bt_cap_commander_broadcast_reception_stop(&fixture->stop_param); + zassert_equal(-EINVAL, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_cap_commander_cb.broadcast_reception_stop", 0, + mock_cap_commander_broadcast_reception_stop_cb_fake.call_count); +} + +ZTEST_F(cap_commander_test_broadcast_reception, test_commander_reception_stop_inval_missing_cas) +{ + int err; + + fixture->stop_param.type = BT_CAP_SET_TYPE_CSIP; + + err = bt_cap_commander_broadcast_reception_stop(&fixture->stop_param); + zassert_equal(-EINVAL, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_cap_commander_cb.broadcast_reception_stop", 0, + mock_cap_commander_broadcast_reception_stop_cb_fake.call_count); +} + +ZTEST_F(cap_commander_test_broadcast_reception, test_commander_reception_stop_inval_no_subgroups) +{ + int err; + + fixture->stop_param.param[0].num_subgroups = 0; + + err = bt_cap_commander_broadcast_reception_stop(&fixture->stop_param); + zassert_equal(-EINVAL, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_cap_commander_cb.broadcast_reception_stop", 0, + mock_cap_commander_broadcast_reception_stop_cb_fake.call_count); +} + +ZTEST_F(cap_commander_test_broadcast_reception, test_commander_reception_stop_inval_num_subgroups) +{ + int err; + + fixture->stop_param.param[0].num_subgroups = CONFIG_BT_BAP_BASS_MAX_SUBGROUPS + 1; + + err = bt_cap_commander_broadcast_reception_stop(&fixture->stop_param); + zassert_equal(-EINVAL, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_cap_commander_cb.broadcast_reception_stop", 0, + mock_cap_commander_broadcast_reception_stop_cb_fake.call_count); +} diff --git a/tests/bluetooth/audio/cap_commander/uut/bap_broadcast_assistant.c b/tests/bluetooth/audio/cap_commander/uut/bap_broadcast_assistant.c index e4ec106c8f6..606eb71aad8 100644 --- a/tests/bluetooth/audio/cap_commander/uut/bap_broadcast_assistant.c +++ b/tests/bluetooth/audio/cap_commander/uut/bap_broadcast_assistant.c @@ -28,3 +28,40 @@ int bt_bap_broadcast_assistant_add_src(struct bt_conn *conn, return 0; } + +int bt_bap_broadcast_assistant_mod_src(struct bt_conn *conn, + const struct bt_bap_broadcast_assistant_mod_src_param *param) +{ + struct bt_bap_scan_delegator_recv_state state; + + zassert_not_null(conn, "conn is NULL"); + zassert_not_null(param, "param is NULL"); + + state.pa_sync_state = param->pa_sync ? BT_BAP_PA_STATE_SYNCED : BT_BAP_PA_STATE_NOT_SYNCED; + state.src_id = param->src_id; + state.num_subgroups = param->num_subgroups; + for (size_t i = 0; i < param->num_subgroups; i++) { + state.subgroups[i].bis_sync = param->subgroups[i].bis_sync; + } + + if (broadcast_assistant_cbs->mod_src != NULL) { + broadcast_assistant_cbs->mod_src(conn, 0); + } + if (broadcast_assistant_cbs->recv_state != NULL) { + broadcast_assistant_cbs->recv_state(conn, 0, &state); + } + + return 0; +} + +int bt_bap_broadcast_assistant_rem_src(struct bt_conn *conn, uint8_t src_id) +{ + zassert_not_null(conn, "conn is NULL"); + zassert_not_equal(src_id, 0, "src_id is 0"); + + if (broadcast_assistant_cbs->rem_src != NULL) { + broadcast_assistant_cbs->rem_src(conn, 0); + } + + return 0; +} diff --git a/tests/bluetooth/audio/cap_commander/uut/cap_commander.c b/tests/bluetooth/audio/cap_commander/uut/cap_commander.c index 19e382aec2c..1e1de370b73 100644 --- a/tests/bluetooth/audio/cap_commander/uut/cap_commander.c +++ b/tests/bluetooth/audio/cap_commander/uut/cap_commander.c @@ -16,7 +16,8 @@ FAKE(mock_cap_commander_volume_offset_changed_cb) \ FAKE(mock_cap_commander_microphone_mute_changed_cb) \ FAKE(mock_cap_commander_microphone_gain_changed_cb) \ - FAKE(mock_cap_commander_broadcast_reception_start_cb) + FAKE(mock_cap_commander_broadcast_reception_start_cb) \ + FAKE(mock_cap_commander_broadcast_reception_stop_cb) DEFINE_FAKE_VOID_FUNC(mock_cap_commander_discovery_complete_cb, struct bt_conn *, int, const struct bt_csip_set_coordinator_set_member *, @@ -28,6 +29,7 @@ DEFINE_FAKE_VOID_FUNC(mock_cap_commander_volume_offset_changed_cb, struct bt_con DEFINE_FAKE_VOID_FUNC(mock_cap_commander_microphone_mute_changed_cb, struct bt_conn *, int); DEFINE_FAKE_VOID_FUNC(mock_cap_commander_microphone_gain_changed_cb, struct bt_conn *, int); DEFINE_FAKE_VOID_FUNC(mock_cap_commander_broadcast_reception_start_cb, struct bt_conn *, int); +DEFINE_FAKE_VOID_FUNC(mock_cap_commander_broadcast_reception_stop_cb, struct bt_conn *, int); const struct bt_cap_commander_cb mock_cap_commander_cb = { .discovery_complete = mock_cap_commander_discovery_complete_cb, @@ -46,6 +48,7 @@ const struct bt_cap_commander_cb mock_cap_commander_cb = { #endif /* CONFIG_BT_MICP_MIC_CTLR */ #if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT) .broadcast_reception_start = mock_cap_commander_broadcast_reception_start_cb, + .broadcast_reception_stop = mock_cap_commander_broadcast_reception_stop_cb, #endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */ }; diff --git a/tests/bsim/bluetooth/audio/src/cap_commander_test.c b/tests/bsim/bluetooth/audio/src/cap_commander_test.c index 7a849fc1912..e27b75e91e6 100644 --- a/tests/bsim/bluetooth/audio/src/cap_commander_test.c +++ b/tests/bsim/bluetooth/audio/src/cap_commander_test.c @@ -982,7 +982,7 @@ static void test_broadcast_reception_stop(size_t acceptor_count) /* reception stop is not implemented yet, for now the following command will fail*/ reception_stop_param.type = BT_CAP_SET_TYPE_AD_HOC; - reception_stop_param.members = NULL; + reception_stop_param.param = NULL; reception_stop_param.count = 0U; err = bt_cap_commander_broadcast_reception_stop(&reception_stop_param); if (err != 0) {