* [PATCH BlueZ v1 0/7] BAP ASE state handling enhancements
@ 2026-11-03 20:30 Luiz Augusto von Dentz
2026-11-03 20:30 ` [PATCH BlueZ v1 1/7] shared/bap: Defer ASE state notifications until idle Luiz Augusto von Dentz
` (6 more replies)
0 siblings, 7 replies; 9+ messages in thread
From: Luiz Augusto von Dentz @ 2026-11-03 20:30 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
The ASCS server deferred the ASE state notifications by 10 ms after the
ASE Control Point notification, while the client completed requests on
the CP notification alone, so it could act on a stale ASE state, e.g.
failing MediaTransport1.Acquire right after Release, whenever the
notifications were not received together.
This is fixed on both sides: the server now defers the ASE state
notifications until idle, so they are sent in the same mainloop cycle
as the CP notification and can be aggregated into the same Notify
Multiple PDU, and the client waits for the ASE state before completing
requests, also making cancelling or detaching a stream which is part of
a grouped request only affect its own request.
For testing, the tester gains markers for PDUs that can be exchanged in
alternative ways, which unit/test-bap uses to run every unicast test a
second time with Notify Multiple, both as client and server, using the
same test data.
Luiz Augusto von Dentz (7):
shared/bap: Defer ASE state notifications until idle
shared/bap: Wait for ASE state before completing requests
unit/test-bap: Add tests for cancelling grouped Enable
unit/test-bap: Add Notify Multiple test
shared/tester: Add support for alternative PDUs
unit/test-bap: Expect the ASE state following Releasing
unit/test-bap: Run unicast tests with Notify Multiple
src/shared/bap.c | 456 ++++++++++++++++++++++++++++++++++++++++----
src/shared/tester.c | 74 +++++++
src/shared/tester.h | 13 ++
unit/test-bap.c | 421 +++++++++++++++++++++++++++++++++++++---
4 files changed, 900 insertions(+), 64 deletions(-)
--
2.56.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH BlueZ v1 1/7] shared/bap: Defer ASE state notifications until idle
2026-11-03 20:30 [PATCH BlueZ v1 0/7] BAP ASE state handling enhancements Luiz Augusto von Dentz
@ 2026-11-03 20:30 ` Luiz Augusto von Dentz
2026-11-03 22:51 ` BAP ASE state handling enhancements bluez.test.bot
2026-11-03 20:30 ` [PATCH BlueZ v1 2/7] shared/bap: Wait for ASE state before completing requests Luiz Augusto von Dentz
` (5 subsequent siblings)
6 siblings, 1 reply; 9+ messages in thread
From: Luiz Augusto von Dentz @ 2026-11-03 20:30 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
While handling an ASE Control Point write, ASE state notifications are
deferred by BAP_PROCESS_TIMEOUT so they are not sent ahead of the CP
notification. This means the CP notification and the ASE state
notifications can be sent 10 ms apart, so a client that acts on the CP
notification may still see the old ASE state.
Fix this by deferring the ASE state notifications until the mainloop is
idle instead, which still sends them after the CP notification but in
the same mainloop cycle so they can be grouped into the same Notify
Multiple PDU. The deferral is scheduled after the state callbacks so any
I/O setup by them, e.g. a CIS loss, is processed first.
---
src/shared/bap.c | 32 ++++++++++++++++++++++++--------
1 file changed, 24 insertions(+), 8 deletions(-)
diff --git a/src/shared/bap.c b/src/shared/bap.c
index b1180b532b8d..24a43ba07b55 100644
--- a/src/shared/bap.c
+++ b/src/shared/bap.c
@@ -285,6 +285,7 @@ struct bt_bap_stream {
uint8_t old_state;
uint8_t state;
unsigned int state_id;
+ bool state_defer;
struct queue *pending_states;
bool no_cache_config;
bool client;
@@ -1841,17 +1842,32 @@ static void bap_ucast_set_state(struct bt_bap_stream *stream, uint8_t state)
if (stream->client)
goto done;
- if (!stream->bap->in_cp_write)
+ if (!stream->bap->in_cp_write) {
stream_notify_state(stream);
- else if (!stream->state_id)
- stream->state_id = timeout_add(BAP_PROCESS_TIMEOUT,
- stream_notify_state,
- bt_bap_stream_ref(stream),
- bt_bap_stream_unref);
- else /* If a state_id is already pending then queue the old one */
+ } else if (stream->state_id || stream->state_defer) {
+ /* If a notification is already pending then queue the old
+ * state.
+ */
queue_push_tail(stream->pending_states,
UINT_TO_PTR(ep->old_state));
-
+ } else {
+ /* Defer ASE state notifications until the mainloop is idle so
+ * the CP notification is sent first while still allowing them
+ * to be grouped into the same Notify Multiple PDU. This is
+ * scheduled after the state callbacks so any I/O they setup,
+ * e.g. on CIS loss, is processed first.
+ */
+ stream->state_defer = true;
+ bt_bap_stream_ref(stream);
+ bap_stream_state_changed(stream);
+ stream->state_defer = false;
+ if (!stream->state_id)
+ stream->state_id = timeout_add(0, stream_notify_state,
+ bt_bap_stream_ref(stream),
+ bt_bap_stream_unref);
+ bt_bap_stream_unref(stream);
+ return;
+ }
done:
bap_stream_state_changed(stream);
--
2.56.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH BlueZ v1 2/7] shared/bap: Wait for ASE state before completing requests
2026-11-03 20:30 [PATCH BlueZ v1 0/7] BAP ASE state handling enhancements Luiz Augusto von Dentz
2026-11-03 20:30 ` [PATCH BlueZ v1 1/7] shared/bap: Defer ASE state notifications until idle Luiz Augusto von Dentz
@ 2026-11-03 20:30 ` Luiz Augusto von Dentz
2026-11-03 20:30 ` [PATCH BlueZ v1 3/7] unit/test-bap: Add tests for cancelling grouped Enable Luiz Augusto von Dentz
` (4 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Luiz Augusto von Dentz @ 2026-11-03 20:30 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
When acting as client the ASE Control Point notification may arrive
before the ASE state notifications, so completing the request on the CP
notification alone may cause the upper layer to act on a stale state,
e.g. replying to MediaTransport1.Release and then failing the following
Acquire with an I/O error since the ASE is still in Streaming state.
Fix this by keeping the request outstanding until each ASE whose
operation succeeded has left the state it was in when the request was
sent, with BAP_STATE_TIMEOUT as a fallback in case the server never
notifies it. Further requests are not sent in the meantime, so each
request only waits for the states it caused. Each request in a group is
now also completed with the response of its own ASE.
Since requests can now be outstanding for longer, detaching or
cancelling a stream grouped in a queued request now only removes its
own request, promoting the next one in the group if it was the leader,
instead of completing or freeing the whole group. Cancelling the
outstanding request only drops its callback, so it still waits for its
CP notification and ASE state before the next request is sent. The
requests are unlinked before their callbacks are called, the queue is
processed again if the outstanding request is removed, and a detached
request whose CP notification is still pending is replaced by a
placeholder so the notification is not taken as the response of the
next request.
---
src/shared/bap.c | 424 ++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 397 insertions(+), 27 deletions(-)
diff --git a/src/shared/bap.c b/src/shared/bap.c
index 24a43ba07b55..8cb7fb40f16c 100644
--- a/src/shared/bap.c
+++ b/src/shared/bap.c
@@ -48,6 +48,7 @@
}
#define BAP_PROCESS_TIMEOUT 10
+#define BAP_STATE_TIMEOUT 1000
#define CODEC_SPECIFIC_CONFIGURATION_MASK (\
(1<<BT_BAP_CC_FREQ)|\
@@ -156,6 +157,10 @@ struct bt_bap_req {
size_t len;
bt_bap_stream_func_t func;
void *user_data;
+ uint8_t state;
+ bool wait;
+ bool has_rsp;
+ struct bt_ascs_ase_rsp rsp;
};
typedef void (*bap_notify_t)(struct bt_bap *bap, uint16_t value_handle,
@@ -176,6 +181,8 @@ struct bt_bap {
struct bt_gatt_client *client;
struct bt_att *att;
struct bt_bap_req *req;
+ unsigned int wait_id;
+ struct bt_ascs_ase_rsp wait_rsp;
unsigned int cp_id;
unsigned int process_id;
@@ -1271,6 +1278,10 @@ static void bap_req_complete(struct bt_bap_req *req,
{
struct queue *group;
+ /* Use the response of the request own ASE if there is one */
+ if (req->has_rsp)
+ rsp = &req->rsp;
+
if (!req->func)
goto done;
@@ -1300,26 +1311,175 @@ static bool match_req_stream(const void *data, const void *match_data)
return req->stream == match_data;
}
-static void bap_req_abort(void *data)
+static bool req_is_waiting(const void *data, const void *user_data)
+{
+ const struct bt_bap_req *req = data;
+
+ return req->wait;
+}
+
+static bool bap_req_waiting(struct bt_bap_req *req)
+{
+ return req->wait || queue_find(req->group, req_is_waiting, NULL);
+}
+
+static void bap_wait_complete(struct bt_bap *bap)
+{
+ struct bt_bap_req *req = bap->req;
+ struct bt_ascs_ase_rsp rsp = bap->wait_rsp;
+
+ if (bap->wait_id) {
+ timeout_remove(bap->wait_id);
+ bap->wait_id = 0;
+ }
+
+ bap->req = NULL;
+ bap_req_complete(req, &rsp);
+}
+
+static bool bap_process_queue(void *data);
+
+/* Complete the request once no stream is waiting for its ASE state */
+static void bap_wait_check(struct bt_bap *bap)
+{
+ if (!bap->req || !bap->wait_id || bap_req_waiting(bap->req))
+ return;
+
+ bap_wait_complete(bap);
+ bap_process_queue(bap);
+}
+
+static void req_clear_wait(void *data, void *user_data)
{
struct bt_bap_req *req = data;
- struct bt_bap *bap = req->stream->bap;
- DBG(bap, "req %p", req);
- bap_req_complete(req, NULL);
+ /* Ignore notifications that don't change the state, e.g. metadata */
+ if (req->stream == user_data && req->stream->ep->state != req->state)
+ req->wait = false;
+}
+
+static void bap_wait_state_changed(struct bt_bap *bap,
+ struct bt_bap_stream *stream)
+{
+ if (!bap->req || !bap->wait_id)
+ return;
+
+ req_clear_wait(bap->req, stream);
+ queue_foreach(bap->req->group, req_clear_wait, stream);
+
+ bap_wait_check(bap);
+}
+
+/* Keep a request without callback in place of a cancelled request whose
+ * CP notification is still pending, so it is not taken as the response of
+ * the next request.
+ */
+static struct bt_bap_req *bap_req_placeholder(struct bt_bap *bap,
+ uint8_t op)
+{
+ struct bt_bap_req *req;
+
+ req = new0(struct bt_bap_req, 1);
+ req->op = op;
+
+ DBG(bap, "req %p placeholder for op 0x%02x", req, op);
+
+ return req;
+}
+
+static void bap_schedule_queue(struct bt_bap *bap)
+{
+ if (bap->req || bap->process_id || queue_isempty(bap->reqs))
+ return;
+
+ bap->process_id = timeout_add(BAP_PROCESS_TIMEOUT, bap_process_queue,
+ bap, NULL);
+}
+
+/* Unlink the requests of a stream from a request and its group into
+ * removed, if the stream is the leader the next member of the group is
+ * promoted.
+ */
+static struct bt_bap_req *bap_req_unlink_stream(struct bt_bap_req *req,
+ struct bt_bap_stream *stream,
+ struct queue *removed)
+{
+ struct bt_bap_req *member;
+
+ while ((member = queue_remove_if(req->group, match_req_stream,
+ stream)))
+ queue_push_tail(removed, member);
+
+ if (req->stream != stream)
+ return req;
+
+ member = queue_pop_head(req->group);
+ if (member) {
+ member->group = req->group;
+ req->group = NULL;
+ }
+
+ queue_push_tail(removed, req);
+
+ return member;
+}
+
+static void bap_req_complete_all(struct queue *reqs,
+ const struct bt_ascs_ase_rsp *rsp)
+{
+ struct bt_bap_req *req;
+
+ while ((req = queue_pop_head(reqs)))
+ bap_req_complete(req, rsp);
+
+ queue_destroy(reqs, NULL);
}
static void bap_abort_stream_req(struct bt_bap *bap,
struct bt_bap_stream *stream)
{
- queue_remove_all(bap->reqs, match_req_stream, stream, bap_req_abort);
+ struct queue *reqs = bap->reqs;
+ struct queue *aborted = queue_new();
+ struct queue *completed = queue_new();
+ struct bt_ascs_ase_rsp rsp = bap->wait_rsp;
+ struct bt_bap_req *req;
- if (bap->req && bap->req->stream == stream) {
- struct bt_bap_req *req = bap->req;
+ /* Unlink all requests of the stream before completing them, so the
+ * callbacks see the remaining requests.
+ */
+ bap->reqs = queue_new();
- bap->req = NULL;
- bap_req_complete(req, NULL);
+ while ((req = queue_pop_head(reqs))) {
+ req = bap_req_unlink_stream(req, stream, aborted);
+ if (req)
+ queue_push_tail(bap->reqs, req);
}
+
+ queue_destroy(reqs, NULL);
+
+ if (bap->req) {
+ uint8_t op = bap->req->op;
+
+ /* If the request is waiting for the ASE state the CP operation
+ * has already succeeded, so complete it with its response.
+ */
+ bap->req = bap_req_unlink_stream(bap->req, stream,
+ bap->wait_id ? completed : aborted);
+ if (!bap->req) {
+ if (bap->wait_id) {
+ timeout_remove(bap->wait_id);
+ bap->wait_id = 0;
+ } else {
+ bap->req = bap_req_placeholder(bap, op);
+ }
+ }
+ }
+
+ bap_req_complete_all(aborted, NULL);
+ bap_req_complete_all(completed, &rsp);
+
+ bap_wait_check(bap);
+ bap_schedule_queue(bap);
}
static void bt_bap_stream_unref(void *data)
@@ -1488,10 +1648,7 @@ static void bap_stream_state_changed(struct bt_bap_stream *stream)
/* Post notification updates */
switch (stream->ep->state) {
case BT_ASCS_ASE_STATE_IDLE:
- if (bap->req && bap->req->stream == stream) {
- bap_req_complete(bap->req, NULL);
- bap->req = NULL;
- }
+ bap_abort_stream_req(bap, stream);
if (stream->ops && stream->ops->detach)
stream->ops->detach(stream);
@@ -1651,6 +1808,9 @@ static void append_group(void *data, void *user_data)
struct iovec *iov = user_data;
size_t i;
+ if (req->stream)
+ req->state = req->stream->ep->state;
+
for (i = 0; i < req->len; i++)
util_iov_push_mem(iov, req->iov[i].iov_len,
req->iov[i].iov_base);
@@ -1684,6 +1844,9 @@ static bool bap_send(struct bt_bap *bap, struct bt_bap_req *req)
iov.iov_base = alloca(iov.iov_len);
iov.iov_len = 0;
+ if (req->stream)
+ req->state = req->stream->ep->state;
+
hdr.op = req->op;
hdr.num = 1 + queue_length(req->group);
@@ -1721,6 +1884,10 @@ static bool bap_process_queue(void *data)
bap->process_id = 0;
}
+ /* Wait for the outstanding request to complete */
+ if (bap->req)
+ return false;
+
while ((req = queue_pop_head(bap->reqs))) {
if (bap_send(bap, req))
break;
@@ -4792,6 +4959,7 @@ static void bap_free(void *data)
struct bt_bap *bap = data;
timeout_remove(bap->process_id);
+ timeout_remove(bap->wait_id);
bt_bap_detach(bap);
@@ -5533,6 +5701,7 @@ static void bap_ep_set_status(struct bt_bap *bap, struct bt_bap_endpoint *ep,
const uint8_t *value, uint16_t length)
{
struct bt_ascs_ase_status *rsp;
+ struct bt_bap_stream *stream;
struct iovec iov = {
.iov_base = (void *) value,
.iov_len = length,
@@ -5573,7 +5742,13 @@ static void bap_ep_set_status(struct bt_bap *bap, struct bt_bap_endpoint *ep,
if (!ep->stream)
return;
- bap_stream_state_changed(ep->stream);
+ stream = ep->stream;
+
+ bap_stream_state_changed(stream);
+
+ /* Complete the request if it was waiting for the ASE state */
+ if (ep->stream == stream)
+ bap_wait_state_changed(bap, stream);
}
static void read_ase_status(bool success, uint8_t att_ecode,
@@ -5673,6 +5848,139 @@ static void bap_endpoint_attach(struct bt_bap *bap, struct bt_bap_endpoint *ep)
bap_endpoint_notify, ep);
}
+struct req_rsp_data {
+ struct bt_bap *bap;
+ const struct bt_ascs_ase_rsp *rsp;
+};
+
+static void req_set_rsp(void *data, void *user_data)
+{
+ struct bt_bap_req *req = data;
+ struct req_rsp_data *rsp = user_data;
+
+ if (!req->stream || !queue_find(rsp->bap->streams, NULL, req->stream))
+ return;
+
+ if (req->stream->ep->id != rsp->rsp->ase)
+ return;
+
+ req->rsp = *rsp->rsp;
+ req->has_rsp = true;
+}
+
+static void req_default_rsp(void *data, void *user_data)
+{
+ struct bt_bap_req *req = data;
+ struct req_rsp_data *rsp = user_data;
+
+ if (req->has_rsp)
+ return;
+
+ req->rsp = *rsp->rsp;
+ req->has_rsp = true;
+}
+
+static bool bap_req_needs_wait(struct bt_bap *bap, struct bt_bap_req *req)
+{
+ struct bt_bap_endpoint *ep;
+
+ if (!req->stream || !queue_find(bap->streams, NULL, req->stream))
+ return false;
+
+ /* Only wait if the operation succeeded for the ASE */
+ if (!req->has_rsp || req->rsp.code != BT_ASCS_RSP_SUCCESS)
+ return false;
+
+ ep = req->stream->ep;
+
+ /* ASE state has already been notified */
+ if (ep->state != req->state)
+ return false;
+
+ switch (req->op) {
+ case BT_ASCS_CONFIG:
+ return req->state != BT_ASCS_ASE_STATE_CONFIG;
+ case BT_ASCS_QOS:
+ return req->state != BT_ASCS_ASE_STATE_QOS;
+ case BT_ASCS_ENABLE:
+ case BT_ASCS_START:
+ case BT_ASCS_DISABLE:
+ case BT_ASCS_STOP:
+ case BT_ASCS_RELEASE:
+ return true;
+ }
+
+ return false;
+}
+
+static void req_set_wait(void *data, void *user_data)
+{
+ struct bt_bap_req *req = data;
+ struct bt_bap *bap = user_data;
+
+ req->wait = bap_req_needs_wait(bap, req);
+}
+
+static bool bap_wait_timeout(void *data)
+{
+ struct bt_bap *bap = data;
+
+ DBG(bap, "req %p timeout waiting for ASE state", bap->req);
+
+ bap->wait_id = 0;
+ bap_wait_complete(bap);
+ bap_process_queue(bap);
+
+ return false;
+}
+
+/* Wait for the ASE state notifications before completing the request, as
+ * the CP notification may arrive first. Further requests are not sent
+ * until then, so each request waits for the states it caused.
+ */
+static bool bap_req_wait(struct bt_bap *bap, struct bt_bap_req *req,
+ const struct bt_ascs_ase_rsp *rsp)
+{
+ struct req_rsp_data data = {
+ .bap = bap,
+ .rsp = rsp,
+ };
+
+ if (!rsp)
+ return false;
+
+ /* ASEs without a response of their own use the last one */
+ req_default_rsp(req, &data);
+ queue_foreach(req->group, req_default_rsp, &data);
+
+ req_set_wait(req, bap);
+ queue_foreach(req->group, req_set_wait, bap);
+
+ if (!bap_req_waiting(req))
+ return false;
+
+ DBG(bap, "req %p wait for ASE state", req);
+
+ bap->wait_rsp = *rsp;
+ bap->wait_id = timeout_add(BAP_STATE_TIMEOUT, bap_wait_timeout, bap,
+ NULL);
+
+ return bap->wait_id != 0;
+}
+
+/* Store the response of each ASE in its request */
+static void bap_req_set_rsp(struct bt_bap *bap, struct bt_bap_req *req,
+ const struct bt_ascs_ase_rsp *ase_rsp)
+{
+ struct req_rsp_data rsp = {
+ .bap = bap,
+ .rsp = ase_rsp,
+ };
+
+ req_set_rsp(req, &rsp);
+ queue_foreach(req->group, req_set_rsp, &rsp);
+}
+
static void bap_cp_notify(struct bt_bap *bap, uint16_t value_handle,
const uint8_t *value, uint16_t length,
void *user_data)
@@ -5682,11 +5990,11 @@ static void bap_cp_notify(struct bt_bap *bap, uint16_t value_handle,
struct bt_bap_req *req;
int i;
- if (!bap->req)
+ /* Ignore if there is no request or it is waiting for ASE state */
+ if (!bap->req || bap->wait_id)
return;
req = bap->req;
- bap->req = NULL;
if (length < sizeof(*rsp)) {
DBG(bap, "Invalid ASE CP notification: length %u < %zu",
@@ -5715,9 +6023,16 @@ static void bap_cp_notify(struct bt_bap *bap, uint16_t value_handle,
}
ase_rsp = &rsp->rsp[i];
+ length -= sizeof(*ase_rsp);
+
+ bap_req_set_rsp(bap, req, ase_rsp);
}
done:
+ if (bap_req_wait(bap, req, ase_rsp))
+ return;
+
+ bap->req = NULL;
bap_req_complete(req, ase_rsp);
bap_process_queue(bap);
}
@@ -6010,6 +6325,11 @@ void bt_bap_detach(struct bt_bap *bap)
return;
/* Cancel ongoing request */
+ if (bap->wait_id) {
+ timeout_remove(bap->wait_id);
+ bap->wait_id = 0;
+ }
+
if (bap->req) {
struct bt_bap_req *req = bap->req;
@@ -7028,26 +7348,76 @@ static bool match_name(const void *data, const void *match_data)
return (!strcmp(pac->name, name));
}
+/* Remove the request matching id from a request and its group, if it is the
+ * leader the next member of the group is promoted.
+ */
+static struct bt_bap_req *bap_req_cancel(struct bt_bap_req *req,
+ unsigned int id)
+{
+ struct bt_bap_req *member;
+
+ member = queue_remove_if(req->group, match_req_id, UINT_TO_PTR(id));
+ if (member) {
+ bap_req_free(member);
+ return req;
+ }
+
+ if (req->id != id)
+ return req;
+
+ member = queue_pop_head(req->group);
+ if (member) {
+ member->group = req->group;
+ req->group = NULL;
+ }
+
+ bap_req_free(req);
+
+ return member;
+}
+
int bt_bap_stream_cancel(struct bt_bap_stream *stream, unsigned int id)
{
- struct bt_bap_req *req;
+ struct bt_bap *bap;
+ const struct queue_entry *entry;
if (!stream)
return -EINVAL;
- if (stream->bap->req && stream->bap->req->id == id) {
- req = stream->bap->req;
- stream->bap->req = NULL;
- bap_req_free(req);
- return 0;
+ bap = stream->bap;
+
+ /* The outstanding request has already been sent, so only drop its
+ * callback and keep waiting for its CP notification and ASE state
+ * before sending the next request.
+ */
+ if (bap->req) {
+ struct bt_bap_req *req = bap->req;
+
+ if (req->id != id)
+ req = queue_find(req->group, match_req_id,
+ UINT_TO_PTR(id));
+ if (req) {
+ req->func = NULL;
+ req->user_data = NULL;
+ return 0;
+ }
}
- req = queue_remove_if(stream->bap->reqs, match_req_id,
- UINT_TO_PTR(id));
- if (!req)
- return 0;
+ for (entry = queue_get_entries(bap->reqs); entry;
+ entry = entry->next) {
+ struct bt_bap_req *req = entry->data;
+ struct bt_bap_req *leader = bap_req_cancel(req, id);
- bap_req_free(req);
+ if (leader == req)
+ continue;
+
+ if (leader)
+ queue_push_after(bap->reqs, req, leader);
+
+ queue_remove(bap->reqs, req);
+
+ break;
+ }
return 0;
}
--
2.56.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH BlueZ v1 3/7] unit/test-bap: Add tests for cancelling grouped Enable
2026-11-03 20:30 [PATCH BlueZ v1 0/7] BAP ASE state handling enhancements Luiz Augusto von Dentz
2026-11-03 20:30 ` [PATCH BlueZ v1 1/7] shared/bap: Defer ASE state notifications until idle Luiz Augusto von Dentz
2026-11-03 20:30 ` [PATCH BlueZ v1 2/7] shared/bap: Wait for ASE state before completing requests Luiz Augusto von Dentz
@ 2026-11-03 20:30 ` Luiz Augusto von Dentz
2026-11-03 20:30 ` [PATCH BlueZ v1 4/7] unit/test-bap: Add Notify Multiple test Luiz Augusto von Dentz
` (3 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Luiz Augusto von Dentz @ 2026-11-03 20:30 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This adds tests that cancel the leader or a member of a grouped Enable
request, either while it is still queued or while it is waiting for the
ASE state, and check that only the remaining requests are completed.
---
unit/test-bap.c | 165 ++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 161 insertions(+), 4 deletions(-)
diff --git a/unit/test-bap.c b/unit/test-bap.c
index d16fbd96c555..8f081d92b936 100644
--- a/unit/test-bap.c
+++ b/unit/test-bap.c
@@ -45,6 +45,17 @@
do { tester_warn("%s:%d: failed in %s", __FILE__, __LINE__, __func__); \
tester_test_failed(); } while (0)
+/* Cancel one of the grouped Enable requests, either while it is queued or
+ * while the request is waiting for the ASE state.
+ */
+enum {
+ CANCEL_NONE,
+ CANCEL_LEADER,
+ CANCEL_MEMBER,
+ CANCEL_WAIT_LEADER,
+ CANCEL_WAIT_MEMBER,
+};
+
struct test_config {
struct iovec cc;
struct iovec base;
@@ -61,6 +72,7 @@ struct test_config {
struct iovec *pac_caps;
const struct iovec *setup_data;
size_t setup_data_len;
+ uint8_t cancel;
};
struct test_data {
@@ -82,6 +94,9 @@ struct test_data {
size_t iovcnt;
struct iovec *iov;
int fds[8][2];
+ unsigned int enable_ids[2];
+ struct bt_bap_stream *cancelled;
+ unsigned int enabled;
};
struct notify {
@@ -9461,6 +9476,31 @@ static void test_bsrc_str(void)
LC3_PAC_CAPS(0x01))
#define STR_AC3 DISC_AC3, STR_SNK_SRC_STREAMING_LC3(0, 0, 0x1, 0x1)
+
+#define STR_SNK_SRC_QOS(cis1, cis2, challoc1, challoc2, codec_id...) \
+ SCC_ASE2(SNK_ID(0), SRC_ID(0), challoc1, challoc2, codec_id), \
+ SCC_SNK_NOTIFY(0, STR_SCC_DATA(challoc1, codec_id)), \
+ SCC_SRC_NOTIFY(0, STR_SCC_DATA(challoc2, codec_id)), \
+ QOS_ASE2(SNK_ID(0), SRC_ID(0), cis1, cis2, QOS_SRC_8_1_1_DATA), \
+ QOS_SNK_NOTIFY(0, cis1, QOS_SRC_8_1_1_DATA), \
+ QOS_SRC_NOTIFY(0, cis2, QOS_SRC_8_1_1_DATA)
+
+/* Enable of the Sink ASE cancelled, only the Source ASE is enabled */
+#define STR_AC3_CANCEL_LEADER \
+ DISC_AC3, \
+ STR_SNK_SRC_QOS(0, 0, 0x1, 0x1, LC3_CODEC_ID_DATA), \
+ ENABLE_ASE(SRC_ID(0)), \
+ SRC_ENABLE_NOTIFY(0, 0), \
+ START_ASE(SRC_ID(0)), \
+ SRC_START_NOTIFY(0, 0)
+
+/* Enable of the Source ASE cancelled, only the Sink ASE is enabled */
+#define STR_AC3_CANCEL_MEMBER \
+ DISC_AC3, \
+ STR_SNK_SRC_QOS(0, 0, 0x1, 0x1, LC3_CODEC_ID_DATA), \
+ ENABLE_ASE(SNK_ID(0)), \
+ SNK_ENABLE_NOTIFY(0, 0), \
+ SNK_START_NOTIFY(0, 0)
#define STR_AC5 DISC_AC5, STR_SNK_SRC_STREAMING_LC3(0, 0, 0x22, 0x2)
#define STR_AC7i DISC_AC7i, STR_SNK_SRC_STREAMING_LC3(0, 1, 0x4, 0x4)
@@ -9649,6 +9689,42 @@ static struct test_config cfg_str_ac3 = {
.qos = LC3_QOS_8_1_1,
};
+static struct test_config cfg_str_ac3_cancel_leader = {
+ .snk = true,
+ .src = true,
+ .snk_locations = { 0x1, -1 },
+ .src_locations = { 0x1, -1 },
+ .qos = LC3_QOS_8_1_1,
+ .cancel = CANCEL_LEADER,
+};
+
+static struct test_config cfg_str_ac3_cancel_member = {
+ .snk = true,
+ .src = true,
+ .snk_locations = { 0x1, -1 },
+ .src_locations = { 0x1, -1 },
+ .qos = LC3_QOS_8_1_1,
+ .cancel = CANCEL_MEMBER,
+};
+
+static struct test_config cfg_str_ac3_cancel_wait_leader = {
+ .snk = true,
+ .src = true,
+ .snk_locations = { 0x1, -1 },
+ .src_locations = { 0x1, -1 },
+ .qos = LC3_QOS_8_1_1,
+ .cancel = CANCEL_WAIT_LEADER,
+};
+
+static struct test_config cfg_str_ac3_cancel_wait_member = {
+ .snk = true,
+ .src = true,
+ .snk_locations = { 0x1, -1 },
+ .src_locations = { 0x1, -1 },
+ .qos = LC3_QOS_8_1_1,
+ .cancel = CANCEL_WAIT_MEMBER,
+};
+
static struct test_config cfg_str_ac5 = {
.snk = true,
.src = true,
@@ -9931,8 +10007,17 @@ static void streaming_ucl_do_stream(struct bt_bap_stream *stream,
}
/* All streams handled */
- if (data->id == 0)
- tester_test_passed();
+ if (data->id)
+ return;
+
+ /* Check the requests not cancelled have been completed */
+ if (data->cfg->cancel && data->enabled + !!data->cancelled !=
+ queue_length(data->streams)) {
+ FAIL_TEST();
+ return;
+ }
+
+ tester_test_passed();
}
static void streaming_ucl_connect(struct bt_bap_stream *stream)
@@ -10005,6 +10090,37 @@ static int streaming_ucl_create_io(struct bt_bap_stream *stream,
return i;
}
+static void streaming_ucl_enable(struct bt_bap_stream *stream, uint8_t code,
+ uint8_t reason, void *user_data)
+{
+ struct test_data *data = user_data;
+
+ tester_debug("stream %p enable code 0x%02x", stream, code);
+
+ /* Cancelled requests shall not be completed */
+ if (code || stream == data->cancelled) {
+ FAIL_TEST();
+ return;
+ }
+
+ data->enabled++;
+}
+
+static void streaming_ucl_cancel(struct test_data *data, unsigned int idx)
+{
+ const struct queue_entry *entry = queue_get_entries(data->streams);
+
+ if (idx)
+ entry = entry->next;
+
+ data->cancelled = entry->data;
+
+ tester_debug("cancel stream %p enable", data->cancelled);
+
+ g_assert(!bt_bap_stream_cancel(data->cancelled,
+ data->enable_ids[idx]));
+}
+
static void streaming_ucl_state(struct bt_bap_stream *stream,
uint8_t old_state, uint8_t new_state,
void *user_data)
@@ -10033,18 +10149,47 @@ static void streaming_ucl_state(struct bt_bap_stream *stream,
for (entry = queue_get_entries(data->streams);
entry; entry = entry->next) {
struct bt_bap_stream *s = entry->data;
+ unsigned int idx = data->id;
- if (data->cfg->state != BT_BAP_STREAM_STATE_ENABLING)
+ /* Don't connect the stream to be cancelled */
+ if (data->cfg->state != BT_BAP_STREAM_STATE_ENABLING &&
+ !(data->cfg->cancel == CANCEL_LEADER && !idx) &&
+ !(data->cfg->cancel == CANCEL_MEMBER && idx))
streaming_ucl_connect(s);
id = bt_bap_stream_enable(s, false, NULL,
- NULL, NULL);
+ data->cfg->cancel ?
+ streaming_ucl_enable : NULL,
+ data);
g_assert(id);
+ if (idx < ARRAY_SIZE(data->enable_ids))
+ data->enable_ids[idx] = id;
+
data->id++;
}
+
+ switch (data->cfg->cancel) {
+ case CANCEL_LEADER:
+ streaming_ucl_cancel(data, 0);
+ data->id--;
+ break;
+ case CANCEL_MEMBER:
+ streaming_ucl_cancel(data, 1);
+ data->id--;
+ break;
+ }
break;
case BT_BAP_STREAM_STATE_ENABLING:
+ /* Cancel once the first ASE is enabling, the request is then
+ * waiting for the other one.
+ */
+ if (!data->cancelled && data->cfg->cancel == CANCEL_WAIT_LEADER)
+ streaming_ucl_cancel(data, 0);
+ else if (!data->cancelled &&
+ data->cfg->cancel == CANCEL_WAIT_MEMBER)
+ streaming_ucl_cancel(data, 1);
+
/* TODO: not correct to call bt_bap_stream_set_io() from this
* callback
*/
@@ -10324,6 +10469,18 @@ static void test_ucl_str(void)
define_test("BAP/UCL/STR/BV-549-C [UCL, AC 3, Generic, QoS, QoS]",
test_setup, test_select, &cfg_str_ac3, STR_AC3);
+ define_test("BAP/UCL/STR/BLUEZ-3 [UCL, AC 3, Cancel Enable leader]",
+ test_setup, test_select, &cfg_str_ac3_cancel_leader,
+ STR_AC3_CANCEL_LEADER);
+ define_test("BAP/UCL/STR/BLUEZ-4 [UCL, AC 3, Cancel Enable member]",
+ test_setup, test_select, &cfg_str_ac3_cancel_member,
+ STR_AC3_CANCEL_MEMBER);
+ define_test("BAP/UCL/STR/BLUEZ-5 [UCL, AC 3, Cancel Enable leader "
+ "waiting]", test_setup, test_select,
+ &cfg_str_ac3_cancel_wait_leader, STR_AC3);
+ define_test("BAP/UCL/STR/BLUEZ-6 [UCL, AC 3, Cancel Enable member "
+ "waiting]", test_setup, test_select,
+ &cfg_str_ac3_cancel_wait_member, STR_AC3);
define_test("BAP/UCL/STR/BV-550-C [UCL, AC 5, Generic, QoS, QoS]",
test_setup, test_select, &cfg_str_ac5, STR_AC5);
define_test("BAP/UCL/STR/BV-551-C [UCL, AC 7(i), Generic, QoS, QoS]",
--
2.56.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH BlueZ v1 4/7] unit/test-bap: Add Notify Multiple test
2026-11-03 20:30 [PATCH BlueZ v1 0/7] BAP ASE state handling enhancements Luiz Augusto von Dentz
` (2 preceding siblings ...)
2026-11-03 20:30 ` [PATCH BlueZ v1 3/7] unit/test-bap: Add tests for cancelling grouped Enable Luiz Augusto von Dentz
@ 2026-11-03 20:30 ` Luiz Augusto von Dentz
2026-11-03 20:30 ` [PATCH BlueZ v1 5/7] shared/tester: Add support for alternative PDUs Luiz Augusto von Dentz
` (2 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Luiz Augusto von Dentz @ 2026-11-03 20:30 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This adds a server test with Notify Multiple enabled which checks that
the ASE Control Point notification and the ASE state notification are
sent in the same Notify Multiple PDU.
---
unit/test-bap.c | 32 +++++++++++++++++++++++++++++++-
1 file changed, 31 insertions(+), 1 deletion(-)
diff --git a/unit/test-bap.c b/unit/test-bap.c
index 8f081d92b936..1b1a51b03e51 100644
--- a/unit/test-bap.c
+++ b/unit/test-bap.c
@@ -73,6 +73,7 @@ struct test_config {
const struct iovec *setup_data;
size_t setup_data_len;
uint8_t cancel;
+ bool mnotify;
};
struct test_data {
@@ -668,7 +669,8 @@ static void gatt_notify_cb(struct gatt_db_attribute *attrib,
}
if (!bt_gatt_server_send_notification(data->server,
- handle, value, len, false))
+ handle, value, len,
+ data->cfg ? data->cfg->mnotify : false))
tester_debug("%s: Failed to send notification", __func__);
}
@@ -2067,6 +2069,30 @@ static void test_ucl_scc_cc_lc3(void)
/* 4.9 Unicast Server Configuration */
+static struct test_config cfg_snk_8_1_mnotify = {
+ .cc = LC3_CONFIG_8_1,
+ .qos = QOS_UCAST,
+ .snk = true,
+ .mnotify = true,
+};
+
+/* The CP notification and the ASE state notification are expected to be
+ * sent in the same Notify Multiple PDU.
+ */
+#define SCC_SNK_8_1_MNOTIFY \
+ DISC_SRC_ASE_LC3, \
+ IOV_DATA(SCC_PDU(1), SCC_PDU_ASE(SNK_ID(0), LC3_CODEC_ID_DATA, \
+ 0x0a, 0x02, 0x01, 0x01, 0x02, 0x02, 0x00, 0x03, 0x04, \
+ 0x1a, 0x00)), \
+ IOV_DATA(0x23, CP_HND, 0x05, 0x00, 0x01, 0x01, SNK_ID(0), 0x00, \
+ 0x00, \
+ SNK_HND(0), 0x23, 0x00, SNK_ID(0), 0x01, \
+ 0x00, 0x02, 0x01, 0x0a, 0x00, \
+ 0x20, 0x4e, 0x00, 0x40, 0x9c, 0x00, 0x20, 0x4e, 0x00, \
+ 0x40, 0x9c, 0x00, LC3_CODEC_ID_DATA, \
+ 0x0a, 0x02, 0x01, 0x01, 0x02, 0x02, 0x00, 0x03, 0x04, \
+ 0x1a, 0x00)
+
static void test_usr_scc_cc_lc3(void)
{
/* 4.9.1 Unicast Server as Audio Sink Performs Config Codec – LC3
@@ -2100,6 +2126,10 @@ static void test_usr_scc_cc_lc3(void)
define_test("BAP/USR/SCC/BV-001-C [USR SNK Config Codec, LC3 8_1]",
test_setup_server, test_server, &cfg_snk_8_1,
SCC_SNK_8_1);
+ define_test("BAP/USR/SCC/BLUEZ-1 [USR SNK Config Codec, LC3 8_1, "
+ "Notify Multiple]",
+ test_setup_server, test_server, &cfg_snk_8_1_mnotify,
+ SCC_SNK_8_1_MNOTIFY);
define_test("BAP/USR/SCC/BV-002-C [USR SNK Config Codec, LC3 8_2]",
test_setup_server, test_server, &cfg_snk_8_2,
SCC_SNK_8_2);
--
2.56.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH BlueZ v1 5/7] shared/tester: Add support for alternative PDUs
2026-11-03 20:30 [PATCH BlueZ v1 0/7] BAP ASE state handling enhancements Luiz Augusto von Dentz
` (3 preceding siblings ...)
2026-11-03 20:30 ` [PATCH BlueZ v1 4/7] unit/test-bap: Add Notify Multiple test Luiz Augusto von Dentz
@ 2026-11-03 20:30 ` Luiz Augusto von Dentz
2026-11-03 20:30 ` [PATCH BlueZ v1 6/7] unit/test-bap: Expect the ASE state following Releasing Luiz Augusto von Dentz
2026-11-03 20:30 ` [PATCH BlueZ v1 7/7] unit/test-bap: Run unicast tests with Notify Multiple Luiz Augusto von Dentz
6 siblings, 0 replies; 9+ messages in thread
From: Luiz Augusto von Dentz @ 2026-11-03 20:30 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This adds IOV_ALT, IOV_ALT_OR and IOV_ALT_END markers which can be used
to express PDUs that can be exchanged in alternative ways, e.g. when
an optional feature is used, along with tester_io_set_alt which selects
the alternative to be used by the test.
---
src/shared/tester.c | 74 +++++++++++++++++++++++++++++++++++++++++++++
src/shared/tester.h | 13 ++++++++
2 files changed, 87 insertions(+)
diff --git a/src/shared/tester.c b/src/shared/tester.c
index 72b280a1f3d5..5207b6aa7cf8 100644
--- a/src/shared/tester.c
+++ b/src/shared/tester.c
@@ -85,6 +85,7 @@ struct test_case {
const void *test_data;
const struct iovec *iov;
size_t iovcnt;
+ struct iovec *alt_iov;
tester_data_func_t pre_setup_func;
tester_data_func_t setup_func;
tester_data_func_t test_func;
@@ -145,6 +146,8 @@ static void test_destroy(void *data)
{
struct test_case *test = data;
+ free(test->alt_iov);
+
if (test->timeout_id > 0)
timeout_remove(test->timeout_id);
@@ -528,6 +531,8 @@ static void next_test_case(void)
{
struct test_case *test;
+ tester_io_set_alt(0);
+
if (test_current)
test_current = test_current->next;
else
@@ -1170,6 +1175,73 @@ static void setup_io(void)
io_set_read_handler(ios[1], test_io_recv, NULL, NULL);
}
+const uint8_t tester_iov_markers[3];
+
+static unsigned int io_alt;
+
+static bool iov_is_marker(const struct iovec *iov, unsigned int idx)
+{
+ return iov->iov_base == &tester_iov_markers[idx];
+}
+
+/* Only keep the selected alternative of the PDUs in between IOV_ALT and
+ * IOV_ALT_END.
+ */
+static int test_setup_alt(struct test_case *test, const struct iovec **iov,
+ int iovcnt)
+{
+ unsigned int alt = 0;
+ bool in_alt = false;
+ int i, cnt = 0;
+
+ for (i = 0; i < iovcnt; i++) {
+ if (iov_is_marker(&(*iov)[i], 0))
+ break;
+ }
+
+ if (i == iovcnt)
+ return iovcnt;
+
+ free(test->alt_iov);
+ test->alt_iov = calloc(iovcnt, sizeof(*test->alt_iov));
+ if (!test->alt_iov)
+ abort();
+
+ for (i = 0; i < iovcnt; i++) {
+ const struct iovec *v = &(*iov)[i];
+
+ if (iov_is_marker(v, 0)) {
+ in_alt = true;
+ alt = 0;
+ continue;
+ }
+
+ if (iov_is_marker(v, 1)) {
+ alt++;
+ continue;
+ }
+
+ if (iov_is_marker(v, 2)) {
+ in_alt = false;
+ continue;
+ }
+
+ if (in_alt && alt != io_alt)
+ continue;
+
+ test->alt_iov[cnt++] = *v;
+ }
+
+ *iov = test->alt_iov;
+
+ return cnt;
+}
+
+void tester_io_set_alt(unsigned int alt)
+{
+ io_alt = alt;
+}
+
struct io *tester_setup_io(const struct iovec *iov, int iovcnt)
{
struct test_case *test = tester_get_test();
@@ -1182,6 +1254,8 @@ struct io *tester_setup_io(const struct iovec *iov, int iovcnt)
}
}
+ iovcnt = test_setup_alt(test, &iov, iovcnt);
+
test->iov = iov;
test->iovcnt = iovcnt;
diff --git a/src/shared/tester.h b/src/shared/tester.h
index dfc1ca3a8ebe..b22e8b38ea2a 100644
--- a/src/shared/tester.h
+++ b/src/shared/tester.h
@@ -23,6 +23,18 @@
#define IOV_NULL {}
+/* Markers for PDUs that can be exchanged in alternative ways:
+ *
+ * IOV_ALT, <alternative 0>, IOV_ALT_OR, <alternative 1>, ..., IOV_ALT_END
+ *
+ * Only the alternative selected with tester_io_set_alt() is used.
+ */
+extern const uint8_t tester_iov_markers[3];
+
+#define IOV_ALT { .iov_base = (void *) &tester_iov_markers[0] }
+#define IOV_ALT_OR { .iov_base = (void *) &tester_iov_markers[1] }
+#define IOV_ALT_END { .iov_base = (void *) &tester_iov_markers[2] }
+
void tester_init(int *argc, char ***argv);
int tester_run(void);
@@ -84,3 +96,4 @@ struct io *tester_setup_io(const struct iovec *iov, int iovcnt);
void tester_shutdown_io(void);
void tester_io_send(void);
void tester_io_set_complete_func(tester_data_func_t func);
+void tester_io_set_alt(unsigned int alt);
--
2.56.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH BlueZ v1 6/7] unit/test-bap: Expect the ASE state following Releasing
2026-11-03 20:30 [PATCH BlueZ v1 0/7] BAP ASE state handling enhancements Luiz Augusto von Dentz
` (4 preceding siblings ...)
2026-11-03 20:30 ` [PATCH BlueZ v1 5/7] shared/tester: Add support for alternative PDUs Luiz Augusto von Dentz
@ 2026-11-03 20:30 ` Luiz Augusto von Dentz
2026-11-03 20:30 ` [PATCH BlueZ v1 7/7] unit/test-bap: Run unicast tests with Notify Multiple Luiz Augusto von Dentz
6 siblings, 0 replies; 9+ messages in thread
From: Luiz Augusto von Dentz @ 2026-11-03 20:30 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
After Releasing the server moves the ASE to Idle, or to Codec Configured
if the configuration is cached, but the Release tests ended at
Releasing so the notification of the following state was ignored.
---
unit/test-bap.c | 41 +++++++++++++++++++++++++++++++++--------
1 file changed, 33 insertions(+), 8 deletions(-)
diff --git a/unit/test-bap.c b/unit/test-bap.c
index 1b1a51b03e51..8b091a5f640d 100644
--- a/unit/test-bap.c
+++ b/unit/test-bap.c
@@ -4007,9 +4007,25 @@ static struct test_config cfg_src_cc_release = {
IOV_NULL, \
IOV_DATA(0x1b, 0x1c, 0x00, 0x03, 0x06)
+/* After Releasing the ASE moves to Idle, or to Codec Configured if the
+ * configuration is cached.
+ */
+#define ASE_SRC_RELEASE_IDLE \
+ ASE_SRC_RELEASE, \
+ IOV_NULL, \
+ IOV_DATA(0x1b, SRC_HND(0), SRC_ID(0), 0x00)
+
+#define LC3_CONFIG_16_2_DATA \
+ LC3_CODEC_ID_DATA, 0x0a, 0x02, 0x01, 0x03, 0x02, 0x02, 0x01, 0x03, \
+ 0x04, 0x28, 0x00
+
+#define ASE_SRC_RELEASE_CC \
+ ASE_SRC_RELEASE, \
+ SCC_SRC_NOTIFY(0, LC3_CONFIG_16_2_DATA)
+
#define SCC_SRC_CC_RELEASE \
SCC_SRC_16_2, \
- ASE_SRC_RELEASE
+ ASE_SRC_RELEASE_IDLE
static struct test_config cfg_snk_cc_release = {
.cc = LC3_CONFIG_16_2,
@@ -4034,9 +4050,18 @@ static struct test_config cfg_snk_cc_release = {
IOV_NULL, \
IOV_DATA(0x1b, 0x16, 0x00, 0x01, 0x06)
+#define ASE_SNK_RELEASE_IDLE \
+ ASE_SNK_RELEASE, \
+ IOV_NULL, \
+ IOV_DATA(0x1b, SNK_HND(0), SNK_ID(0), 0x00)
+
+#define ASE_SNK_RELEASE_CC \
+ ASE_SNK_RELEASE, \
+ SCC_SNK_NOTIFY(0, LC3_CONFIG_16_2_DATA)
+
#define SCC_SNK_CC_RELEASE \
SCC_SNK_16_2, \
- ASE_SNK_RELEASE
+ ASE_SNK_RELEASE_IDLE
static void state_qos_release(struct bt_bap_stream *stream,
uint8_t old_state, uint8_t new_state,
@@ -4063,7 +4088,7 @@ static struct test_config cfg_src_qos_release = {
#define SCC_SRC_QOS_RELEASE \
SCC_SRC_16_2_1, \
- ASE_SRC_RELEASE
+ ASE_SRC_RELEASE_CC
static struct test_config cfg_snk_qos_release = {
.cc = LC3_CONFIG_16_2,
@@ -4075,7 +4100,7 @@ static struct test_config cfg_snk_qos_release = {
#define SCC_SNK_QOS_RELEASE \
SCC_SNK_16_2_1, \
- ASE_SNK_RELEASE
+ ASE_SNK_RELEASE_CC
static void state_enable_release(struct bt_bap_stream *stream,
uint8_t old_state, uint8_t new_state,
@@ -4102,7 +4127,7 @@ static struct test_config cfg_src_enable_release = {
#define SCC_SRC_ENABLE_RELEASE \
SCC_SRC_ENABLE, \
- ASE_SRC_RELEASE
+ ASE_SRC_RELEASE_CC
static struct test_config cfg_snk_enable_release = {
.cc = LC3_CONFIG_16_2,
@@ -4114,7 +4139,7 @@ static struct test_config cfg_snk_enable_release = {
#define SCC_SNK_ENABLE_RELEASE \
SCC_SNK_ENABLE, \
- ASE_SNK_RELEASE
+ ASE_SNK_RELEASE_CC
static void state_start_release(struct bt_bap_stream *stream,
uint8_t old_state, uint8_t new_state,
@@ -4142,7 +4167,7 @@ static struct test_config cfg_src_start_release = {
#define SCC_SRC_START_RELEASE \
SCC_SRC_ENABLE, \
SRC_START, \
- ASE_SRC_RELEASE
+ ASE_SRC_RELEASE_CC
static void state_disable_release(struct bt_bap_stream *stream,
uint8_t old_state, uint8_t new_state,
@@ -4169,7 +4194,7 @@ static struct test_config cfg_src_disable_release = {
#define SCC_SRC_DISABLE_RELEASE \
SCC_SRC_DISABLE, \
- ASE_SRC_RELEASE
+ ASE_SRC_RELEASE_CC
/* Test Purpose:
* Verify that a Unicast Client IUT can release an ASE by initiating a Release
--
2.56.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH BlueZ v1 7/7] unit/test-bap: Run unicast tests with Notify Multiple
2026-11-03 20:30 [PATCH BlueZ v1 0/7] BAP ASE state handling enhancements Luiz Augusto von Dentz
` (5 preceding siblings ...)
2026-11-03 20:30 ` [PATCH BlueZ v1 6/7] unit/test-bap: Expect the ASE state following Releasing Luiz Augusto von Dentz
@ 2026-11-03 20:30 ` Luiz Augusto von Dentz
6 siblings, 0 replies; 9+ messages in thread
From: Luiz Augusto von Dentz @ 2026-11-03 20:30 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This runs each unicast test a second time with Notify Multiple, both as
server and as client, using the same test data: each chain of
notifications is given an alternative with them sent as Handle Value
Multiple Notification, which is the one selected for these tests.
---
unit/test-bap.c | 185 +++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 168 insertions(+), 17 deletions(-)
diff --git a/unit/test-bap.c b/unit/test-bap.c
index 8b091a5f640d..99c5ae92e447 100644
--- a/unit/test-bap.c
+++ b/unit/test-bap.c
@@ -95,6 +95,10 @@ struct test_data {
size_t iovcnt;
struct iovec *iov;
int fds[8][2];
+ bool mnotify;
+ struct iovec *alt_iov;
+ size_t alt_iovcnt;
+ struct queue *alt_pdus;
unsigned int enable_ids[2];
struct bt_bap_stream *cancelled;
unsigned int enabled;
@@ -132,24 +136,169 @@ static struct bt_bap_pac_qos lc3_qos = {
#define iov_data(args...) ((const struct iovec[]) { args })
+/* Unicast tests are also run with Notify Multiple, using the same test data
+ * with the notifications sent as Handle Value Multiple Notification as
+ * alternative.
+ */
#define define_test(name, setup, function, _cfg, args...) \
do { \
const struct iovec iov[] = { args }; \
static struct test_data data; \
- data.caps = &lc3_caps; \
- data.qos = &lc3_qos; \
- data.cfg = _cfg; \
- if (data.cfg && data.cfg->pac_caps) \
- data.caps = data.cfg->pac_caps; \
- if (data.cfg && data.cfg->pac_qos) \
- data.qos = data.cfg->pac_qos; \
- data.iovcnt = ARRAY_SIZE(iov); \
- data.iov = util_iov_dup(iov, ARRAY_SIZE(iov)); \
- data.streams = queue_new(); \
- tester_add(name, &data, setup, function, \
- test_teardown); \
+ static struct test_data data_mnotify; \
+ test_add(&data, name, setup, function, _cfg, \
+ iov, ARRAY_SIZE(iov), false); \
+ if (setup == test_setup_server || \
+ setup == test_setup) \
+ test_add(&data_mnotify, name, setup, \
+ function, _cfg, iov, \
+ ARRAY_SIZE(iov), true); \
} while (0)
+static void test_setup_server(const void *user_data);
+static void test_setup(const void *user_data);
+static void test_teardown(const void *user_data);
+
+#define ATT_MTU 64
+#define ATT_OP_NFY 0x1b
+#define ATT_OP_NFY_MULT 0x23
+
+static const struct iovec iov_null = IOV_NULL;
+static const struct iovec iov_alt = IOV_ALT;
+static const struct iovec iov_alt_or = IOV_ALT_OR;
+static const struct iovec iov_alt_end = IOV_ALT_END;
+
+static bool iov_is_nfy(const struct iovec *iov)
+{
+ return iov->iov_base && iov->iov_len >= 3 &&
+ ((uint8_t *)iov->iov_base)[0] == ATT_OP_NFY;
+}
+
+static void mnotify_pdu_free(void *data)
+{
+ struct iovec *pdu = data;
+
+ free(pdu->iov_base);
+ free(pdu);
+}
+
+static struct iovec *mnotify_pdu_new(struct test_data *data)
+{
+ struct iovec *pdu = new0(struct iovec, 1);
+
+ pdu->iov_base = malloc(ATT_MTU);
+ util_iov_push_u8(pdu, ATT_OP_NFY_MULT);
+ queue_push_tail(data->alt_pdus, pdu);
+
+ return pdu;
+}
+
+static void mnotify_push(struct test_data *data, const struct iovec *iov)
+{
+ data->alt_iov[data->alt_iovcnt++] = *iov;
+}
+
+/* Add each chain of notifications, i.e. notifications separated by
+ * IOV_NULL, with the alternative of sending them as Handle Value Multiple
+ * Notifications:
+ *
+ * IOV_ALT, <notifications>, IOV_ALT_OR, <multiple notifications>,
+ * IOV_ALT_END
+ */
+static void test_setup_mnotify(struct test_data *data)
+{
+ size_t i, j, k;
+
+ data->alt_pdus = queue_new();
+ data->alt_iov = new0(struct iovec, data->iovcnt * 5);
+ data->alt_iovcnt = 0;
+
+ for (i = 0; i < data->iovcnt; i = j + 1) {
+ struct iovec *pdu;
+
+ if (!iov_is_nfy(&data->iov[i])) {
+ j = i;
+ mnotify_push(data, &data->iov[i]);
+ continue;
+ }
+
+ /* Find the end of the chain */
+ for (j = i; j + 2 < data->iovcnt; j += 2) {
+ if (data->iov[j + 1].iov_base ||
+ !iov_is_nfy(&data->iov[j + 2]))
+ break;
+ }
+
+ mnotify_push(data, &iov_alt);
+
+ for (k = i; k <= j; k++)
+ mnotify_push(data, &data->iov[k]);
+
+ mnotify_push(data, &iov_alt_or);
+
+ pdu = mnotify_pdu_new(data);
+
+ for (k = i; k <= j; k += 2) {
+ const struct iovec *nfy = &data->iov[k];
+ size_t len = nfy->iov_len - 3;
+
+ /* Start a new PDU if it doesn't fit in the MTU */
+ if (pdu->iov_len > 1 &&
+ pdu->iov_len + 4 + len > ATT_MTU) {
+ mnotify_push(data, pdu);
+ mnotify_push(data, &iov_null);
+ pdu = mnotify_pdu_new(data);
+ }
+
+ /* Handle */
+ util_iov_push_mem(pdu, 2, nfy->iov_base + 1);
+ util_iov_push_le16(pdu, len);
+ util_iov_push_mem(pdu, len, nfy->iov_base + 3);
+ }
+
+ mnotify_push(data, pdu);
+ mnotify_push(data, &iov_alt_end);
+ }
+}
+
+static void test_add(struct test_data *data, const char *name,
+ tester_data_func_t setup, tester_data_func_t function,
+ struct test_config *cfg, const struct iovec *iov,
+ size_t iovcnt, bool mnotify)
+{
+ char mname[256];
+
+ data->caps = &lc3_caps;
+ data->qos = &lc3_qos;
+ data->cfg = cfg;
+ if (cfg && cfg->pac_caps)
+ data->caps = cfg->pac_caps;
+ if (cfg && cfg->pac_qos)
+ data->qos = cfg->pac_qos;
+ data->iovcnt = iovcnt;
+ data->iov = util_iov_dup(iov, iovcnt);
+ data->streams = queue_new();
+ data->mnotify = mnotify;
+
+ if (mnotify) {
+ test_setup_mnotify(data);
+ snprintf(mname, sizeof(mname), "%s (Notify Multiple)", name);
+ name = mname;
+ }
+
+ tester_add(name, data, setup, function, test_teardown);
+}
+
+static struct io *test_setup_iov(struct test_data *data)
+{
+ if (!data->mnotify)
+ return tester_setup_io(data->iov, data->iovcnt);
+
+ /* Use the Handle Value Multiple Notification alternative */
+ tester_io_set_alt(1);
+
+ return tester_setup_io(data->alt_iov, data->alt_iovcnt);
+}
+
static void client_ready_cb(bool success, uint8_t att_ecode, void *user_data)
{
if (!success)
@@ -670,7 +819,7 @@ static void gatt_notify_cb(struct gatt_db_attribute *attrib,
if (!bt_gatt_server_send_notification(data->server,
handle, value, len,
- data->cfg ? data->cfg->mnotify : false))
+ data->mnotify || (data->cfg && data->cfg->mnotify)))
tester_debug("%s: Failed to send notification", __func__);
}
@@ -942,7 +1091,7 @@ static void test_client(const void *user_data)
struct test_data *data = (void *)user_data;
struct io *io;
- io = tester_setup_io(data->iov, data->iovcnt);
+ io = test_setup_iov(data);
g_assert(io);
tester_io_set_complete_func(test_complete_cb);
@@ -1141,6 +1290,8 @@ static void test_teardown(const void *user_data)
bt_bap_unref(data->bap);
bt_gatt_client_unref(data->client);
util_iov_free(data->iov, data->iovcnt);
+ free(data->alt_iov);
+ queue_destroy(data->alt_pdus, mnotify_pdu_free);
util_iov_free(data->base, 1);
@@ -1499,7 +1650,7 @@ static void test_server(const void *user_data)
struct test_data *data = (void *)user_data;
struct io *io;
- io = tester_setup_io(data->iov, data->iovcnt);
+ io = test_setup_iov(data);
g_assert(io);
tester_io_set_complete_func(test_complete_cb);
@@ -1530,7 +1681,7 @@ static void test_server_state(const void *user_data)
struct test_data *data = (void *)user_data;
struct io *io;
- io = tester_setup_io(data->iov, data->iovcnt);
+ io = test_setup_iov(data);
g_assert(io);
tester_io_set_complete_func(test_complete_cb);
@@ -10406,7 +10557,7 @@ static void test_select(const void *user_data)
data->id = 0;
- io = tester_setup_io(data->iov, data->iovcnt);
+ io = test_setup_iov(data);
g_assert(io);
tester_io_set_complete_func(NULL);
--
2.56.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* RE: BAP ASE state handling enhancements
2026-11-03 20:30 ` [PATCH BlueZ v1 1/7] shared/bap: Defer ASE state notifications until idle Luiz Augusto von Dentz
@ 2026-11-03 22:51 ` bluez.test.bot
0 siblings, 0 replies; 9+ messages in thread
From: bluez.test.bot @ 2026-11-03 22:51 UTC (permalink / raw)
To: linux-bluetooth, luiz.dentz
[-- Attachment #1: Type: text/plain, Size: 2134 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/series/1200848/
---Test result---
Test Summary:
CheckPatch PASS 2.89 seconds
GitLint PASS 1.67 seconds
BuildEll PASS 21.48 seconds
BluezMake PASS 313.38 seconds
MakeCheck PASS 21.60 seconds
MakeDistcheck PASS 154.84 seconds
CheckValgrind PASS 365.43 seconds
CheckSmatch WARNING 302.51 seconds
bluezmakeextell PASS 99.97 seconds
TestFunctional PASS 1236.54 seconds
IncrementalBuild PASS 626.74 seconds
ScanBuild PASS 1200.75 seconds
Details
##############################
Test: CheckSmatch - WARNING
Desc: Run smatch tool with source
Output:
src/shared/bap.c:317:25: warning: array of flexible structuressrc/shared/bap.c: note: in included file:./src/shared/ascs.h:88:25: warning: array of flexible structuressrc/shared/bap.c:317:25: warning: array of flexible structuressrc/shared/bap.c: note: in included file:./src/shared/ascs.h:88:25: warning: array of flexible structuressrc/shared/bap.c:317:25: warning: array of flexible structuressrc/shared/bap.c: note: in included file:./src/shared/ascs.h:88:25: warning: array of flexible structuressrc/shared/bap.c:317:25: warning: array of flexible structuressrc/shared/bap.c: note: in included file:./src/shared/ascs.h:88:25: warning: array of flexible structuressrc/shared/bap.c:317:25: warning: array of flexible structuressrc/shared/bap.c: note: in included file:./src/shared/ascs.h:88:25: warning: array of flexible structuressrc/shared/bap.c:317:25: warning: array of flexible structuressrc/shared/bap.c: note: in included file:./src/shared/ascs.h:88:25: warning: array of flexible structures
https://github.com/bluez/bluez/pull/2754
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-11-03 22:51 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-03 20:30 [PATCH BlueZ v1 0/7] BAP ASE state handling enhancements Luiz Augusto von Dentz
2026-11-03 20:30 ` [PATCH BlueZ v1 1/7] shared/bap: Defer ASE state notifications until idle Luiz Augusto von Dentz
2026-11-03 22:51 ` BAP ASE state handling enhancements bluez.test.bot
2026-11-03 20:30 ` [PATCH BlueZ v1 2/7] shared/bap: Wait for ASE state before completing requests Luiz Augusto von Dentz
2026-11-03 20:30 ` [PATCH BlueZ v1 3/7] unit/test-bap: Add tests for cancelling grouped Enable Luiz Augusto von Dentz
2026-11-03 20:30 ` [PATCH BlueZ v1 4/7] unit/test-bap: Add Notify Multiple test Luiz Augusto von Dentz
2026-11-03 20:30 ` [PATCH BlueZ v1 5/7] shared/tester: Add support for alternative PDUs Luiz Augusto von Dentz
2026-11-03 20:30 ` [PATCH BlueZ v1 6/7] unit/test-bap: Expect the ASE state following Releasing Luiz Augusto von Dentz
2026-11-03 20:30 ` [PATCH BlueZ v1 7/7] unit/test-bap: Run unicast tests with Notify Multiple Luiz Augusto von Dentz
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox