Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH BlueZ 0/8] client/btpclient: Add BAP/ASCS/PACS support for auto-pts BAP tests
@ 2026-09-04 13:51 Frédéric Danis
  2026-09-04 13:51 ` [PATCH BlueZ 1/8] client/btpclient: Add BTP_EV_BAP_ASE_FOUND support Frédéric Danis
                   ` (7 more replies)
  0 siblings, 8 replies; 10+ messages in thread
From: Frédéric Danis @ 2026-09-04 13:51 UTC (permalink / raw)
  To: linux-bluetooth

This series extends btpclient with support for the BAP, ASCS, and PACS
BTP services required to pass the auto-pts Basic Audio Profile test
suites (BAP/UCL/*, BAP/USR/*).

- Patch 1 adds BTP_EV_BAP_ASE_FOUND, needed by BAP/UCL/SCC/BV-004-C
  and BAP/UCL/SCC/BV-019-C.
- Patch 2 fetches Codec capabilities when replying to an ASE.
- Patch 3 introduces ASCS BTP support: audio endpoints are registered
  via the VENDOR service's ASCS setup command, and the
  SelectProperties() D-Bus reply is postponed until auto-pts has
  requested the ASE and PACS characteristic values, allowing
  BAP/UCL/SCC/* tests to pass.
- Patch 4 adds BTP_OP_PACS_SET_LOCATION support to store the expected
  location for a local endpoint.
- Patch 5 adds BTP_OP_BAP_SEND support.
- Patch 6 extends ASCS support to cover the BAP/UCL/STR/* tests.
- Patch 7 fixes CIG/CIS id assignment to be deferred and based on the
  Unicast Client/Server role, matching what auto-pts expects (0/0 for
  Client tests, 1/1 for Server tests) instead of eagerly assigning
  CIG 0 to every discovered ASE.
- Patch 8 adds automatic stream acquisition and data sending for the
  BAP/USR/STR/* tests.

Together these patches allow btpclient to pass the BAP/UCL and
BAP/USR auto-pts test suites.

Frédéric Danis (8):
  client/btpclient: Add BTP_EV_BAP_ASE_FOUND support
  client/btpclient: Get Codec capabilities on ASE reply
  client/btpclient: Add ASCS BTP support
  client/btpclient: Add BTP_OP_PACS_SET_LOCATION support
  client/btpclient: Add BTP_OP_BAP_SEND support
  client/btpclient: Add ASCS support for BAP/UCL/STR/* tests
  client/btpclient: Defer CIG/CIS assignment based on Client/Server role
  client/btpclient: Add stream auto acquire for BAP/USR/STR/* tests

 Makefile.tools               |    7 +-
 client/btpclient/ascs.c      | 1641 ++++++++++++++++++++++++++++++++++
 client/btpclient/ascs.h      |   21 +
 client/btpclient/bap.c       |  381 ++++++++
 client/btpclient/bap.h       |    4 +
 client/btpclient/btpclient.c |  194 +++-
 client/btpclient/btpclient.h |   36 +
 client/btpclient/core.c      |   75 +-
 client/btpclient/gap.c       |    3 -
 client/btpclient/pacs.c      |  107 +++
 client/btpclient/pacs.h      |   13 +
 client/btpclient/vendor.c    |  103 +++
 client/btpclient/vendor.h    |   13 +
 src/shared/btp.h             |  156 ++++
 14 files changed, 2744 insertions(+), 10 deletions(-)
 create mode 100644 client/btpclient/ascs.c
 create mode 100644 client/btpclient/ascs.h
 create mode 100644 client/btpclient/pacs.c
 create mode 100644 client/btpclient/pacs.h
 create mode 100644 client/btpclient/vendor.c
 create mode 100644 client/btpclient/vendor.h

-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH BlueZ 1/8] client/btpclient: Add BTP_EV_BAP_ASE_FOUND support
  2026-09-04 13:51 [PATCH BlueZ 0/8] client/btpclient: Add BAP/ASCS/PACS support for auto-pts BAP tests Frédéric Danis
@ 2026-09-04 13:51 ` Frédéric Danis
  2026-09-04 17:38   ` client/btpclient: Add BAP/ASCS/PACS support for auto-pts BAP tests bluez.test.bot
  2026-09-04 13:51 ` [PATCH BlueZ 2/8] client/btpclient: Get Codec capabilities on ASE reply Frédéric Danis
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 10+ messages in thread
From: Frédéric Danis @ 2026-09-04 13:51 UTC (permalink / raw)
  To: linux-bluetooth

This is used at least for BAP/UCL/SCC/BV-004-C and BAP/UCL/SCC/BV-019-C
tests.
---
 client/btpclient/bap.c       | 109 +++++++++++++++++++++++++++++++++++
 client/btpclient/bap.h       |   2 +
 client/btpclient/btpclient.c |   6 ++
 client/btpclient/btpclient.h |   8 +++
 src/shared/btp.h             |  11 ++++
 5 files changed, 136 insertions(+)

diff --git a/client/btpclient/bap.c b/client/btpclient/bap.c
index 0302fc1c0..cdbae4ac2 100644
--- a/client/btpclient/bap.c
+++ b/client/btpclient/bap.c
@@ -96,6 +96,115 @@ failed:
 	btp_send_error(btp, BTP_BAP_SERVICE, index, status);
 }
 
+static void bap_charac_read_setup(struct l_dbus_message *message,
+							void *user_data)
+{
+	struct l_dbus_message_builder *builder;
+
+	builder = l_dbus_message_builder_new(message);
+	l_dbus_message_builder_enter_array(builder, "{sv}");
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_leave_dict(builder);
+	l_dbus_message_builder_leave_array(builder);
+	l_dbus_message_builder_finalize(builder);
+	l_dbus_message_builder_destroy(builder);
+}
+
+static void bap_read_ase_reply(struct l_dbus_proxy *proxy,
+						struct l_dbus_message *result,
+						void *user_data)
+{
+	struct btp_ase *ase = user_data;
+	struct btp_device *device = ase->device;
+	struct btp_adapter *adapter = find_adapter_by_device(device);
+	struct btp_bap_ase_found_ev *rp;
+	struct l_dbus_message_iter iter;
+	uint8_t *data;
+	uint32_t n;
+
+	if (l_dbus_message_is_error(result)) {
+		const char *name, *desc;
+
+		l_dbus_message_get_error(result, &name, &desc);
+		l_error("Failed to read value (%s), %s", name, desc);
+
+		btp_send_error(btp, BTP_BAP_SERVICE, adapter->index,
+							BTP_ERROR_FAIL);
+		return;
+	}
+
+	if (!l_dbus_message_get_arguments(result, "ay", &iter))
+		goto failed;
+
+	if (!l_dbus_message_iter_get_fixed_array(&iter, &data, &n)) {
+		l_debug("Cannot read value");
+		goto failed;
+	}
+
+	ase->ase_id = data[0];
+
+	rp = l_new(struct btp_bap_ase_found_ev, 1);
+	rp->address_type = device->address_type;
+	rp->address = device->address;
+	rp->dir = ase->dir;
+	rp->ase_id = data[0];
+
+	btp_send(btp, BTP_BAP_SERVICE, BTP_EV_BAP_ASE_FOUND, adapter->index,
+				sizeof(struct btp_bap_ase_found_ev), rp);
+
+	free(rp);
+
+	return;
+
+failed:
+	btp_send_error(btp, BTP_BAP_SERVICE, adapter->index, BTP_ERROR_FAIL);
+}
+
+void bap_proxy_added(struct l_dbus_proxy *proxy, void *user_data)
+{
+	struct btp_device *device = user_data;
+	const char *interface = l_dbus_proxy_get_interface(proxy);
+
+	if (!strcmp(interface, "org.bluez.GattCharacteristic1")) {
+		char *str, str_uuid[MAX_LEN_UUID_STR];
+		bt_uuid_t uuid;
+		struct btp_ase *ase;
+
+		if (!l_dbus_proxy_get_property(proxy, "UUID", "s", &str))
+			return;
+
+		bt_uuid16_create(&uuid, ASE_SINK_UUID);
+		bt_uuid_to_string(&uuid, str_uuid, MAX_LEN_UUID_STR);
+		if (!bt_uuid_strcmp(str, str_uuid)) {
+			ase = l_new(struct btp_ase, 1);
+			ase->device = device;
+			ase->dir = BTP_BAP_DIR_SINK;
+			ase->uuid = uuid;
+			l_queue_push_tail(device->ases, ase);
+
+			l_dbus_proxy_method_call(proxy, "ReadValue",
+						bap_charac_read_setup,
+						bap_read_ase_reply,
+						ase, NULL);
+		}
+
+		bt_uuid16_create(&uuid, ASE_SOURCE_UUID);
+		bt_uuid_to_string(&uuid, str_uuid, MAX_LEN_UUID_STR);
+		if (!bt_uuid_strcmp(str, str_uuid)) {
+			ase = l_new(struct btp_ase, 1);
+			ase->device = device;
+			ase->dir = BTP_BAP_DIR_SOURCE;
+			ase->uuid = uuid;
+			l_queue_push_tail(device->ases, ase);
+
+			l_dbus_proxy_method_call(proxy, "ReadValue",
+						bap_charac_read_setup,
+						bap_read_ase_reply,
+						ase, NULL);
+		}
+	}
+}
+
 bool bap_register_service(struct btp *btp_, struct l_dbus *dbus_,
 					struct l_dbus_client *client)
 {
diff --git a/client/btpclient/bap.h b/client/btpclient/bap.h
index 2b7a218b5..7fea19f8b 100644
--- a/client/btpclient/bap.h
+++ b/client/btpclient/bap.h
@@ -11,3 +11,5 @@ bool bap_register_service(struct btp *btp_, struct l_dbus *dbus_,
 					struct l_dbus_client *client);
 void bap_unregister_service(struct btp *btp);
 bool bap_is_service_registered(void);
+
+void bap_proxy_added(struct l_dbus_proxy *proxy, void *user_data);
diff --git a/client/btpclient/btpclient.c b/client/btpclient/btpclient.c
index 0d124bc7a..c0aa13287 100644
--- a/client/btpclient/btpclient.c
+++ b/client/btpclient/btpclient.c
@@ -25,6 +25,7 @@
 #include "bluetooth/uuid.h"
 #include "src/shared/btp.h"
 #include "btpclient.h"
+#include "bap.h"
 #include "core.h"
 #include "gap.h"
 #include "gatt.h"
@@ -347,6 +348,7 @@ static void signal_handler(uint32_t signo, void *user_data)
 
 static void btp_device_free(struct btp_device *device)
 {
+	l_queue_destroy(device->ases, l_free);
 	l_queue_destroy(device->services, l_free);
 	l_queue_destroy(device->characteristics, l_free);
 	l_queue_destroy(device->descriptors, l_free);
@@ -444,6 +446,7 @@ static void proxy_added(struct l_dbus_proxy *proxy, void *user_data)
 		device->services = l_queue_new();
 		device->characteristics = l_queue_new();
 		device->descriptors = l_queue_new();
+		device->ases = l_queue_new();
 
 		l_queue_push_tail(adapter->devices, device);
 
@@ -545,6 +548,9 @@ static void proxy_added(struct l_dbus_proxy *proxy, void *user_data)
 		}
 
 		l_queue_push_tail(device->characteristics, attribute);
+
+		if (bap_is_service_registered())
+			bap_proxy_added(proxy, device);
 	}
 
 	if (!strcmp(interface, "org.bluez.GattDescriptor1")) {
diff --git a/client/btpclient/btpclient.h b/client/btpclient/btpclient.h
index 8a5ea2172..74ca7278c 100644
--- a/client/btpclient/btpclient.h
+++ b/client/btpclient/btpclient.h
@@ -26,6 +26,14 @@ struct btp_device {
 	struct l_queue *services;
 	struct l_queue *characteristics;
 	struct l_queue *descriptors;
+	struct l_queue *ases;
+};
+
+struct btp_ase {
+	struct btp_device *device;
+	bt_uuid_t uuid;
+	uint8_t dir;
+	uint8_t ase_id;
 };
 
 struct btp_agent {
diff --git a/src/shared/btp.h b/src/shared/btp.h
index 5590bb51f..9757301f2 100644
--- a/src/shared/btp.h
+++ b/src/shared/btp.h
@@ -425,6 +425,9 @@ struct btp_gatt_write_rp {
 	uint8_t att_response;
 } __packed;
 
+#define BTP_BAP_DIR_SINK			0x01
+#define BTP_BAP_DIR_SOURCE			0x02
+
 #define BTP_OP_BAP_READ_SUPPORTED_COMMANDS	0x01
 
 #define BTP_OP_BAP_DISCOVER			0x02
@@ -440,6 +443,14 @@ struct btp_bap_discovery_completed_ev {
 	uint8_t status;
 } __packed;
 
+#define BTP_EV_BAP_ASE_FOUND			0x82
+struct btp_bap_ase_found_ev {
+	uint8_t address_type;
+	bdaddr_t address;
+	uint8_t dir;
+	uint8_t ase_id;
+} __packed;
+
 struct btp;
 
 typedef void (*btp_destroy_func_t)(void *user_data);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH BlueZ 2/8] client/btpclient: Get Codec capabilities on ASE reply
  2026-09-04 13:51 [PATCH BlueZ 0/8] client/btpclient: Add BAP/ASCS/PACS support for auto-pts BAP tests Frédéric Danis
  2026-09-04 13:51 ` [PATCH BlueZ 1/8] client/btpclient: Add BTP_EV_BAP_ASE_FOUND support Frédéric Danis
@ 2026-09-04 13:51 ` Frédéric Danis
  2026-09-04 13:51 ` [PATCH BlueZ 3/8] client/btpclient: Add ASCS BTP support Frédéric Danis
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Frédéric Danis @ 2026-09-04 13:51 UTC (permalink / raw)
  To: linux-bluetooth

---
 Makefile.tools         |  2 +-
 client/btpclient/bap.c | 94 ++++++++++++++++++++++++++++++++++++++++--
 src/shared/btp.h       | 12 ++++++
 3 files changed, 104 insertions(+), 4 deletions(-)

diff --git a/Makefile.tools b/Makefile.tools
index 4a59b6dee..5c4a6070a 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -567,7 +567,7 @@ noinst_PROGRAMS += client/btpclient/btpclient client/btpclient/btpclientctl
 
 client_btpclient_btpclient_SOURCES = client/btpclient/btpclient.c \
 				client/btpclient/btpclient.h \
-				src/shared/btp.c src/shared/btp.h \
+				src/shared/btp.c src/shared/btp.h src/shared/bap-defs.h \
 				client/btpclient/bap.c client/btpclient/bap.h \
 				client/btpclient/core.c client/btpclient/core.h \
 				client/btpclient/gap.c client/btpclient/gap.h \
diff --git a/client/btpclient/bap.c b/client/btpclient/bap.c
index cdbae4ac2..d30d931f1 100644
--- a/client/btpclient/bap.c
+++ b/client/btpclient/bap.c
@@ -17,6 +17,7 @@
 
 #include "bluetooth/bluetooth.h"
 #include "bluetooth/uuid.h"
+#include "src/shared/bap-defs.h"
 #include "src/shared/btp.h"
 #include "btpclient.h"
 #include "bap.h"
@@ -59,6 +60,13 @@ failed:
 	btp_send_error(btp, BTP_BAP_SERVICE, index, BTP_ERROR_FAIL);
 }
 
+static bool match_attribute_uuid(const void *attr, const void *uuid)
+{
+	const struct gatt_attribute *attribute = attr;
+
+	return !bt_uuid_cmp(&attribute->uuid, uuid);
+}
+
 static void btp_bap_discover(uint8_t index, const void *param, uint16_t length,
 								void *user_data)
 {
@@ -110,17 +118,17 @@ static void bap_charac_read_setup(struct l_dbus_message *message,
 	l_dbus_message_builder_destroy(builder);
 }
 
-static void bap_read_ase_reply(struct l_dbus_proxy *proxy,
+static void bap_read_pac_reply(struct l_dbus_proxy *proxy,
 						struct l_dbus_message *result,
 						void *user_data)
 {
 	struct btp_ase *ase = user_data;
 	struct btp_device *device = ase->device;
 	struct btp_adapter *adapter = find_adapter_by_device(device);
-	struct btp_bap_ase_found_ev *rp;
+	struct btp_bap_codec_cap_found_ev *rp;
 	struct l_dbus_message_iter iter;
 	uint8_t *data;
-	uint32_t n;
+	uint32_t n, i, capa_length;
 
 	if (l_dbus_message_is_error(result)) {
 		const char *name, *desc;
@@ -141,6 +149,73 @@ static void bap_read_ase_reply(struct l_dbus_proxy *proxy,
 		goto failed;
 	}
 
+	rp = l_new(struct btp_bap_codec_cap_found_ev, 1);
+	rp->address_type = device->address_type;
+	rp->address = device->address;
+	rp->dir = ase->dir;
+	rp->coding_format = data[1];
+	capa_length = data[6];
+	i = 0;
+	while (i < capa_length) {
+		struct bt_ltv *ltv = (struct bt_ltv *)(data + i + 7);
+
+		if ((i + ltv->len >= capa_length) || (!ltv->len))
+			goto failed;
+
+		if (ltv->type == 0x01)
+			rp->frequencies = bt_get_le16(ltv->value);
+		else if (ltv->type == 0x02)
+			rp->frame_durations = ltv->value[0];
+		else if (ltv->type == 0x03)
+			rp->channel_counts = ltv->value[0];
+		else if (ltv->type == 0x04)
+			rp->octets_per_frame = bt_get_le32(ltv->value);
+
+		i += ltv->len + 1;
+	}
+
+	btp_send(btp, BTP_BAP_SERVICE, BTP_BAP_EV_CODEC_CAP_FOUND,
+		adapter->index, sizeof(struct btp_bap_codec_cap_found_ev), rp);
+
+	free(rp);
+
+	return;
+
+failed:
+	btp_send_error(btp, BTP_BAP_SERVICE, adapter->index, BTP_ERROR_FAIL);
+}
+
+static void bap_read_ase_reply(struct l_dbus_proxy *proxy,
+						struct l_dbus_message *result,
+						void *user_data)
+{
+	struct btp_ase *ase = user_data;
+	struct btp_device *device = ase->device;
+	struct btp_adapter *adapter = find_adapter_by_device(device);
+	struct btp_bap_ase_found_ev *rp;
+	struct l_dbus_message_iter iter;
+	uint8_t *data;
+	uint32_t n;
+	bt_uuid_t uuid;
+	struct gatt_attribute *attribute;
+
+	if (l_dbus_message_is_error(result)) {
+		const char *name, *desc;
+
+		l_dbus_message_get_error(result, &name, &desc);
+		l_error("Failed to read value (%s), %s", name, desc);
+
+		goto failed;
+	}
+
+	if (!l_dbus_message_get_arguments(result, "ay", &iter))
+		goto failed;
+
+	if (!l_dbus_message_iter_get_fixed_array(&iter, &data, &n)) {
+		l_debug("Cannot read value");
+		goto failed;
+	}
+
 	ase->ase_id = data[0];
 
 	rp = l_new(struct btp_bap_ase_found_ev, 1);
@@ -154,6 +229,19 @@ static void bap_read_ase_reply(struct l_dbus_proxy *proxy,
 
 	free(rp);
 
+	if (bt_uuid16_cmp(&ase->uuid, ASE_SINK_UUID))
+		bt_uuid16_create(&uuid, PAC_SINK_CHRC_UUID);
+	else
+		bt_uuid16_create(&uuid, PAC_SOURCE_CHRC_UUID);
+	attribute = l_queue_find(device->characteristics,
+						match_attribute_uuid, &uuid);
+	if (!attribute)
+		goto failed;
+
+	l_dbus_proxy_method_call(attribute->proxy, "ReadValue",
+				bap_charac_read_setup, bap_read_pac_reply,
+				ase, NULL);
+
 	return;
 
 failed:
diff --git a/src/shared/btp.h b/src/shared/btp.h
index 9757301f2..100308de1 100644
--- a/src/shared/btp.h
+++ b/src/shared/btp.h
@@ -443,6 +443,18 @@ struct btp_bap_discovery_completed_ev {
 	uint8_t status;
 } __packed;
 
+#define BTP_BAP_EV_CODEC_CAP_FOUND		0x81
+struct btp_bap_codec_cap_found_ev {
+	uint8_t address_type;
+	bdaddr_t address;
+	uint8_t dir;
+	uint8_t coding_format;
+	uint16_t frequencies;
+	uint8_t frame_durations;
+	uint32_t octets_per_frame;
+	uint8_t channel_counts;
+} __packed;
+
 #define BTP_EV_BAP_ASE_FOUND			0x82
 struct btp_bap_ase_found_ev {
 	uint8_t address_type;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH BlueZ 3/8] client/btpclient: Add ASCS BTP support
  2026-09-04 13:51 [PATCH BlueZ 0/8] client/btpclient: Add BAP/ASCS/PACS support for auto-pts BAP tests Frédéric Danis
  2026-09-04 13:51 ` [PATCH BlueZ 1/8] client/btpclient: Add BTP_EV_BAP_ASE_FOUND support Frédéric Danis
  2026-09-04 13:51 ` [PATCH BlueZ 2/8] client/btpclient: Get Codec capabilities on ASE reply Frédéric Danis
@ 2026-09-04 13:51 ` Frédéric Danis
  2026-09-04 13:51 ` [PATCH BlueZ 4/8] client/btpclient: Add BTP_OP_PACS_SET_LOCATION support Frédéric Danis
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Frédéric Danis @ 2026-09-04 13:51 UTC (permalink / raw)
  To: linux-bluetooth

The audio endpoints are registered by ASCS setup command of VENDOR
service at start of the test.

For SelectProperties() D-Bus method, the reply is postponed after
auto-pts has requested the ASE and PACS characteritic values.

This allows to pass the BAP/UCL/SCC/* tests.
---
 Makefile.tools               |   4 +-
 client/btpclient/ascs.c      | 937 +++++++++++++++++++++++++++++++++++
 client/btpclient/ascs.h      |  18 +
 client/btpclient/btpclient.c |  85 +++-
 client/btpclient/btpclient.h |   7 +
 client/btpclient/core.c      |  59 ++-
 client/btpclient/gap.c       |   3 -
 client/btpclient/vendor.c    | 102 ++++
 client/btpclient/vendor.h    |  13 +
 src/shared/btp.h             |  43 ++
 10 files changed, 1262 insertions(+), 9 deletions(-)
 create mode 100644 client/btpclient/ascs.c
 create mode 100644 client/btpclient/ascs.h
 create mode 100644 client/btpclient/vendor.c
 create mode 100644 client/btpclient/vendor.h

diff --git a/Makefile.tools b/Makefile.tools
index 5c4a6070a..3fa943331 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -568,10 +568,12 @@ noinst_PROGRAMS += client/btpclient/btpclient client/btpclient/btpclientctl
 client_btpclient_btpclient_SOURCES = client/btpclient/btpclient.c \
 				client/btpclient/btpclient.h \
 				src/shared/btp.c src/shared/btp.h src/shared/bap-defs.h \
+				client/btpclient/ascs.c client/btpclient/ascs.h \
 				client/btpclient/bap.c client/btpclient/bap.h \
 				client/btpclient/core.c client/btpclient/core.h \
 				client/btpclient/gap.c client/btpclient/gap.h \
-				client/btpclient/gatt.c client/btpclient/gatt.h
+				client/btpclient/gatt.c client/btpclient/gatt.h \
+				client/btpclient/vendor.c client/btpclient/vendor.h
 client_btpclient_btpclient_LDADD = lib/libbluetooth-internal.la \
 				src/libshared-ell.la $(ell_ldadd)
 client/btpclient/btpclient.$(OBJEXT): src/libshared-ell.la ell/internal
diff --git a/client/btpclient/ascs.c b/client/btpclient/ascs.c
new file mode 100644
index 000000000..063fc8306
--- /dev/null
+++ b/client/btpclient/ascs.c
@@ -0,0 +1,937 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2026  Collabora Ltd.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include <ell/ell.h>
+
+#include "bluetooth/bluetooth.h"
+#include "bluetooth/uuid.h"
+#include "src/shared/btp.h"
+#include "src/shared/util.h"
+#include "src/shared/lc3.h"
+#include "btpclient.h"
+#include "ascs.h"
+
+#define SINK_PATH "/org/bluez/btpclient/sink"
+#define SOURCE_PATH "/org/bluez/btpclient/source"
+#define ENDPOINT_IFACE "org.bluez.MediaEndpoint1"
+
+#define EP_SUPPORTED_CTXT 0x0fff
+
+/* PAC LC3 Source:
+ *
+ * Frequencies: 8Khz 11Khz 16Khz 22Khz 24Khz 32Khz 44.1Khz 48Khz
+ * Duration: 7.5 ms 10 ms
+ * Channel count: 3
+ * Frame length: 26-240
+ */
+uint8_t source_capa[] = {
+	0x03, LC3_FREQ, LC3_FREQ_ANY & 0xFF, LC3_FREQ_ANY >> 8,
+	0x02, LC3_DURATION, LC3_DURATION_ANY,
+	0x05, LC3_FRAME_LEN, 26, 0, 240, 0,
+	0x02, LC3_CHAN_COUNT, 3
+};
+
+/* PAC LC3 Sink:
+ *
+ * Frequencies: 8Khz 11Khz 16Khz 22Khz 24Khz 32Khz 44.1Khz 48Khz
+ * Duration: 7.5 ms 10 ms
+ * Frame length: 26-240
+ */
+uint8_t sink_capa[] = {
+	0x03, LC3_FREQ, LC3_FREQ_ANY & 0xFF, LC3_FREQ_ANY >> 8,
+	0x02, LC3_DURATION, LC3_DURATION_ANY,
+	0x05, LC3_FRAME_LEN, 26, 0, 240, 0
+};
+
+struct codec_preset {
+	char *name;
+	const struct iovec data;
+	const struct iovec meta;
+	struct bt_bap_qos qos;
+	uint8_t target_latency;
+	uint32_t chan_alloc;
+	bool custom;
+	bool alt;
+	struct codec_preset *alt_preset;
+};
+
+#define LC3_PRESET_LL(_name, _data, _qos) \
+	{ \
+		.name = _name, \
+		.data = _data, \
+		.qos = _qos, \
+		.target_latency = 0x01, \
+	}
+
+#define LC3_PRESET_HR(_name, _data, _qos) \
+	{ \
+		.name = _name, \
+		.data = _data, \
+		.qos = _qos, \
+		.target_latency = 0x03, \
+	}
+
+static struct codec_preset lc3_ucast_presets[] = {
+	/* QoS Configuration settings for low latency audio data */
+	LC3_PRESET_LL("8_1_1", LC3_CONFIG_8_1, LC3_QOS_8_1_1),
+	LC3_PRESET_LL("8_2_1", LC3_CONFIG_8_2, LC3_QOS_8_2_1),
+	LC3_PRESET_LL("16_1_1", LC3_CONFIG_16_1, LC3_QOS_16_1_1),
+	LC3_PRESET_LL("16_2_1", LC3_CONFIG_16_2, LC3_QOS_16_2_1),
+	LC3_PRESET_LL("24_1_1", LC3_CONFIG_24_1, LC3_QOS_24_1_1),
+	LC3_PRESET_LL("24_2_1", LC3_CONFIG_24_2, LC3_QOS_24_2_1),
+	LC3_PRESET_LL("32_1_1", LC3_CONFIG_32_1, LC3_QOS_32_1_1),
+	LC3_PRESET_LL("32_2_1", LC3_CONFIG_32_2, LC3_QOS_32_2_1),
+	LC3_PRESET_LL("44_1_1", LC3_CONFIG_44_1, LC3_QOS_44_1_1),
+	LC3_PRESET_LL("44_2_1", LC3_CONFIG_44_2, LC3_QOS_44_2_1),
+	LC3_PRESET_LL("48_1_1", LC3_CONFIG_48_1, LC3_QOS_48_1_1),
+	LC3_PRESET_LL("48_2_1", LC3_CONFIG_48_2, LC3_QOS_48_2_1),
+	LC3_PRESET_LL("48_3_1", LC3_CONFIG_48_3, LC3_QOS_48_3_1),
+	LC3_PRESET_LL("48_4_1", LC3_CONFIG_48_4, LC3_QOS_48_4_1),
+	LC3_PRESET_LL("48_5_1", LC3_CONFIG_48_5, LC3_QOS_48_5_1),
+	LC3_PRESET_LL("48_6_1", LC3_CONFIG_48_6, LC3_QOS_48_6_1),
+	/* QoS Configuration settings for high reliability audio data */
+	LC3_PRESET_HR("8_1_2", LC3_CONFIG_8_1, LC3_QOS_8_1_2),
+	LC3_PRESET_HR("8_2_2", LC3_CONFIG_8_2, LC3_QOS_8_2_2),
+	LC3_PRESET_HR("16_1_2", LC3_CONFIG_16_1, LC3_QOS_16_1_2),
+	LC3_PRESET_HR("16_2_2", LC3_CONFIG_16_2, LC3_QOS_16_2_2),
+	LC3_PRESET_HR("24_1_2", LC3_CONFIG_24_1, LC3_QOS_24_1_2),
+	LC3_PRESET_HR("24_2_2", LC3_CONFIG_24_2, LC3_QOS_24_2_2),
+	LC3_PRESET_HR("32_1_2", LC3_CONFIG_32_1, LC3_QOS_32_1_2),
+	LC3_PRESET_HR("32_2_2", LC3_CONFIG_32_2, LC3_QOS_32_2_2),
+	LC3_PRESET_HR("44_1_2", LC3_CONFIG_44_1, LC3_QOS_44_1_2),
+	LC3_PRESET_HR("44_2_2", LC3_CONFIG_44_2, LC3_QOS_44_2_2),
+	LC3_PRESET_HR("48_1_2", LC3_CONFIG_48_1, LC3_QOS_48_1_2),
+	LC3_PRESET_HR("48_2_2", LC3_CONFIG_48_2, LC3_QOS_48_2_2),
+	LC3_PRESET_HR("48_3_2", LC3_CONFIG_48_3, LC3_QOS_48_3_2),
+	LC3_PRESET_HR("48_4_2", LC3_CONFIG_48_4, LC3_QOS_48_4_2),
+	LC3_PRESET_HR("48_5_2", LC3_CONFIG_48_5, LC3_QOS_48_5_2),
+	LC3_PRESET_HR("48_6_2", LC3_CONFIG_48_6, LC3_QOS_48_6_2)
+};
+
+static struct btp *btp;
+static struct l_dbus *dbus;
+static bool ascs_service_registered;
+struct l_queue *pending_select_properties;
+
+static void btp_ascs_read_commands(uint8_t index, const void *param,
+					uint16_t length, void *user_data)
+{
+	const uint8_t supported_commands[] = {
+		BTP_OP_ASCS_READ_SUPPORTED_COMMANDS,
+		BTP_OP_ASCS_CONFIGURE_CODEC,
+	};
+	uint8_t *commands = NULL;
+	size_t commands_len = 0;
+	size_t i;
+
+	if (index != BTP_INDEX_NON_CONTROLLER) {
+		btp_send_error(btp, BTP_ASCS_SERVICE, index,
+						BTP_ERROR_INVALID_INDEX);
+		return;
+	}
+
+	for (i = 0; i < L_ARRAY_SIZE(supported_commands); i++) {
+		if (!add_supported_command(&commands, &commands_len,
+						supported_commands[i]))
+			goto failed;
+	}
+
+	btp_send(btp, BTP_ASCS_SERVICE, BTP_OP_ASCS_READ_SUPPORTED_COMMANDS,
+			BTP_INDEX_NON_CONTROLLER, commands_len, commands);
+
+	l_free(commands);
+
+	return;
+
+failed:
+	l_free(commands);
+	btp_send_error(btp, BTP_ASCS_SERVICE, index, BTP_ERROR_FAIL);
+}
+
+static void ascs_register_endpoint_setup(struct l_dbus_message *message,
+							void *user_data)
+{
+	uint uuid = L_PTR_TO_UINT(user_data);
+	struct l_dbus_message_builder *builder;
+	const char *path, *uuid_str;
+	uint8_t *capa;
+	unsigned int capa_size, val, i;
+
+	if (uuid == PAC_SOURCE_CHRC_UUID) {
+		path = SOURCE_PATH;
+		uuid_str = PAC_SOURCE_UUID;
+		capa = source_capa;
+		capa_size = sizeof(source_capa);
+	} else {
+		path = SINK_PATH;
+		uuid_str = PAC_SINK_UUID;
+		capa = sink_capa;
+		capa_size = sizeof(sink_capa);
+	}
+
+	builder = l_dbus_message_builder_new(message);
+
+	l_dbus_message_builder_append_basic(builder, 'o', path);
+
+	l_dbus_message_builder_enter_array(builder, "{sv}");
+
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's', "UUID");
+	l_dbus_message_builder_enter_variant(builder, "s");
+	l_dbus_message_builder_append_basic(builder, 's', uuid_str);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
+
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's', "Codec");
+	l_dbus_message_builder_enter_variant(builder, "y");
+	val = 0x06;
+	l_dbus_message_builder_append_basic(builder, 'y', &val);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
+
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's', "Capabilities");
+	l_dbus_message_builder_enter_variant(builder, "ay");
+	l_dbus_message_builder_enter_array(builder, "y");
+	for (i = 0; i < capa_size; i++)
+		l_dbus_message_builder_append_basic(builder, 'y', &(capa[i]));
+	l_dbus_message_builder_leave_array(builder);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
+
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's', "Locations");
+	l_dbus_message_builder_enter_variant(builder, "u");
+	val = 1;
+	l_dbus_message_builder_append_basic(builder, 'u', &val);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
+
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's', "SupportedContext");
+	l_dbus_message_builder_enter_variant(builder, "q");
+	val = EP_SUPPORTED_CTXT;
+	l_dbus_message_builder_append_basic(builder, 'q', &val);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
+
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's', "Context");
+	l_dbus_message_builder_enter_variant(builder, "q");
+	val = EP_SUPPORTED_CTXT;
+	l_dbus_message_builder_append_basic(builder, 'q', &val);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
+
+	l_dbus_message_builder_leave_array(builder);
+
+	l_dbus_message_builder_finalize(builder);
+	l_dbus_message_builder_destroy(builder);
+}
+
+static void ascs_register_endpoint_reply(struct l_dbus_proxy *proxy,
+						struct l_dbus_message *result,
+						void *user_data)
+{
+	uint uuid = L_PTR_TO_UINT(user_data);
+	const char *path;
+
+	if (uuid == PAC_SOURCE_CHRC_UUID)
+		path = SOURCE_PATH;
+	else
+		path = SINK_PATH;
+
+	if (l_dbus_message_is_error(result)) {
+		const char *name, *desc;
+
+		l_dbus_message_get_error(result, &name, &desc);
+		l_error("Failed to set register endpoint (%s), %s", name, desc);
+
+		goto failed;
+	}
+
+	l_info("Media endpoint %s registered", path);
+
+	return;
+
+failed:
+	if (!l_dbus_object_remove_interface(dbus, path, ENDPOINT_IFACE))
+		l_info("Unable to remove endpoint instance");
+}
+
+static bool register_endpoint(struct btp_adapter *adapter, bt_uuid_t *uuid)
+{
+	uint16_t pac_uuid;
+	const char *path;
+
+	if (bt_uuid16_cmp(uuid, ASE_SINK_UUID)) {
+		pac_uuid = PAC_SOURCE_CHRC_UUID;
+		path = SOURCE_PATH;
+	} else {
+		pac_uuid = PAC_SINK_CHRC_UUID;
+		path = SINK_PATH;
+	}
+
+	if (!l_dbus_object_add_interface(dbus, path,
+						ENDPOINT_IFACE, adapter)) {
+		l_info("Unable to instantiate endpoint interface");
+		return false;
+	}
+
+	if (!l_dbus_object_add_interface(dbus, path,
+						L_DBUS_INTERFACE_PROPERTIES,
+						adapter)) {
+		l_info("Unable to instantiate endpoint properties interface");
+		return false;
+	}
+
+	l_dbus_proxy_method_call(adapter->media_proxy,
+			"RegisterEndpoint",
+			ascs_register_endpoint_setup,
+			ascs_register_endpoint_reply,
+			L_UINT_TO_PTR(pac_uuid), NULL);
+
+	return true;
+}
+
+static void send_state_changed(uint8_t index, const bdaddr_t *address,
+					uint8_t address_type, uint8_t id,
+					uint8_t state)
+{
+	struct btp_ascs_ase_state_changed_ev ev;
+
+	memcpy(&ev.address, address, sizeof(ev.address));
+	ev.address_type = address_type;
+	ev.ase_id = id;
+	ev.state = state;
+
+	btp_send(btp, BTP_ASCS_SERVICE, BTP_EV_ASCS_ASE_STATE_CHANGED,
+						index, sizeof(ev), &ev);
+}
+
+static void btp_ascs_configure_codec(uint8_t index, const void *param,
+					uint16_t length, void *user_data)
+{
+	struct btp_adapter *adapter = find_adapter_by_index(index);
+	struct btp_device *dev;
+	const struct btp_ascs_configure_codec_cp *cp = param;
+	struct btp_ascs_operation_completed_ev ev;
+	uint8_t status = BTP_ERROR_FAIL;
+
+	if (!adapter) {
+		status = BTP_ERROR_INVALID_INDEX;
+		goto failed;
+	}
+
+	dev = find_device_by_address(adapter, &cp->address, cp->address_type);
+	if (!dev)
+		goto failed;
+
+	btp_send(btp, BTP_ASCS_SERVICE, BTP_OP_ASCS_CONFIGURE_CODEC, index, 0,
+									NULL);
+
+	memcpy(&ev.address, &cp->address, sizeof(ev.address));
+	ev.address_type = cp->address_type;
+	ev.ase_id = cp->ase_id;
+	ev.opcode = BTP_OP_ASCS_CONFIGURE_CODEC;
+	ev.status = 0;
+	ev.flags = 0;
+
+	btp_send(btp, BTP_ASCS_SERVICE, BTP_EV_ASCS_OPERATION_COMPLETED,
+				adapter->index, sizeof(ev), &ev);
+
+	send_state_changed(adapter->index, &cp->address, cp->address_type,
+				cp->ase_id, BT_BAP_STREAM_STATE_CONFIG);
+
+	return;
+
+failed:
+	btp_send_error(btp, BTP_ASCS_SERVICE, index, status);
+}
+
+bool ascs_setup(struct btp_adapter *adapter)
+{
+	bt_uuid_t uuid;
+
+	bt_uuid16_create(&uuid, ASE_SINK_UUID);
+	register_endpoint(adapter, &uuid);
+	bt_uuid16_create(&uuid, ASE_SOURCE_UUID);
+	register_endpoint(adapter, &uuid);
+
+	return true;
+}
+
+static struct l_dbus_message *ep_set_conf(struct l_dbus *dbus,
+						struct l_dbus_message *message,
+						void *user_data)
+{
+	l_info("Set configuration (%s)", l_dbus_message_get_path(message));
+
+	return l_dbus_message_new_method_return(message);
+}
+
+static struct l_dbus_message *ep_select_conf(struct l_dbus *dbus,
+						struct l_dbus_message *message,
+						void *user_data)
+{
+	l_info("Select configuration (%s)", l_dbus_message_get_path(message));
+
+	return l_dbus_message_new_error(message,
+					"org.bluez.Error.NotSupported",
+					"NotSupported");
+}
+
+static void parse_capabilities(uint8_t *data, uint32_t len, uint16_t *rates,
+				uint8_t *durations, uint8_t *channel_counts,
+				uint16_t *frame_min, uint16_t *frame_max,
+				uint8_t *blks)
+{
+	uint32_t i = 0;
+
+	while (i < len) {
+		struct bt_ltv *ltv = (struct bt_ltv *)(data + i);
+
+		if ((i + ltv->len >= len) || (!ltv->len)) {
+			l_warn("Invalid LTV in capabilities");
+			return;
+		}
+
+		switch (ltv->type) {
+		case LC3_FREQ:
+			if (rates)
+				*rates = bt_get_le16(ltv->value);
+			break;
+		case LC3_DURATION:
+			if (durations)
+				*durations = ltv->value[0];
+			break;
+		case LC3_CHAN_COUNT:
+			if (channel_counts)
+				*channel_counts = ltv->value[0];
+			break;
+		case LC3_FRAME_LEN:
+			if (frame_min)
+				*frame_min = bt_get_le16(&ltv->value[0]);
+			if (frame_max)
+				*frame_max = bt_get_le16(&ltv->value[2]);
+			break;
+		case LC3_FRAME_COUNT:
+			if (blks)
+				*blks = ltv->value[0];
+			break;
+		default:
+			l_debug("Unknown capability type: 0x%02X", ltv->type);
+		}
+
+		i += ltv->len + 1;
+	}
+}
+
+static void parse_config(struct codec_preset *preset, uint8_t *freq,
+				uint8_t *duration, uint16_t *frame_len)
+{
+	uint8_t *cfg = preset->data.iov_base;
+	size_t len = preset->data.iov_len;
+	uint8_t *ptr = cfg;
+
+	while ((size_t)(ptr - cfg) < len) {
+		uint8_t length = ptr[0];
+		uint8_t type;
+
+		if (length < 1) {
+			l_warn("Invalid LTV in capabilities");
+			return;
+		}
+
+		ptr++;
+		type = ptr[0];
+
+		switch (type) {
+		case LC3_CONFIG_FREQ:
+			if (freq)
+				*freq = ptr[1];
+			break;
+		case LC3_CONFIG_DURATION:
+			if (duration)
+				*duration = ptr[1];
+			break;
+		case LC3_CONFIG_FRAME_LEN:
+			if (frame_len)
+				*frame_len = bt_get_le16(&ptr[1]);
+			break;
+		default:
+			l_debug("Unknown config type: 0x%02X", type);
+		}
+
+		ptr += length;
+	}
+}
+
+static struct codec_preset *select_properties(uint16_t rates, uint8_t durations,
+				uint8_t channel_counts, uint16_t frame_min,
+				uint16_t frame_max, uint32_t locations,
+				uint32_t channel, uint8_t target_latency)
+{
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(lc3_ucast_presets); i++) {
+		struct codec_preset *preset = &lc3_ucast_presets[i];
+		uint8_t c_freq = 0;
+		uint8_t c_duration = UINT8_MAX;
+		uint16_t c_frame_len = UINT16_MAX;
+
+		parse_config(preset, &c_freq, &c_duration, &c_frame_len);
+		if ((c_freq == 0) || (c_duration == UINT8_MAX) ||
+						(c_frame_len == UINT16_MAX))
+			continue;
+
+		if (!((1 << (c_freq - 1)) & rates))
+			continue;
+
+		if (!((1 << c_duration) & durations))
+			continue;
+
+		if ((c_frame_len < frame_min) || (c_frame_len > frame_max))
+			continue;
+
+		if (target_latency &&
+				(preset->target_latency != target_latency))
+			continue;
+
+		return preset;
+	}
+
+	return NULL;
+}
+
+static void ltv_find(size_t i, uint8_t l, uint8_t t, uint8_t *v,
+					void *user_data)
+{
+	bool *found = user_data;
+
+	*found = true;
+}
+
+static struct l_dbus_message *get_properties_reply(
+						struct l_dbus_message *message,
+						struct btp_adapter *adapter,
+						struct btp_ase *ase)
+{
+	struct l_dbus_message_iter props, var, var_2;
+	const char *key;
+	uint8_t *data;
+	uint32_t n = UINT8_MAX;
+	uint8_t durations = 0, channel_counts = 0;
+	uint16_t rates = 0, frame_min = 0, frame_max = 0;
+	uint32_t locations = 0, channel = 1;
+	struct l_dbus_message *reply;
+	struct l_dbus_message_builder *builder;
+	uint8_t i;
+	struct codec_preset *preset;
+	struct iovec *caps;
+	uint8_t type = LC3_CONFIG_CHAN_ALLOC;
+	bool found = false;
+	uint8_t *ptr;
+	uint16_t sdu;
+
+	l_info("Select properties (%s)", l_dbus_message_get_path(message));
+
+	if (!l_dbus_message_get_arguments(message, "a{sv}", &props))
+		goto failed;
+
+	while (l_dbus_message_iter_next_entry(&props, &key, &var)) {
+		if (!strcmp(key, "Capabilities")) {
+			if (!l_dbus_message_iter_get_variant(&var, "ay",
+								&var_2)) {
+				l_debug("Failed to get data variant");
+				goto failed;
+			}
+
+			if (!l_dbus_message_iter_get_fixed_array(&var_2,
+								&data, &n)) {
+				l_debug("Cannot get Capabilities");
+				goto failed;
+			}
+
+			parse_capabilities(data, n, &rates, &durations,
+						&channel_counts, &frame_min,
+						&frame_max, NULL);
+		} else if (!strcmp(key, "ChannelAllocation")) {
+			if (!l_dbus_message_iter_get_variant(&var, "u",
+								&channel))
+				goto failed;
+		} else if (!strcmp(key, "Locations")) {
+			if (!l_dbus_message_iter_get_variant(&var, "u",
+								&locations))
+				goto failed;
+		}
+	}
+
+	preset = select_properties(rates, durations, channel_counts,
+					frame_min, frame_max, locations,
+					channel, adapter->target_latency);
+	if (!preset)
+		goto failed;
+
+	caps = util_iov_dup(&preset->data, 1);
+	/* Check if Channel Allocation is present in caps */
+	util_ltv_foreach(caps->iov_base, caps->iov_len,
+				&type, ltv_find, &found);
+
+	/* If Channel Allocation has not been set directly via
+	 * preset->data then attempt to set it if chan_alloc has been
+	 * set.
+	 */
+	if (!found && locations) {
+		uint8_t chan_alloc_ltv[] = {
+			0x05, LC3_CONFIG_CHAN_ALLOC, 0, 0, 0, 0
+		};
+
+		put_le32(channel, &chan_alloc_ltv[2]);
+		util_iov_append(caps, &chan_alloc_ltv,
+					sizeof(chan_alloc_ltv));
+	}
+
+	reply = l_dbus_message_new_method_return(message);
+
+	builder = l_dbus_message_builder_new(reply);
+	l_dbus_message_builder_enter_array(builder, "{sv}");
+
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's', "Capabilities");
+	l_dbus_message_builder_enter_variant(builder, "ay");
+	l_dbus_message_builder_enter_array(builder, "y");
+	ptr = caps->iov_base;
+	for (i = 0; i < caps->iov_len; i++)
+		l_dbus_message_builder_append_basic(builder, 'y', ptr++);
+	l_dbus_message_builder_leave_array(builder);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
+
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's', "QoS");
+	l_dbus_message_builder_enter_variant(builder, "a{sv}");
+	l_dbus_message_builder_enter_array(builder, "{sv}");
+
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's',
+					"PresentationDelay");
+	l_dbus_message_builder_enter_variant(builder, "u");
+	l_dbus_message_builder_append_basic(builder, 'u',
+					&preset->qos.ucast.delay);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
+
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's',
+					"TargetLatency");
+	l_dbus_message_builder_enter_variant(builder, "y");
+	l_dbus_message_builder_append_basic(builder, 'y',
+					&preset->qos.ucast.target_latency);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
+
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's', "Interval");
+	l_dbus_message_builder_enter_variant(builder, "u");
+	l_dbus_message_builder_append_basic(builder, 'u',
+					&preset->qos.ucast.io_qos.interval);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
+
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's', "PHY");
+	l_dbus_message_builder_enter_variant(builder, "y");
+	l_dbus_message_builder_append_basic(builder, 'y',
+					&preset->qos.ucast.io_qos.phys);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
+
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's', "SDU");
+	l_dbus_message_builder_enter_variant(builder, "q");
+	sdu = preset->qos.ucast.io_qos.sdu * channel;
+	l_dbus_message_builder_append_basic(builder, 'q', &sdu);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
+
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's',
+					"Retransmissions");
+	l_dbus_message_builder_enter_variant(builder, "y");
+	l_dbus_message_builder_append_basic(builder, 'y',
+					&preset->qos.ucast.io_qos.rtn);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
+
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's', "Latency");
+	l_dbus_message_builder_enter_variant(builder, "q");
+	l_dbus_message_builder_append_basic(builder, 'q',
+				&preset->qos.ucast.io_qos.latency);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
+
+	l_dbus_message_builder_leave_array(builder);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
+
+	l_dbus_message_builder_leave_array(builder);
+	l_dbus_message_builder_finalize(builder);
+	l_dbus_message_builder_destroy(builder);
+
+	return reply;
+
+failed:
+	return l_dbus_message_new_error(message, "org.bluez.Error.Invalid",
+						"Invalid arguments");
+}
+
+static struct l_dbus_message *ep_select_properties(struct l_dbus *dbus,
+						struct l_dbus_message *message,
+						void *user_data)
+{
+	struct btp_adapter *adapter = user_data;
+	struct l_dbus_message_iter props, var;
+	const struct l_queue_entry *e1, *e2;
+	const char *key, *ep_path = NULL;
+	struct btp_ase *ase = NULL;
+
+	if (!l_dbus_message_get_arguments(message, "a{sv}", &props))
+		goto failed;
+
+	while (l_dbus_message_iter_next_entry(&props, &key, &var)) {
+		if (!strcmp(key, "Endpoint")) {
+			if (!l_dbus_message_iter_get_variant(&var, "o",
+								&ep_path))
+				goto failed;
+		}
+	}
+
+	if (!ep_path)
+		goto failed;
+
+	for (e1 = l_queue_get_entries(adapter->devices); e1; e1 = e1->next) {
+		struct btp_device *device = e1->data;
+
+		for (e2 = l_queue_get_entries(device->ases); e2;
+							e2 = e2->next) {
+			struct btp_ase *a = e2->data;
+
+			if (a->ep_proxy == NULL)
+				continue;
+
+			if (!strcmp(l_dbus_proxy_get_path(a->ep_proxy),
+								ep_path)) {
+				ase = a;
+				break;
+			}
+		}
+	}
+
+	if (!ase) {
+		l_info("Select properties (%s) postponed",
+					l_dbus_message_get_path(message));
+		l_queue_push_tail(pending_select_properties,
+					l_dbus_message_ref(message));
+		return NULL;
+	}
+
+	return get_properties_reply(message, adapter, ase);
+
+failed:
+	return l_dbus_message_new_error(message, "org.bluez.Error.Invalid",
+						"Invalid arguments");
+}
+
+static struct l_dbus_message *ep_clear_conf(struct l_dbus *dbus,
+						struct l_dbus_message *message,
+						void *user_data)
+{
+	l_info("Clear configuration (%s)", l_dbus_message_get_path(message));
+
+	return l_dbus_message_new_error(message,
+					"org.bluez.Error.NotSupported",
+					"NotSupported");
+}
+
+static void setup_endpoint_interface(struct l_dbus_interface *iface)
+{
+	l_dbus_interface_method(iface, "SetConfiguration", 0,
+					ep_set_conf, "", "oa{sv}",
+					"transport", "properties");
+	l_dbus_interface_method(iface, "SelectConfiguration", 0,
+					ep_select_conf, "ay", "ay",
+					"capabilities");
+	l_dbus_interface_method(iface, "SelectProperties", 0,
+					ep_select_properties, "a{sv}",
+					"a{sv}", "capabilities");
+	l_dbus_interface_method(iface, "ClearConfiguration", 0,
+					ep_clear_conf, "", "o", "transport");
+}
+
+static struct l_dbus_message *find_pending_msg(struct l_queue *l,
+							struct btp_ase *ase)
+{
+	const struct l_queue_entry *entry;
+
+	for (entry = l_queue_get_entries(l); entry; entry = entry->next) {
+		struct l_dbus_message *msg = entry->data;
+		struct l_dbus_message_iter props, var;
+		const char *key, *ep_path = NULL;
+
+		if (!l_dbus_message_get_arguments(msg, "a{sv}", &props))
+			continue;
+
+		while (l_dbus_message_iter_next_entry(&props, &key, &var)) {
+			if (!strcmp(key, "Endpoint")) {
+				if (l_dbus_message_iter_get_variant(&var, "o",
+								&ep_path))
+					break;
+			}
+		}
+
+		if (!l_dbus_proxy_get_path(ase->ep_proxy))
+			continue;
+
+		if (ep_path && !strcmp(l_dbus_proxy_get_path(ase->ep_proxy),
+								ep_path))
+			return msg;
+	}
+
+	return NULL;
+}
+
+void ascs_property_changed(struct l_dbus_proxy *proxy, const char *name,
+				struct l_dbus_message *msg, void *user_data)
+{
+	const char *interface = l_dbus_proxy_get_interface(proxy);
+
+	if (!strcmp(interface, "org.bluez.GattCharacteristic1")) {
+		char *uuid, *str;
+		struct btp_device *device;
+		struct btp_adapter *adapter;
+		struct btp_ase *ase;
+		struct l_dbus_message_iter iter;
+		uint8_t *data;
+		uint32_t n;
+		const struct l_queue_entry *entry;
+		struct l_dbus_message *reply;
+
+		if (strcmp(name, "Value"))
+			return;
+
+		if (!l_dbus_proxy_get_property(proxy, "UUID", "s", &uuid))
+			return;
+
+		if (!l_dbus_proxy_get_property(proxy, "Service", "o", &str))
+			return;
+
+		device = find_device_by_service_path(str);
+		if (!device)
+			return;
+
+		adapter = find_adapter_by_device(device);
+		if (!adapter)
+			return;
+
+		ase = find_ase_by_uuid(device, uuid);
+		if (!ase)
+			return;
+
+		if (!l_dbus_message_get_arguments(msg, "ay", &iter))
+			return;
+
+		if (!l_dbus_message_iter_get_fixed_array(&iter, &data, &n)) {
+			l_debug("Cannot read value");
+			return;
+		}
+
+		ase->ase_id = data[0];
+
+		for (entry = l_queue_get_entries(device->endpoints); entry;
+							entry = entry->next) {
+			struct l_dbus_proxy *p = entry->data;
+			const char *str, *pac_uuid;
+
+			if (!l_dbus_proxy_get_property(p, "UUID", "s", &str))
+				continue;
+
+			if (bt_uuid16_cmp(&ase->uuid, ASE_SINK_UUID))
+				pac_uuid = PAC_SINK_UUID;
+			else
+				pac_uuid = PAC_SOURCE_UUID;
+
+			if (!strcmp(str, pac_uuid)) {
+				ase->ep_proxy = p;
+				break;
+			}
+		}
+
+		msg = find_pending_msg(pending_select_properties, ase);
+		if (msg) {
+			l_queue_remove(pending_select_properties, msg);
+			reply = get_properties_reply(msg, adapter, ase);
+			l_dbus_send(dbus, reply);
+
+			l_dbus_message_unref(msg);
+		}
+	}
+}
+
+bool ascs_register_service(struct btp *btp_, struct l_dbus *dbus_,
+					struct l_dbus_client *client)
+{
+	btp = btp_;
+	dbus = dbus_;
+
+	btp_register(btp, BTP_ASCS_SERVICE,
+					BTP_OP_ASCS_READ_SUPPORTED_COMMANDS,
+					btp_ascs_read_commands, NULL, NULL);
+
+	btp_register(btp, BTP_ASCS_SERVICE, BTP_OP_ASCS_CONFIGURE_CODEC,
+					btp_ascs_configure_codec, NULL, NULL);
+
+	if (!l_dbus_register_interface(dbus, ENDPOINT_IFACE,
+						setup_endpoint_interface,
+						NULL, false)) {
+		l_info("Unable to register endpoint interface");
+		return false;
+	}
+
+	pending_select_properties = l_queue_new();
+
+	ascs_service_registered = true;
+
+	return true;
+}
+
+void ascs_unregister_service(struct btp *btp)
+{
+	l_queue_destroy(pending_select_properties,
+				(l_queue_destroy_func_t)l_dbus_message_unref);
+
+	if (!l_dbus_unregister_interface(dbus, ENDPOINT_IFACE))
+		l_info("Unable to unregister endpoint interface");
+
+	btp_unregister_service(btp, BTP_ASCS_SERVICE);
+	ascs_service_registered = false;
+}
+
+bool ascs_is_service_registered(void)
+{
+	return ascs_service_registered;
+}
diff --git a/client/btpclient/ascs.h b/client/btpclient/ascs.h
new file mode 100644
index 000000000..46d38ae90
--- /dev/null
+++ b/client/btpclient/ascs.h
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2026  Collabora Ltd.
+ *
+ */
+
+bool ascs_register_service(struct btp *btp_, struct l_dbus *dbus_,
+					struct l_dbus_client *client);
+void ascs_unregister_service(struct btp *btp);
+bool ascs_is_service_registered(void);
+
+bool ascs_setup(struct btp_adapter *adapter);
+
+void ascs_property_changed(struct l_dbus_proxy *proxy, const char *name,
+				struct l_dbus_message *msg, void *user_data);
diff --git a/client/btpclient/btpclient.c b/client/btpclient/btpclient.c
index c0aa13287..eb4d348db 100644
--- a/client/btpclient/btpclient.c
+++ b/client/btpclient/btpclient.c
@@ -25,6 +25,7 @@
 #include "bluetooth/uuid.h"
 #include "src/shared/btp.h"
 #include "btpclient.h"
+#include "ascs.h"
 #include "bap.h"
 #include "core.h"
 #include "gap.h"
@@ -229,6 +230,24 @@ struct btp_device *find_device_by_proxy(struct l_dbus_proxy *proxy)
 	return NULL;
 }
 
+static bool match_uuid(const void *entry, const void *data)
+{
+	const struct btp_ase *ase = entry;
+	const bt_uuid_t *uuid = data;
+
+	return !bt_uuid_cmp(&ase->uuid, uuid);
+}
+
+struct btp_ase *find_ase_by_uuid(struct btp_device *device, char *uuid)
+{
+	bt_uuid_t u;
+
+	if (bt_string_to_uuid(&u, uuid) < 0)
+		return NULL;
+
+	return l_queue_find(device->ases, match_uuid, &u);
+}
+
 static bool match_device_service_path(const void *device, const void *path)
 {
 	const struct btp_device *dev = device;
@@ -246,7 +265,7 @@ static bool match_device_service_path(const void *device, const void *path)
 	return false;
 }
 
-static struct btp_device *find_device_by_service_path(const char *path)
+struct btp_device *find_device_by_service_path(const char *path)
 {
 	const struct l_queue_entry *entry;
 	struct btp_device *device;
@@ -348,6 +367,7 @@ static void signal_handler(uint32_t signo, void *user_data)
 
 static void btp_device_free(struct btp_device *device)
 {
+	l_queue_destroy(device->endpoints, NULL);
 	l_queue_destroy(device->ases, l_free);
 	l_queue_destroy(device->services, l_free);
 	l_queue_destroy(device->characteristics, l_free);
@@ -447,6 +467,7 @@ static void proxy_added(struct l_dbus_proxy *proxy, void *user_data)
 		device->characteristics = l_queue_new();
 		device->descriptors = l_queue_new();
 		device->ases = l_queue_new();
+		device->endpoints = l_queue_new();
 
 		l_queue_push_tail(adapter->devices, device);
 
@@ -584,6 +605,34 @@ static void proxy_added(struct l_dbus_proxy *proxy, void *user_data)
 
 		l_queue_push_tail(device->descriptors, attribute);
 	}
+
+	if (!strcmp(interface, "org.bluez.Media1")) {
+		struct btp_adapter *adapter;
+
+		adapter = find_adapter_by_path(path);
+		if (!adapter)
+			return;
+
+		adapter->media_proxy = proxy;
+
+		return;
+	}
+
+	if (!strcmp(interface, "org.bluez.MediaEndpoint1")) {
+		char *str;
+		struct btp_device *device;
+
+		if (!l_dbus_proxy_get_property(proxy, "Device", "o", &str))
+			return;
+
+		device = find_device_by_path(str);
+		if (!device)
+			return;
+
+		l_queue_push_tail(device->endpoints, proxy);
+
+		return;
+	}
 }
 
 static bool device_match_by_proxy(const void *a, const void *b)
@@ -653,13 +702,47 @@ static void proxy_removed(struct l_dbus_proxy *proxy, void *user_data)
 
 		l_queue_remove(device->descriptors, proxy);
 	}
+
+	if (!strcmp(interface, "org.bluez.Media1")) {
+		struct btp_adapter *adapter;
+
+		adapter = find_adapter_by_path(path);
+		if (!adapter)
+			return;
+
+		adapter->media_proxy = NULL;
+
+		return;
+	}
+
+	if (!strcmp(interface, "org.bluez.MediaEndpoint1")) {
+		char *str;
+		struct btp_device *device;
+
+		if (!l_dbus_proxy_get_property(proxy, "Device", "o", &str))
+			return;
+
+		device = find_device_by_path(str);
+		if (!device)
+			return;
+
+		l_queue_remove(device->endpoints, proxy);
+	}
 }
 
 static void property_changed(struct l_dbus_proxy *proxy, const char *name,
 				struct l_dbus_message *msg, void *user_data)
 {
+	const char *interface = l_dbus_proxy_get_interface(proxy);
+	const char *path = l_dbus_proxy_get_path(proxy);
+
+	l_info("Property changed: %s %s %s", name, path, interface);
+
 	if (gap_is_service_registered())
 		gap_property_changed(proxy, name, msg, user_data);
+
+	if (ascs_is_service_registered())
+		ascs_property_changed(proxy, name, msg, user_data);
 }
 
 static void client_connected(struct l_dbus *dbus, void *user_data)
diff --git a/client/btpclient/btpclient.h b/client/btpclient/btpclient.h
index 74ca7278c..0d81405d3 100644
--- a/client/btpclient/btpclient.h
+++ b/client/btpclient/btpclient.h
@@ -12,11 +12,14 @@
 struct btp_adapter {
 	struct l_dbus_proxy *proxy;
 	struct l_dbus_proxy *ad_proxy;
+	struct l_dbus_proxy *media_proxy;
 	uint8_t index;
 	uint32_t supported_settings;
 	uint32_t current_settings;
 	uint32_t default_settings;
 	struct l_queue *devices;
+
+	uint8_t target_latency;
 };
 
 struct btp_device {
@@ -27,6 +30,7 @@ struct btp_device {
 	struct l_queue *characteristics;
 	struct l_queue *descriptors;
 	struct l_queue *ases;
+	struct l_queue *endpoints;
 };
 
 struct btp_ase {
@@ -34,6 +38,7 @@ struct btp_ase {
 	bt_uuid_t uuid;
 	uint8_t dir;
 	uint8_t ase_id;
+	struct l_dbus_proxy *ep_proxy;
 };
 
 struct btp_agent {
@@ -58,6 +63,8 @@ struct btp_device *find_device_by_address(struct btp_adapter *adapter,
 struct btp_device *find_device_by_path(const char *path);
 struct btp_adapter *find_adapter_by_device(struct btp_device *device);
 struct btp_device *find_device_by_proxy(struct l_dbus_proxy *proxy);
+struct btp_device *find_device_by_service_path(const char *path);
+struct btp_ase *find_ase_by_uuid(struct btp_device *device, char *uuid);
 
 struct btp_agent *get_agent(void);
 
diff --git a/client/btpclient/core.c b/client/btpclient/core.c
index f5d64904f..8ab3f3e34 100644
--- a/client/btpclient/core.c
+++ b/client/btpclient/core.c
@@ -13,10 +13,12 @@
 #include "bluetooth/uuid.h"
 #include "src/shared/btp.h"
 #include "btpclient.h"
+#include "ascs.h"
 #include "bap.h"
 #include "core.h"
 #include "gap.h"
 #include "gatt.h"
+#include "vendor.h"
 
 static struct btp *btp;
 static struct l_dbus *dbus;
@@ -61,7 +63,17 @@ failed:
 static void btp_core_read_services(uint8_t index, const void *param,
 					uint16_t length, void *user_data)
 {
-	uint8_t services = 0;
+	const uint8_t supported_services[] = {
+		BTP_CORE_SERVICE,
+		BTP_GAP_SERVICE,
+		BTP_GATT_SERVICE,
+		BTP_ASCS_SERVICE,
+		BTP_BAP_SERVICE,
+		BTP_VENDOR_SERVICE,
+	};
+	uint8_t *services = NULL;
+	size_t services_len = 0;
+	size_t i;
 
 	if (index != BTP_INDEX_NON_CONTROLLER) {
 		btp_send_error(btp, BTP_CORE_SERVICE, index,
@@ -69,11 +81,22 @@ static void btp_core_read_services(uint8_t index, const void *param,
 		return;
 	}
 
-	services |= (1 << BTP_CORE_SERVICE);
-	services |= (1 << BTP_GAP_SERVICE);
+	for (i = 0; i < L_ARRAY_SIZE(supported_services); i++) {
+		if (!add_supported_command(&services, &services_len,
+						supported_services[i]))
+			goto failed;
+	}
 
 	btp_send(btp, BTP_CORE_SERVICE, BTP_OP_CORE_READ_SUPPORTED_SERVICES,
-			BTP_INDEX_NON_CONTROLLER, sizeof(services), &services);
+			BTP_INDEX_NON_CONTROLLER, services_len, services);
+
+	l_free(services);
+
+	return;
+
+failed:
+	l_free(services);
+	btp_send_error(btp, BTP_CORE_SERVICE, index, BTP_ERROR_FAIL);
 }
 
 static void btp_core_register(uint8_t index, const void *param,
@@ -107,6 +130,14 @@ static void btp_core_register(uint8_t index, const void *param,
 		if (!gatt_register_service(btp, dbus, client))
 			goto failed;
 
+		break;
+	case BTP_ASCS_SERVICE:
+		if (ascs_is_service_registered())
+			goto failed;
+
+		if (!ascs_register_service(btp, dbus, client))
+			goto failed;
+
 		break;
 	case BTP_BAP_SERVICE:
 		if (bap_is_service_registered())
@@ -115,6 +146,14 @@ static void btp_core_register(uint8_t index, const void *param,
 		if (!bap_register_service(btp, dbus, client))
 			goto failed;
 
+		break;
+	case BTP_VENDOR_SERVICE:
+		if (vendor_is_service_registered())
+			goto failed;
+
+		if (!vendor_register_service(btp, dbus, client))
+			goto failed;
+
 		break;
 	case BTP_L2CAP_SERVICE:
 	case BTP_MESH_NODE_SERVICE:
@@ -158,12 +197,24 @@ static void btp_core_unregister(uint8_t index, const void *param,
 
 		gatt_unregister_service(btp);
 		break;
+	case BTP_ASCS_SERVICE:
+		if (!ascs_is_service_registered())
+			goto failed;
+
+		ascs_unregister_service(btp);
+		break;
 	case BTP_BAP_SERVICE:
 		if (!bap_is_service_registered())
 			goto failed;
 
 		bap_unregister_service(btp);
 		break;
+	case BTP_VENDOR_SERVICE:
+		if (!vendor_is_service_registered())
+			goto failed;
+
+		vendor_unregister_service(btp);
+		break;
 	case BTP_L2CAP_SERVICE:
 	case BTP_MESH_NODE_SERVICE:
 	case BTP_CORE_SERVICE:
diff --git a/client/btpclient/gap.c b/client/btpclient/gap.c
index 6aeaf5359..f81141903 100644
--- a/client/btpclient/gap.c
+++ b/client/btpclient/gap.c
@@ -2684,9 +2684,6 @@ void gap_property_changed(struct l_dbus_proxy *proxy, const char *name,
 				struct l_dbus_message *msg, void *user_data)
 {
 	const char *interface = l_dbus_proxy_get_interface(proxy);
-	const char *path = l_dbus_proxy_get_path(proxy);
-
-	l_info("property_changed %s %s %s", name, path, interface);
 
 	if (!strcmp(interface, "org.bluez.Adapter1")) {
 		struct btp_adapter *adapter = find_adapter_by_proxy(proxy);
diff --git a/client/btpclient/vendor.c b/client/btpclient/vendor.c
new file mode 100644
index 000000000..30ce5649c
--- /dev/null
+++ b/client/btpclient/vendor.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2026  Collabora Ltd.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ell/ell.h>
+
+#include "bluetooth/bluetooth.h"
+#include "bluetooth/uuid.h"
+#include "src/shared/btp.h"
+#include "btpclient.h"
+#include "ascs.h"
+#include "vendor.h"
+
+static struct btp *btp;
+static bool vendor_service_registered;
+
+static void btp_vendor_read_commands(uint8_t index, const void *param,
+					uint16_t length, void *user_data)
+{
+	const uint8_t supported_commands[] = {
+		BTP_OP_VENDOR_READ_SUPPORTED_COMMANDS,
+		BTP_OP_VENDOR_ASCS_SETUP,
+	};
+	uint8_t *commands = NULL;
+	size_t commands_len = 0;
+	size_t i;
+
+	if (index != BTP_INDEX_NON_CONTROLLER) {
+		btp_send_error(btp, BTP_VENDOR_SERVICE, index,
+						BTP_ERROR_INVALID_INDEX);
+		return;
+	}
+
+	for (i = 0; i < L_ARRAY_SIZE(supported_commands); i++) {
+		if (!add_supported_command(&commands, &commands_len,
+						supported_commands[i]))
+			goto failed;
+	}
+
+	btp_send(btp, BTP_VENDOR_SERVICE, BTP_OP_VENDOR_READ_SUPPORTED_COMMANDS,
+			BTP_INDEX_NON_CONTROLLER, commands_len, commands);
+
+	l_free(commands);
+
+	return;
+
+failed:
+	l_free(commands);
+	btp_send_error(btp, BTP_VENDOR_SERVICE, index, BTP_ERROR_FAIL);
+}
+
+static void btp_vendor_ascs_setup(uint8_t index, const void *param,
+					uint16_t length, void *user_data)
+{
+	struct btp_adapter *adapter = find_adapter_by_index(index);
+	const struct btp_vendor_ascs_setup_cp *cp = param;
+
+	adapter->target_latency = cp->target_latency;
+
+	ascs_setup(adapter);
+
+	btp_send(btp, BTP_VENDOR_SERVICE, BTP_OP_VENDOR_ASCS_SETUP, index, 0,
+									NULL);
+}
+
+bool vendor_register_service(struct btp *btp_, struct l_dbus *dbus_,
+					struct l_dbus_client *client)
+{
+	btp = btp_;
+
+	btp_register(btp, BTP_VENDOR_SERVICE,
+					BTP_OP_VENDOR_READ_SUPPORTED_COMMANDS,
+					btp_vendor_read_commands, NULL, NULL);
+
+	btp_register(btp, BTP_VENDOR_SERVICE,
+					BTP_OP_VENDOR_ASCS_SETUP,
+					btp_vendor_ascs_setup, NULL, NULL);
+
+	vendor_service_registered = true;
+
+	return true;
+}
+
+void vendor_unregister_service(struct btp *btp)
+{
+	btp_unregister_service(btp, BTP_VENDOR_SERVICE);
+	vendor_service_registered = false;
+}
+
+bool vendor_is_service_registered(void)
+{
+	return vendor_service_registered;
+}
diff --git a/client/btpclient/vendor.h b/client/btpclient/vendor.h
new file mode 100644
index 000000000..0eb91f91e
--- /dev/null
+++ b/client/btpclient/vendor.h
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2026  Collabora Ltd.
+ *
+ */
+
+bool vendor_register_service(struct btp *btp_, struct l_dbus *dbus_,
+					struct l_dbus_client *client);
+void vendor_unregister_service(struct btp *btp);
+bool vendor_is_service_registered(void);
diff --git a/src/shared/btp.h b/src/shared/btp.h
index 100308de1..65a8c8b6a 100644
--- a/src/shared/btp.h
+++ b/src/shared/btp.h
@@ -26,7 +26,9 @@
 #define BTP_GATT_SERVICE	2
 #define BTP_L2CAP_SERVICE	3
 #define BTP_MESH_NODE_SERVICE	4
+#define BTP_ASCS_SERVICE	13
 #define BTP_BAP_SERVICE		14
+#define BTP_VENDOR_SERVICE	255
 
 struct btp_hdr {
 	uint8_t service;
@@ -425,6 +427,40 @@ struct btp_gatt_write_rp {
 	uint8_t att_response;
 } __packed;
 
+#define BTP_OP_ASCS_READ_SUPPORTED_COMMANDS	0x01
+
+#define BTP_OP_ASCS_CONFIGURE_CODEC		0x02
+struct btp_ascs_configure_codec_cp {
+	uint8_t address_type;
+	bdaddr_t address;
+	uint8_t ase_id;
+	uint8_t coding_format;
+	uint16_t vid;
+	uint16_t cid;
+	uint8_t cc_ltvs_len;
+	uint8_t cc_ltvs[];
+} __packed;
+
+#define BTP_EV_ASCS_OPERATION_COMPLETED		0x80
+struct btp_ascs_operation_completed_ev {
+	uint8_t address_type;
+	bdaddr_t address;
+	uint8_t ase_id;
+	uint8_t opcode;
+	uint8_t status;
+
+	/* RFU */
+	uint8_t flags;
+} __packed;
+
+#define BTP_EV_ASCS_ASE_STATE_CHANGED		0x82
+struct btp_ascs_ase_state_changed_ev {
+	uint8_t address_type;
+	bdaddr_t address;
+	uint8_t ase_id;
+	uint8_t state;
+} __packed;
+
 #define BTP_BAP_DIR_SINK			0x01
 #define BTP_BAP_DIR_SOURCE			0x02
 
@@ -463,6 +499,13 @@ struct btp_bap_ase_found_ev {
 	uint8_t ase_id;
 } __packed;
 
+#define BTP_OP_VENDOR_READ_SUPPORTED_COMMANDS	0x01
+
+#define BTP_OP_VENDOR_ASCS_SETUP		0x02
+struct btp_vendor_ascs_setup_cp {
+	uint8_t target_latency;
+} __packed;
+
 struct btp;
 
 typedef void (*btp_destroy_func_t)(void *user_data);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH BlueZ 4/8] client/btpclient: Add BTP_OP_PACS_SET_LOCATION support
  2026-09-04 13:51 [PATCH BlueZ 0/8] client/btpclient: Add BAP/ASCS/PACS support for auto-pts BAP tests Frédéric Danis
                   ` (2 preceding siblings ...)
  2026-09-04 13:51 ` [PATCH BlueZ 3/8] client/btpclient: Add ASCS BTP support Frédéric Danis
@ 2026-09-04 13:51 ` Frédéric Danis
  2026-09-04 13:51 ` [PATCH BlueZ 5/8] client/btpclient: Add BTP_OP_BAP_SEND support Frédéric Danis
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Frédéric Danis @ 2026-09-04 13:51 UTC (permalink / raw)
  To: linux-bluetooth

This allows to store the expected location for a local endpoint.
---
 Makefile.tools               |   1 +
 client/btpclient/btpclient.h |   3 +
 client/btpclient/core.c      |  16 ++++++
 client/btpclient/pacs.c      | 107 +++++++++++++++++++++++++++++++++++
 client/btpclient/pacs.h      |  13 +++++
 src/shared/btp.h             |   9 +++
 6 files changed, 149 insertions(+)
 create mode 100644 client/btpclient/pacs.c
 create mode 100644 client/btpclient/pacs.h

diff --git a/Makefile.tools b/Makefile.tools
index 3fa943331..0d9eb1a7e 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -573,6 +573,7 @@ client_btpclient_btpclient_SOURCES = client/btpclient/btpclient.c \
 				client/btpclient/core.c client/btpclient/core.h \
 				client/btpclient/gap.c client/btpclient/gap.h \
 				client/btpclient/gatt.c client/btpclient/gatt.h \
+				client/btpclient/pacs.c client/btpclient/pacs.h \
 				client/btpclient/vendor.c client/btpclient/vendor.h
 client_btpclient_btpclient_LDADD = lib/libbluetooth-internal.la \
 				src/libshared-ell.la $(ell_ldadd)
diff --git a/client/btpclient/btpclient.h b/client/btpclient/btpclient.h
index 0d81405d3..2ba612ade 100644
--- a/client/btpclient/btpclient.h
+++ b/client/btpclient/btpclient.h
@@ -19,6 +19,9 @@ struct btp_adapter {
 	uint32_t default_settings;
 	struct l_queue *devices;
 
+	uint32_t sink_locations;
+	uint32_t source_locations;
+
 	uint8_t target_latency;
 };
 
diff --git a/client/btpclient/core.c b/client/btpclient/core.c
index 8ab3f3e34..bbbc7edfd 100644
--- a/client/btpclient/core.c
+++ b/client/btpclient/core.c
@@ -18,6 +18,7 @@
 #include "core.h"
 #include "gap.h"
 #include "gatt.h"
+#include "pacs.h"
 #include "vendor.h"
 
 static struct btp *btp;
@@ -67,6 +68,7 @@ static void btp_core_read_services(uint8_t index, const void *param,
 		BTP_CORE_SERVICE,
 		BTP_GAP_SERVICE,
 		BTP_GATT_SERVICE,
+		BTP_PACS_SERVICE,
 		BTP_ASCS_SERVICE,
 		BTP_BAP_SERVICE,
 		BTP_VENDOR_SERVICE,
@@ -130,6 +132,14 @@ static void btp_core_register(uint8_t index, const void *param,
 		if (!gatt_register_service(btp, dbus, client))
 			goto failed;
 
+		break;
+	case BTP_PACS_SERVICE:
+		if (pacs_is_service_registered())
+			goto failed;
+
+		if (!pacs_register_service(btp, dbus, client))
+			goto failed;
+
 		break;
 	case BTP_ASCS_SERVICE:
 		if (ascs_is_service_registered())
@@ -197,6 +207,12 @@ static void btp_core_unregister(uint8_t index, const void *param,
 
 		gatt_unregister_service(btp);
 		break;
+	case BTP_PACS_SERVICE:
+		if (!pacs_is_service_registered())
+			goto failed;
+
+		ascs_unregister_service(btp);
+		break;
 	case BTP_ASCS_SERVICE:
 		if (!ascs_is_service_registered())
 			goto failed;
diff --git a/client/btpclient/pacs.c b/client/btpclient/pacs.c
new file mode 100644
index 000000000..6277812a0
--- /dev/null
+++ b/client/btpclient/pacs.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2026  Collabora Ltd.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ell/ell.h>
+
+#include "bluetooth/bluetooth.h"
+#include "bluetooth/uuid.h"
+#include "src/shared/btp.h"
+#include "btpclient.h"
+#include "pacs.h"
+
+static struct btp *btp;
+static bool pacs_service_registered;
+
+static void btp_pacs_read_commands(uint8_t index, const void *param,
+					uint16_t length, void *user_data)
+{
+	const uint8_t supported_commands[] = {
+		BTP_OP_PACS_READ_SUPPORTED_COMMANDS,
+		BTP_OP_PACS_SET_LOCATION,
+	};
+	uint8_t *commands = NULL;
+	size_t commands_len = 0;
+	size_t i;
+
+	if (index != BTP_INDEX_NON_CONTROLLER) {
+		btp_send_error(btp, BTP_PACS_SERVICE, index,
+						BTP_ERROR_INVALID_INDEX);
+		return;
+	}
+
+	for (i = 0; i < L_ARRAY_SIZE(supported_commands); i++) {
+		if (!add_supported_command(&commands, &commands_len,
+						supported_commands[i]))
+			goto failed;
+	}
+
+	btp_send(btp, BTP_PACS_SERVICE, BTP_OP_PACS_READ_SUPPORTED_COMMANDS,
+			BTP_INDEX_NON_CONTROLLER, commands_len, commands);
+
+	l_free(commands);
+
+	return;
+
+failed:
+	l_free(commands);
+	btp_send_error(btp, BTP_PACS_SERVICE, index, BTP_ERROR_FAIL);
+}
+
+static void btp_pacs_set_location(uint8_t index, const void *param,
+					uint16_t length, void *user_data)
+{
+	struct btp_adapter *adapter = find_adapter_by_index(index);
+	const struct btp_pacs_set_location_cp *cp = param;
+
+	switch (cp->dir) {
+	case BTP_BAP_DIR_SINK:
+		adapter->sink_locations = cp->location;
+		break;
+	case BTP_BAP_DIR_SOURCE:
+		adapter->source_locations = cp->location;
+		break;
+	default:
+		btp_send_error(btp, BTP_PACS_SERVICE, index, BTP_ERROR_FAIL);
+		return;
+	}
+
+	btp_send(btp, BTP_PACS_SERVICE, BTP_OP_PACS_SET_LOCATION, index, 0,
+									NULL);
+}
+
+bool pacs_register_service(struct btp *btp_, struct l_dbus *dbus_,
+					struct l_dbus_client *client)
+{
+	btp = btp_;
+
+	btp_register(btp, BTP_PACS_SERVICE, BTP_OP_PACS_READ_SUPPORTED_COMMANDS,
+					btp_pacs_read_commands, NULL, NULL);
+
+	btp_register(btp, BTP_PACS_SERVICE, BTP_OP_PACS_SET_LOCATION,
+					btp_pacs_set_location, NULL, NULL);
+
+	pacs_service_registered = true;
+
+	return true;
+}
+
+void pacs_unregister_service(struct btp *btp)
+{
+	btp_unregister_service(btp, BTP_PACS_SERVICE);
+	pacs_service_registered = false;
+}
+
+bool pacs_is_service_registered(void)
+{
+	return pacs_service_registered;
+}
diff --git a/client/btpclient/pacs.h b/client/btpclient/pacs.h
new file mode 100644
index 000000000..a1579a8d6
--- /dev/null
+++ b/client/btpclient/pacs.h
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2026  Collabora Ltd.
+ *
+ */
+
+bool pacs_register_service(struct btp *btp_, struct l_dbus *dbus_,
+					struct l_dbus_client *client);
+void pacs_unregister_service(struct btp *btp);
+bool pacs_is_service_registered(void);
diff --git a/src/shared/btp.h b/src/shared/btp.h
index 65a8c8b6a..8ad57f48f 100644
--- a/src/shared/btp.h
+++ b/src/shared/btp.h
@@ -26,6 +26,7 @@
 #define BTP_GATT_SERVICE	2
 #define BTP_L2CAP_SERVICE	3
 #define BTP_MESH_NODE_SERVICE	4
+#define BTP_PACS_SERVICE	12
 #define BTP_ASCS_SERVICE	13
 #define BTP_BAP_SERVICE		14
 #define BTP_VENDOR_SERVICE	255
@@ -427,6 +428,14 @@ struct btp_gatt_write_rp {
 	uint8_t att_response;
 } __packed;
 
+#define BTP_OP_PACS_READ_SUPPORTED_COMMANDS	0x01
+
+#define BTP_OP_PACS_SET_LOCATION		0x03
+struct btp_pacs_set_location_cp {
+	uint8_t dir;
+	uint32_t location;
+} __packed;
+
 #define BTP_OP_ASCS_READ_SUPPORTED_COMMANDS	0x01
 
 #define BTP_OP_ASCS_CONFIGURE_CODEC		0x02
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH BlueZ 5/8] client/btpclient: Add BTP_OP_BAP_SEND support
  2026-09-04 13:51 [PATCH BlueZ 0/8] client/btpclient: Add BAP/ASCS/PACS support for auto-pts BAP tests Frédéric Danis
                   ` (3 preceding siblings ...)
  2026-09-04 13:51 ` [PATCH BlueZ 4/8] client/btpclient: Add BTP_OP_PACS_SET_LOCATION support Frédéric Danis
@ 2026-09-04 13:51 ` Frédéric Danis
  2026-09-04 13:51 ` [PATCH BlueZ 6/8] client/btpclient: Add ASCS support for BAP/UCL/STR/* tests Frédéric Danis
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Frédéric Danis @ 2026-09-04 13:51 UTC (permalink / raw)
  To: linux-bluetooth

---
 client/btpclient/bap.c | 41 +++++++++++++++++++++++++++++++++++++++++
 src/shared/btp.h       | 13 +++++++++++++
 2 files changed, 54 insertions(+)

diff --git a/client/btpclient/bap.c b/client/btpclient/bap.c
index d30d931f1..f7bee9831 100644
--- a/client/btpclient/bap.c
+++ b/client/btpclient/bap.c
@@ -31,6 +31,7 @@ static void btp_bap_read_commands(uint8_t index, const void *param,
 	const uint8_t supported_commands[] = {
 		BTP_OP_BAP_READ_SUPPORTED_COMMANDS,
 		BTP_OP_BAP_DISCOVER,
+		BTP_OP_BAP_SEND,
 	};
 	uint8_t *commands = NULL;
 	size_t commands_len = 0;
@@ -104,6 +105,43 @@ failed:
 	btp_send_error(btp, BTP_BAP_SERVICE, index, status);
 }
 
+static void btp_bap_send(uint8_t index, const void *param, uint16_t length,
+								void *user_data)
+{
+	const struct btp_bap_send_cp *cp = param;
+	struct btp_adapter *adapter = find_adapter_by_index(index);
+	uint8_t status = BTP_ERROR_FAIL;
+	struct btp_device *dev;
+	struct btp_ase *ase;
+	ssize_t bytes_written;
+	struct btp_bap_send_rp rp;
+
+	if (!adapter) {
+		status = BTP_ERROR_INVALID_INDEX;
+		goto failed;
+	}
+
+	dev = find_device_by_address(adapter, &cp->address, cp->address_type);
+	ase = find_ase_by_dir(dev, BTP_BAP_DIR_SINK);
+	if (!ase || !ase->io)
+		goto failed;
+
+	bytes_written = write(l_io_get_fd(ase->io), cp->data,
+		cp->data_len <= ase->tx_mtu ? cp->data_len : ase->tx_mtu);
+	if (bytes_written < 0) {
+		l_error("Failed to write (%d)", errno);
+		goto failed;
+	}
+	rp.data_len = bytes_written;
+
+	btp_send(btp, BTP_BAP_SERVICE, BTP_OP_BAP_SEND, index, sizeof(rp), &rp);
+
+	return;
+
+failed:
+	btp_send_error(btp, BTP_BAP_SERVICE, index, status);
+}
+
 static void bap_charac_read_setup(struct l_dbus_message *message,
 							void *user_data)
 {
@@ -304,6 +342,9 @@ bool bap_register_service(struct btp *btp_, struct l_dbus *dbus_,
 	btp_register(btp, BTP_BAP_SERVICE, BTP_OP_BAP_DISCOVER,
 					btp_bap_discover, NULL, NULL);
 
+	btp_register(btp, BTP_BAP_SERVICE, BTP_OP_BAP_SEND,
+					btp_bap_send, NULL, NULL);
+
 	bap_service_registered = true;
 
 	return true;
diff --git a/src/shared/btp.h b/src/shared/btp.h
index 8ad57f48f..fe93158aa 100644
--- a/src/shared/btp.h
+++ b/src/shared/btp.h
@@ -481,6 +481,19 @@ struct btp_bap_discover_cp {
 	bdaddr_t address;
 } __packed;
 
+#define BTP_OP_BAP_SEND				0x03
+struct btp_bap_send_cp {
+	uint8_t address_type;
+	bdaddr_t address;
+	uint8_t ase_id;
+	uint8_t data_len;
+	uint8_t data[];
+} __packed;
+
+struct btp_bap_send_rp {
+	uint8_t data_len;
+} __packed;
+
 #define BTP_EV_BAP_DISCOVERY_COMPLETED		0x80
 struct btp_bap_discovery_completed_ev {
 	uint8_t address_type;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH BlueZ 6/8] client/btpclient: Add ASCS support for BAP/UCL/STR/* tests
  2026-09-04 13:51 [PATCH BlueZ 0/8] client/btpclient: Add BAP/ASCS/PACS support for auto-pts BAP tests Frédéric Danis
                   ` (4 preceding siblings ...)
  2026-09-04 13:51 ` [PATCH BlueZ 5/8] client/btpclient: Add BTP_OP_BAP_SEND support Frédéric Danis
@ 2026-09-04 13:51 ` Frédéric Danis
  2026-09-04 13:51 ` [PATCH BlueZ 7/8] client/btpclient: Defer CIG/CIS assignment based on Client/Server role Frédéric Danis
  2026-09-04 13:51 ` [PATCH BlueZ 8/8] client/btpclient: Add stream auto acquire for BAP/USR/STR/* tests Frédéric Danis
  7 siblings, 0 replies; 10+ messages in thread
From: Frédéric Danis @ 2026-09-04 13:51 UTC (permalink / raw)
  To: linux-bluetooth

---
 client/btpclient/ascs.c      | 696 ++++++++++++++++++++++++++++++++---
 client/btpclient/ascs.h      |   3 +
 client/btpclient/bap.c       | 107 +++++-
 client/btpclient/btpclient.c |  35 ++
 client/btpclient/btpclient.h |  16 +
 client/btpclient/vendor.c    |   1 +
 src/shared/btp.h             |  68 ++++
 7 files changed, 872 insertions(+), 54 deletions(-)

diff --git a/client/btpclient/ascs.c b/client/btpclient/ascs.c
index 063fc8306..ca4c6db07 100644
--- a/client/btpclient/ascs.c
+++ b/client/btpclient/ascs.c
@@ -131,6 +131,11 @@ static void btp_ascs_read_commands(uint8_t index, const void *param,
 	const uint8_t supported_commands[] = {
 		BTP_OP_ASCS_READ_SUPPORTED_COMMANDS,
 		BTP_OP_ASCS_CONFIGURE_CODEC,
+		BTP_OP_ASCS_CONFIGURE_QOS,
+		BTP_OP_ASCS_ENABLE,
+		BTP_OP_ASCS_RECEIVER_START_READY,
+		BTP_OP_ASCS_ADD_ASE_TO_CIS,
+		BTP_OP_ASCS_PRECONFIGURE_QOS,
 	};
 	uint8_t *commands = NULL;
 	size_t commands_len = 0;
@@ -362,6 +367,394 @@ failed:
 	btp_send_error(btp, BTP_ASCS_SERVICE, index, status);
 }
 
+static bool match_ase(const void *entry, const void *data)
+{
+	const struct btp_ase *ase = entry;
+	uint8_t id = L_PTR_TO_UINT(data);
+
+	return ase->ase_id == id;
+}
+
+struct configure_qos_complete_data {
+	struct btp_adapter *adapter;
+	const struct btp_ascs_configure_qos_cp *cp;
+};
+
+static void configure_qos_complete_cb(void *data, void *user_data)
+{
+	struct btp_ase *ase = data;
+	struct configure_qos_complete_data *param = user_data;
+	const struct btp_ascs_configure_qos_cp *cp = param->cp;
+	struct btp_ascs_operation_completed_ev ev;
+
+	if ((cp->ase_id && cp->ase_id != ase->ase_id) ||
+					(cp->cig_id != ase->cig_id))
+		return;
+
+	memcpy(&ev.address, &cp->address, sizeof(ev.address));
+	ev.address_type = cp->address_type;
+	ev.ase_id = ase->ase_id;
+	ev.opcode = BTP_OP_ASCS_CONFIGURE_QOS;
+	ev.status = 0;
+	ev.flags = 0;
+
+	btp_send(btp, BTP_ASCS_SERVICE, BTP_EV_ASCS_OPERATION_COMPLETED,
+			param->adapter->index, sizeof(ev), &ev);
+
+	send_state_changed(param->adapter->index,  &cp->address,
+					cp->address_type, ase->ase_id,
+					BT_BAP_STREAM_STATE_QOS);
+}
+
+static void btp_ascs_configure_qos(uint8_t index, const void *param,
+					uint16_t length, void *user_data)
+{
+	struct btp_adapter *adapter = find_adapter_by_index(index);
+	struct btp_device *dev;
+	const struct btp_ascs_configure_qos_cp *cp = param;
+	struct configure_qos_complete_data data;
+	uint8_t status = BTP_ERROR_FAIL;
+
+	if (!adapter) {
+		status = BTP_ERROR_INVALID_INDEX;
+		goto failed;
+	}
+
+	dev = find_device_by_address(adapter, &cp->address, cp->address_type);
+	if (!dev)
+		goto failed;
+
+	btp_send(btp, BTP_ASCS_SERVICE, BTP_OP_ASCS_CONFIGURE_QOS, index, 0,
+									NULL);
+
+	/* Send BTP_EV_ASCS_OPERATION_COMPLETED for each of the ASEs */
+	data.adapter = adapter;
+	data.cp = cp;
+	l_queue_foreach(dev->ases, configure_qos_complete_cb, &data);
+
+	return;
+
+failed:
+	btp_send_error(btp, BTP_ASCS_SERVICE, index, status);
+}
+
+static bool read_cb(struct l_io *io, void *user_data)
+{
+	struct btp_ase *ase = user_data;
+	struct btp_device *device = ase->device;
+	struct btp_adapter *adapter = find_adapter_by_device(device);
+	struct btp_bap_stream_received_ev *ev;
+	ssize_t bytes_read;
+
+	ev = l_malloc(sizeof(struct btp_bap_stream_received_ev) + ase->rx_mtu);
+	memcpy(&ev->address, &device->address, sizeof(ev->address));
+	ev->address_type = device->address_type;
+	ev->ase_id = ase->ase_id;
+
+	bytes_read = read(l_io_get_fd(io), ev->data, ase->rx_mtu);
+	if (bytes_read < 0) {
+		l_info("Invalid read length: %ld", bytes_read);
+		l_free(ev);
+		return false;
+	}
+	ev->data_len = bytes_read;
+
+	btp_send(btp, BTP_BAP_SERVICE, BTP_EV_BAP_STREAM_RECEIVED,
+				adapter->index, sizeof(*ev) + bytes_read, ev);
+
+	l_free(ev);
+
+	return false;
+}
+
+static bool match_ase_path(const void *entry, const void *data)
+{
+	const struct btp_ase *ase = entry;
+	const char *path = data;
+	const char *ase_path = l_dbus_proxy_get_path(ase->transport_proxy);
+
+	if (!path || !ase_path)
+		return false;
+
+	return !strcmp(path, ase_path);
+}
+
+static void ase_change_state(struct btp_ase *ase,
+					enum ase_transport_state state)
+{
+	struct btp_device *device = ase->device;
+	struct btp_adapter *adapter = find_adapter_by_device(device);
+	struct l_dbus_message_iter iter;
+	const char *path;
+
+	ase->transport_state = state;
+	if (state == ASE_TRANSPORT_ACQUIRED)
+		send_state_changed(adapter->index, &device->address,
+						device->address_type,
+						ase->ase_id,
+						BT_BAP_STREAM_STATE_ENABLING);
+
+	if (adapter->desync)
+		return;
+
+	/* Update linked endpoints */
+	if (!l_dbus_proxy_get_property(ase->transport_proxy, "Links", "ao",
+						&iter))
+		return;
+
+	while (l_dbus_message_iter_next_entry(&iter, &path)) {
+		struct btp_ase *l;
+
+		l = l_queue_find(device->ases, match_ase_path, path);
+		if (!l)
+			continue;
+
+		l->transport_state = state;
+		if (state != ASE_TRANSPORT_ACQUIRED)
+			continue;
+
+		send_state_changed(adapter->index, &l->device->address,
+						l->device->address_type,
+						l->ase_id,
+						BT_BAP_STREAM_STATE_ENABLING);
+	}
+}
+
+static void ascs_acquire_reply(struct l_dbus_proxy *proxy,
+						struct l_dbus_message *result,
+						void *user_data)
+{
+	struct btp_ase *ase = user_data;
+	struct btp_device *device = ase->device;
+	struct btp_adapter *adapter = find_adapter_by_device(device);
+	uint8_t status = BTP_ERROR_FAIL;
+	int sk;
+	uint16_t rx, tx;
+	struct l_dbus_message_iter iter;
+
+	if (l_dbus_message_is_error(result)) {
+		const char *name, *desc;
+
+		l_dbus_message_get_error(result, &name, &desc);
+		l_error("Failed to acquire endpoint (%s), %s", name, desc);
+
+		goto failed;
+	}
+
+	if (!l_dbus_message_get_arguments(result, "hqq", &sk, &rx, &tx))
+		goto failed;
+
+	ase_change_state(ase, ASE_TRANSPORT_ACQUIRED);
+	ase->rx_mtu = rx;
+	ase->tx_mtu = tx;
+	ase->io = l_io_new(sk);
+	if (!ase->io) {
+		close(sk);
+		goto failed;
+	}
+
+	if (ase->dir == BTP_BAP_DIR_SOURCE)
+		l_io_set_read_handler(ase->io, read_cb, ase, NULL);
+
+	/* Update linked endpoints */
+	if (l_dbus_proxy_get_property(ase->transport_proxy, "Links", "ao",
+						&iter)) {
+		const char *path;
+
+		while (l_dbus_message_iter_next_entry(&iter, &path)) {
+			struct btp_ase *l;
+
+			l = l_queue_find(device->ases, match_ase_path, path);
+			if (l) {
+				l->rx_mtu = rx;
+				l->tx_mtu = tx;
+				if (l->dir == BTP_BAP_DIR_SOURCE)
+					l_io_set_read_handler(ase->io,
+							read_cb, l, NULL);
+				break;
+			}
+		}
+	}
+
+	return;
+
+failed:
+	btp_send_error(btp, BTP_ASCS_SERVICE, adapter->index, status);
+}
+
+static void btp_ascs_enable(uint8_t index, const void *param,
+					uint16_t length, void *user_data)
+{
+	struct btp_adapter *adapter = find_adapter_by_index(index);
+	const struct btp_ascs_enable_cp *cp = param;
+	struct btp_ascs_operation_completed_ev ev;
+	struct btp_device *dev;
+	struct btp_ase *ase;
+	uint8_t status = BTP_ERROR_FAIL;
+
+	if (!adapter) {
+		status = BTP_ERROR_INVALID_INDEX;
+		goto failed;
+	}
+
+	btp_send(btp, BTP_ASCS_SERVICE, BTP_OP_ASCS_ENABLE, index, 0, NULL);
+
+	memcpy(&ev.address, &cp->address, sizeof(ev.address));
+	ev.address_type = cp->address_type;
+	ev.ase_id = cp->ase_id;
+	ev.opcode = BTP_OP_ASCS_ENABLE;
+	ev.status = 0;
+	ev.flags = 0;
+
+	btp_send(btp, BTP_ASCS_SERVICE, BTP_EV_ASCS_OPERATION_COMPLETED,
+			adapter->index, sizeof(ev), &ev);
+
+	dev = find_device_by_address(adapter, &cp->address, cp->address_type);
+	if (!dev)
+		goto failed;
+
+	ase = l_queue_find(dev->ases, match_ase, L_UINT_TO_PTR(cp->ase_id));
+	if (!ase)
+		goto failed;
+
+	/* In desync mode, Acquire the transport straight away and defer
+	 * sending the ENABLING state notification until Acquire completes;
+	 * otherwise notify ENABLING immediately.
+	 */
+	if (adapter->desync) {
+		l_dbus_proxy_method_call(ase->transport_proxy, "Acquire",
+					NULL, ascs_acquire_reply, ase, NULL);
+		ase_change_state(ase, ASE_TRANSPORT_ACQUIRING);
+	} else {
+		send_state_changed(adapter->index, &cp->address,
+						cp->address_type,
+						ase->ase_id,
+						BT_BAP_STREAM_STATE_ENABLING);
+	}
+
+	return;
+
+failed:
+	btp_send_error(btp, BTP_ASCS_SERVICE, index, status);
+}
+
+static void btp_ascs_receiver_start_ready(uint8_t index, const void *param,
+					uint16_t length, void *user_data)
+{
+	struct btp_adapter *adapter = find_adapter_by_index(index);
+	const struct btp_ascs_enable_cp *cp = param;
+	uint8_t status = BTP_ERROR_FAIL;
+
+	if (!adapter) {
+		status = BTP_ERROR_INVALID_INDEX;
+		goto failed;
+	}
+
+	/* Outside desync mode, Acquire the transport on Receiver Start
+	 * Ready; in desync mode it was already Acquired when Enable was
+	 * received.
+	 */
+	if (!adapter->desync) {
+		struct btp_device *dev;
+		struct btp_ase *ase;
+
+		dev = find_device_by_address(adapter, &cp->address,
+							cp->address_type);
+		if (!dev)
+			goto failed;
+
+		ase = l_queue_find(dev->ases, match_ase,
+						L_UINT_TO_PTR(cp->ase_id));
+		if (!ase)
+			goto failed;
+
+		l_dbus_proxy_method_call(ase->transport_proxy, "Acquire",
+					NULL, ascs_acquire_reply, ase, NULL);
+		ase_change_state(ase, ASE_TRANSPORT_ACQUIRING);
+
+		if (adapter->desync)
+			return;
+
+		send_state_changed(adapter->index, &cp->address,
+						cp->address_type,
+						ase->ase_id,
+						BT_BAP_STREAM_STATE_ENABLING);
+	}
+
+	btp_send(btp, BTP_ASCS_SERVICE, BTP_OP_ASCS_RECEIVER_START_READY,
+							index, 0, NULL);
+
+	return;
+
+failed:
+	btp_send_error(btp, BTP_ASCS_SERVICE, index, status);
+}
+
+static void btp_ascs_add_ase_to_cis(uint8_t index, const void *param,
+					uint16_t length, void *user_data)
+{
+	struct btp_adapter *adapter = find_adapter_by_index(index);
+	struct btp_device *dev;
+	const struct btp_ascs_add_ase_to_cis_cp *cp = param;
+	struct btp_ase *ase;
+	uint8_t status = BTP_ERROR_FAIL;
+
+	if (!adapter) {
+		status = BTP_ERROR_INVALID_INDEX;
+		goto failed;
+	}
+
+	dev = find_device_by_address(adapter, &cp->address, cp->address_type);
+	if (!dev)
+		goto failed;
+
+	ase = l_queue_find(dev->ases, match_ase, L_UINT_TO_PTR(cp->ase_id));
+	if (!ase)
+		goto failed;
+
+	if (ase->cig_id != cp->cig_id || ase->cis_id != cp->cis_id) {
+		l_error("Invalid CIG/CIS ID, expecting %u/%u, got %u/%u",
+						ase->cig_id, ase->cis_id,
+						cp->cig_id, cp->cis_id);
+		goto failed;
+	}
+
+	btp_send(btp, BTP_ASCS_SERVICE, BTP_OP_ASCS_ADD_ASE_TO_CIS, index, 0,
+									NULL);
+
+	if (ase->transport_proxy) {
+		struct btp_ascs_cis_connected_ev ev;
+
+		memcpy(&ev.address, &dev->address, sizeof(ev.address));
+		ev.address_type = dev->address_type;
+		ev.ase_id = ase->ase_id;
+		ev.cis_id = ase->cis_id;
+
+		btp_send(btp, BTP_ASCS_SERVICE, BTP_EV_ASCS_CIS_CONNECTED,
+				adapter->index, sizeof(ev), &ev);
+	}
+
+	return;
+
+failed:
+	btp_send_error(btp, BTP_ASCS_SERVICE, index, status);
+}
+
+static void btp_ascs_preconfigure_qos(uint8_t index, const void *param,
+					uint16_t length, void *user_data)
+{
+	struct btp_adapter *adapter = find_adapter_by_index(index);
+
+	if (!adapter) {
+		btp_send_error(btp, BTP_ASCS_SERVICE, index,
+						BTP_ERROR_INVALID_INDEX);
+		return;
+	}
+
+	btp_send(btp, BTP_ASCS_SERVICE, BTP_OP_ASCS_PRECONFIGURE_QOS, index, 0,
+									NULL);
+}
+
 bool ascs_setup(struct btp_adapter *adapter)
 {
 	bt_uuid_t uuid;
@@ -626,6 +1019,28 @@ static struct l_dbus_message *get_properties_reply(
 	l_dbus_message_builder_enter_variant(builder, "a{sv}");
 	l_dbus_message_builder_enter_array(builder, "{sv}");
 
+	if (ase->cig_id != BT_ISO_QOS_CIG_UNSET) {
+		l_dbus_message_builder_enter_dict(builder, "sv");
+		l_dbus_message_builder_append_basic(builder, 's',
+						"CIG");
+		l_dbus_message_builder_enter_variant(builder, "y");
+		l_dbus_message_builder_append_basic(builder, 'y',
+						&ase->cig_id);
+		l_dbus_message_builder_leave_variant(builder);
+		l_dbus_message_builder_leave_dict(builder);
+	}
+
+	if (ase->cis_id != BT_ISO_QOS_CIS_UNSET) {
+		l_dbus_message_builder_enter_dict(builder, "sv");
+		l_dbus_message_builder_append_basic(builder, 's',
+						"CIS");
+		l_dbus_message_builder_enter_variant(builder, "y");
+		l_dbus_message_builder_append_basic(builder, 'y',
+						&ase->cis_id);
+		l_dbus_message_builder_leave_variant(builder);
+		l_dbus_message_builder_leave_dict(builder);
+	}
+
 	l_dbus_message_builder_enter_dict(builder, "sv");
 	l_dbus_message_builder_append_basic(builder, 's',
 					"PresentationDelay");
@@ -815,79 +1230,242 @@ static struct l_dbus_message *find_pending_msg(struct l_queue *l,
 	return NULL;
 }
 
+static void set_desync_reply(struct l_dbus_proxy *proxy,
+				struct l_dbus_message *result, void *user_data)
+{
+	struct btp_ase *ase = user_data;
+	struct btp_adapter *adapter;
+	struct btp_ascs_cis_connected_ev ev;
+
+	if (l_dbus_message_is_error(result)) {
+		const char *name, *desc;
+
+		l_dbus_message_get_error(result, &name, &desc);
+		l_error("Failed to desync Links (%s), %s", name, desc);
+		return;
+	}
+
+	adapter = find_adapter_by_device(ase->device);
+	if (!adapter)
+		return;
+
+	memcpy(&ev.address, &ase->device->address, sizeof(ev.address));
+	ev.address_type = ase->device->address_type;
+	ev.ase_id = ase->ase_id;
+	ev.cis_id = ase->cis_id;
+
+	btp_send(btp, BTP_ASCS_SERVICE, BTP_EV_ASCS_CIS_CONNECTED,
+			adapter->index, sizeof(ev), &ev);
+}
+
+void ascs_ase_replied(struct btp_adapter *adapter, struct btp_ase *ase)
+{
+	struct l_dbus_message *msg, *reply;
+
+	msg = find_pending_msg(pending_select_properties, ase);
+	if (msg) {
+		l_queue_remove(pending_select_properties, msg);
+		reply = get_properties_reply(msg, adapter, ase);
+		l_dbus_send(dbus, reply);
+
+		l_dbus_message_unref(msg);
+	}
+}
+
+static bool transport_get_cig_cis(struct l_dbus_proxy *proxy, uint8_t *cig,
+								uint8_t *cis)
+{
+	struct l_dbus_message_iter iter, var;
+	const char *key;
+
+	*cig = BT_ISO_QOS_CIG_UNSET;
+	*cis = BT_ISO_QOS_CIS_UNSET;
+
+	if (!l_dbus_proxy_get_property(proxy, "QoS", "a{sv}", &iter))
+		return false;
+
+	while (l_dbus_message_iter_next_entry(&iter, &key, &var)) {
+		if (!strcmp(key, "CIG")) {
+			if (!l_dbus_message_iter_get_variant(&var, "y", cig))
+				return false;
+		}
+
+		if (!strcmp(key, "CIS")) {
+			if (!l_dbus_message_iter_get_variant(&var, "y", cis))
+				return false;
+		}
+	}
+
+	return true;
+}
+
+void ascs_proxy_added(struct l_dbus_proxy *proxy, void *user_data)
+{
+	char *str, *state;
+	struct btp_device *device = user_data;
+	struct btp_adapter *adapter;
+	uint8_t dir;
+	uint8_t cig = BT_ISO_QOS_CIG_UNSET, cis = BT_ISO_QOS_CIS_UNSET;
+	struct btp_ase *ase;
+
+	adapter = find_adapter_by_device(device);
+	if (!adapter)
+		return;
+
+	if (!l_dbus_proxy_get_property(proxy, "UUID", "s", &str))
+		return;
+
+	if (!l_dbus_proxy_get_property(proxy, "State", "s", &state))
+		return;
+
+	if (!bt_uuid_strcmp(str, PAC_SINK_UUID))
+		dir = BTP_BAP_DIR_SOURCE;
+	else
+		dir = BTP_BAP_DIR_SINK;
+
+	if (!transport_get_cig_cis(proxy, &cig, &cis))
+		return;
+
+	ase = find_ase(device, cig, cis, dir);
+	if (!ase)
+		return;
+
+	ase->transport_proxy = proxy;
+
+	/* In desync mode, the CIS_CONNECTED event is sent later, once the
+	 * "Desynchronized" property has been set (see set_desync_reply());
+	 * otherwise send it now.
+	 */
+	if (!adapter->desync) {
+		struct btp_ascs_cis_connected_ev ev;
+
+		memcpy(&ev.address, &device->address, sizeof(ev.address));
+		ev.address_type = device->address_type;
+		ev.ase_id = ase->ase_id;
+		ev.cis_id = ase->cis_id;
+
+		btp_send(btp, BTP_ASCS_SERVICE, BTP_EV_ASCS_CIS_CONNECTED,
+				adapter->index, sizeof(ev), &ev);
+	}
+
+	if (ase->transport_state == ASE_TRANSPORT_ACQUIRING &&
+			!strcmp(state, "idle")) {
+		l_dbus_proxy_method_call(proxy, "Acquire",
+					NULL,
+					ascs_acquire_reply,
+					ase, NULL);
+	}
+}
+
 void ascs_property_changed(struct l_dbus_proxy *proxy, const char *name,
 				struct l_dbus_message *msg, void *user_data)
 {
 	const char *interface = l_dbus_proxy_get_interface(proxy);
 
-	if (!strcmp(interface, "org.bluez.GattCharacteristic1")) {
-		char *uuid, *str;
-		struct btp_device *device;
-		struct btp_adapter *adapter;
-		struct btp_ase *ase;
-		struct l_dbus_message_iter iter;
-		uint8_t *data;
-		uint32_t n;
-		const struct l_queue_entry *entry;
-		struct l_dbus_message *reply;
+	if (!strcmp(interface, "org.bluez.MediaTransport1")) {
+		if (!strcmp(name, "State")) {
+			const char *state, *path, *uuid;
+			struct btp_device *dev;
+			struct btp_adapter *adapter;
+			uint8_t dir, cig, cis;
+			struct btp_ase *ase;
+			uint8_t ase_state;
 
-		if (strcmp(name, "Value"))
-			return;
+			if (!l_dbus_message_get_arguments(msg, "s", &state))
+				return;
 
-		if (!l_dbus_proxy_get_property(proxy, "UUID", "s", &uuid))
-			return;
+			if (!l_dbus_proxy_get_property(proxy, "Device", "o",
+								&path))
+				return;
 
-		if (!l_dbus_proxy_get_property(proxy, "Service", "o", &str))
-			return;
+			dev = find_device_by_path(path);
+			if (!dev)
+				return;
 
-		device = find_device_by_service_path(str);
-		if (!device)
-			return;
+			adapter = find_adapter_by_device(dev);
+			if (!adapter)
+				return;
 
-		adapter = find_adapter_by_device(device);
-		if (!adapter)
-			return;
+			if (!l_dbus_proxy_get_property(proxy, "UUID", "s",
+								&uuid))
+				return;
 
-		ase = find_ase_by_uuid(device, uuid);
-		if (!ase)
-			return;
+			if (!bt_uuid_strcmp(uuid, PAC_SINK_UUID))
+				dir = BTP_BAP_DIR_SOURCE;
+			else
+				dir = BTP_BAP_DIR_SINK;
 
-		if (!l_dbus_message_get_arguments(msg, "ay", &iter))
-			return;
+			if (!transport_get_cig_cis(proxy, &cig, &cis))
+				return;
 
-		if (!l_dbus_message_iter_get_fixed_array(&iter, &data, &n)) {
-			l_debug("Cannot read value");
-			return;
+			ase = find_ase(dev, cig, cis, dir);
+			if (!ase)
+				return;
+
+			if (!strcmp(state, "active"))
+				ase_state = BT_BAP_STREAM_STATE_STREAMING;
+			else {
+				if (ase->io) {
+					l_io_destroy(ase->io);
+					ase->io = NULL;
+				}
+
+				ase_state = BT_BAP_STREAM_STATE_IDLE;
+			}
+
+			send_state_changed(adapter->index,  &dev->address,
+							dev->address_type,
+							ase->ase_id,
+							ase_state);
 		}
 
-		ase->ase_id = data[0];
+		if (!strcmp(name, "Desynchronized")) {
+			struct btp_device *dev;
+			struct btp_adapter *adapter;
+			const bool desynchronized;
+			const char *path, *uuid;
+			uint8_t dir, cig, cis;
+			struct btp_ase *ase;
 
-		for (entry = l_queue_get_entries(device->endpoints); entry;
-							entry = entry->next) {
-			struct l_dbus_proxy *p = entry->data;
-			const char *str, *pac_uuid;
+			if (!l_dbus_message_get_arguments(msg, "b",
+							&desynchronized))
+				return;
 
-			if (!l_dbus_proxy_get_property(p, "UUID", "s", &str))
-				continue;
+			if (!l_dbus_proxy_get_property(proxy, "Device", "o",
+								&path))
+				return;
+
+			dev = find_device_by_path(path);
+			if (!dev)
+				return;
+
+			adapter = find_adapter_by_device(dev);
+			if (!adapter || !adapter->desync)
+				return;
+
+			if (!l_dbus_proxy_get_property(proxy, "UUID", "s",
+								&uuid))
+				return;
 
-			if (bt_uuid16_cmp(&ase->uuid, ASE_SINK_UUID))
-				pac_uuid = PAC_SINK_UUID;
+			if (!bt_uuid_strcmp(uuid, PAC_SINK_UUID))
+				dir = BTP_BAP_DIR_SOURCE;
 			else
-				pac_uuid = PAC_SOURCE_UUID;
+				dir = BTP_BAP_DIR_SINK;
 
-			if (!strcmp(str, pac_uuid)) {
-				ase->ep_proxy = p;
-				break;
-			}
-		}
+			if (!transport_get_cig_cis(proxy, &cig, &cis))
+				return;
 
-		msg = find_pending_msg(pending_select_properties, ase);
-		if (msg) {
-			l_queue_remove(pending_select_properties, msg);
-			reply = get_properties_reply(msg, adapter, ase);
-			l_dbus_send(dbus, reply);
+			ase = find_ase(dev, cig, cis, dir);
+			if (!ase)
+				return;
 
-			l_dbus_message_unref(msg);
+			if (!desynchronized) {
+				l_dbus_proxy_set_property(proxy,
+							set_desync_reply,
+							ase, NULL,
+							"Desynchronized", "b",
+							true);
+			}
 		}
 	}
 }
@@ -905,6 +1483,22 @@ bool ascs_register_service(struct btp *btp_, struct l_dbus *dbus_,
 	btp_register(btp, BTP_ASCS_SERVICE, BTP_OP_ASCS_CONFIGURE_CODEC,
 					btp_ascs_configure_codec, NULL, NULL);
 
+	btp_register(btp, BTP_ASCS_SERVICE, BTP_OP_ASCS_CONFIGURE_QOS,
+					btp_ascs_configure_qos, NULL, NULL);
+
+	btp_register(btp, BTP_ASCS_SERVICE, BTP_OP_ASCS_ENABLE,
+					btp_ascs_enable, NULL, NULL);
+
+	btp_register(btp, BTP_ASCS_SERVICE, BTP_OP_ASCS_RECEIVER_START_READY,
+					btp_ascs_receiver_start_ready,
+					NULL, NULL);
+
+	btp_register(btp, BTP_ASCS_SERVICE, BTP_OP_ASCS_ADD_ASE_TO_CIS,
+					btp_ascs_add_ase_to_cis, NULL, NULL);
+
+	btp_register(btp, BTP_ASCS_SERVICE, BTP_OP_ASCS_PRECONFIGURE_QOS,
+					btp_ascs_preconfigure_qos, NULL, NULL);
+
 	if (!l_dbus_register_interface(dbus, ENDPOINT_IFACE,
 						setup_endpoint_interface,
 						NULL, false)) {
diff --git a/client/btpclient/ascs.h b/client/btpclient/ascs.h
index 46d38ae90..b3a2eb7e2 100644
--- a/client/btpclient/ascs.h
+++ b/client/btpclient/ascs.h
@@ -14,5 +14,8 @@ bool ascs_is_service_registered(void);
 
 bool ascs_setup(struct btp_adapter *adapter);
 
+void ascs_proxy_added(struct l_dbus_proxy *proxy, void *user_data);
 void ascs_property_changed(struct l_dbus_proxy *proxy, const char *name,
 				struct l_dbus_message *msg, void *user_data);
+
+void ascs_ase_replied(struct btp_adapter *adapter, struct btp_ase *ase);
diff --git a/client/btpclient/bap.c b/client/btpclient/bap.c
index f7bee9831..b1d70a50d 100644
--- a/client/btpclient/bap.c
+++ b/client/btpclient/bap.c
@@ -20,6 +20,7 @@
 #include "src/shared/bap-defs.h"
 #include "src/shared/btp.h"
 #include "btpclient.h"
+#include "ascs.h"
 #include "bap.h"
 
 static struct btp *btp;
@@ -105,6 +106,14 @@ failed:
 	btp_send_error(btp, BTP_BAP_SERVICE, index, status);
 }
 
+static bool match_ase(const void *entry, const void *data)
+{
+	const struct btp_ase *ase = entry;
+	uint8_t id = L_PTR_TO_UINT(data);
+
+	return ase->ase_id == id;
+}
+
 static void btp_bap_send(uint8_t index, const void *param, uint16_t length,
 								void *user_data)
 {
@@ -122,7 +131,7 @@ static void btp_bap_send(uint8_t index, const void *param, uint16_t length,
 	}
 
 	dev = find_device_by_address(adapter, &cp->address, cp->address_type);
-	ase = find_ase_by_dir(dev, BTP_BAP_DIR_SINK);
+	ase = l_queue_find(dev->ases, match_ase, L_UINT_TO_PTR(cp->ase_id));
 	if (!ase || !ase->io)
 		goto failed;
 
@@ -236,6 +245,8 @@ static void bap_read_ase_reply(struct l_dbus_proxy *proxy,
 	uint32_t n;
 	bt_uuid_t uuid;
 	struct gatt_attribute *attribute;
+	const char *pac_uuid;
+	const struct l_queue_entry *entry;
 
 	if (l_dbus_message_is_error(result)) {
 		const char *name, *desc;
@@ -267,10 +278,30 @@ static void bap_read_ase_reply(struct l_dbus_proxy *proxy,
 
 	free(rp);
 
-	if (bt_uuid16_cmp(&ase->uuid, ASE_SINK_UUID))
+	if (bt_uuid16_cmp(&ase->uuid, ASE_SINK_UUID)) {
 		bt_uuid16_create(&uuid, PAC_SINK_CHRC_UUID);
-	else
+		pac_uuid = PAC_SINK_UUID;
+	} else {
 		bt_uuid16_create(&uuid, PAC_SOURCE_CHRC_UUID);
+		pac_uuid = PAC_SOURCE_UUID;
+	}
+
+	for (entry = l_queue_get_entries(device->endpoints); entry;
+						entry = entry->next) {
+		struct l_dbus_proxy *p = entry->data;
+		const char *str;
+
+		if (!l_dbus_proxy_get_property(p, "UUID", "s", &str))
+			continue;
+
+		if (!strcmp(str, pac_uuid)) {
+			ase->ep_proxy = p;
+			break;
+		}
+	}
+
+	ascs_ase_replied(adapter, ase);
+
 	attribute = l_queue_find(device->characteristics,
 						match_attribute_uuid, &uuid);
 	if (!attribute)
@@ -286,6 +317,72 @@ failed:
 	btp_send_error(btp, BTP_BAP_SERVICE, adapter->index, BTP_ERROR_FAIL);
 }
 
+static uint8_t get_next_cis(struct btp_device *device, uint8_t dir)
+{
+	const struct l_queue_entry *adapter_entry;
+	const struct l_queue_entry *ase_entry;
+	uint8_t cis = 0;
+	bool found = false;
+
+	/* For the same device, reuse the opposite cis_id if there is no ASE
+	 * in the requested direction already using that same cis_id
+	 */
+	for (ase_entry = l_queue_get_entries(device->ases); ase_entry;
+					ase_entry = ase_entry->next) {
+		struct btp_ase *ase = ase_entry->data;
+		const struct l_queue_entry *entry;
+		bool has_same_dir = false;
+
+		if (ase->dir == dir)
+			continue;
+
+		for (entry = l_queue_get_entries(device->ases); entry;
+					entry = entry->next) {
+			struct btp_ase *peer = entry->data;
+
+			if (peer->dir == dir && peer->cis_id == ase->cis_id) {
+				has_same_dir = true;
+				break;
+			}
+		}
+
+		if (!has_same_dir)
+			return ase->cis_id;
+	}
+
+	/* Else returns the global highest cis_id + 1 across all ASEs of all
+	 * devices, or 0 if no ASE exists
+	 */
+	for (adapter_entry = l_queue_get_entries(get_adapters_list());
+					adapter_entry;
+					adapter_entry = adapter_entry->next) {
+		struct btp_adapter *adapter = adapter_entry->data;
+		const struct l_queue_entry *device_entry;
+
+		for (device_entry = l_queue_get_entries(adapter->devices);
+					device_entry;
+					device_entry = device_entry->next) {
+			struct btp_device *dev = device_entry->data;
+
+			for (ase_entry = l_queue_get_entries(dev->ases);
+						ase_entry;
+						ase_entry = ase_entry->next) {
+				struct btp_ase *ase = ase_entry->data;
+
+				if (!found || ase->cis_id > cis)
+					cis = ase->cis_id;
+
+				found = true;
+			}
+		}
+	}
+
+	if (!found)
+		return 0;
+
+	return cis + 1;
+}
+
 void bap_proxy_added(struct l_dbus_proxy *proxy, void *user_data)
 {
 	struct btp_device *device = user_data;
@@ -306,6 +403,8 @@ void bap_proxy_added(struct l_dbus_proxy *proxy, void *user_data)
 			ase->device = device;
 			ase->dir = BTP_BAP_DIR_SINK;
 			ase->uuid = uuid;
+			ase->cig_id = 0;
+			ase->cis_id = get_next_cis(device, ase->dir);
 			l_queue_push_tail(device->ases, ase);
 
 			l_dbus_proxy_method_call(proxy, "ReadValue",
@@ -321,6 +420,8 @@ void bap_proxy_added(struct l_dbus_proxy *proxy, void *user_data)
 			ase->device = device;
 			ase->dir = BTP_BAP_DIR_SOURCE;
 			ase->uuid = uuid;
+			ase->cig_id = 0;
+			ase->cis_id = get_next_cis(device, ase->dir);
 			l_queue_push_tail(device->ases, ase);
 
 			l_dbus_proxy_method_call(proxy, "ReadValue",
diff --git a/client/btpclient/btpclient.c b/client/btpclient/btpclient.c
index eb4d348db..6e084e2c8 100644
--- a/client/btpclient/btpclient.c
+++ b/client/btpclient/btpclient.c
@@ -230,6 +230,26 @@ struct btp_device *find_device_by_proxy(struct l_dbus_proxy *proxy)
 	return NULL;
 }
 
+static bool match_cigcisdir(const void *entry, const void *data)
+{
+	const struct btp_ase *ase = entry;
+	uint32_t cigcisdir = L_PTR_TO_UINT(data);
+	uint8_t cig = cigcisdir & 0xFF;
+	uint8_t cis = (cigcisdir >> 8) & 0xFF;
+	uint8_t dir = (cigcisdir >> 16) & 0xFF;
+
+	return ase->cig_id == cig && ase->cis_id == cis && ase->dir == dir;
+}
+
+struct btp_ase *find_ase(struct btp_device *device, uint8_t cig, uint8_t cis,
+								uint8_t dir)
+{
+	uint32_t cigcisdir = cig + (cis << 8) + (dir << 16);
+
+	return l_queue_find(device->ases, match_cigcisdir,
+						L_UINT_TO_PTR(cigcisdir));
+}
+
 static bool match_uuid(const void *entry, const void *data)
 {
 	const struct btp_ase *ase = entry;
@@ -633,6 +653,21 @@ static void proxy_added(struct l_dbus_proxy *proxy, void *user_data)
 
 		return;
 	}
+
+	if (!strcmp(interface, "org.bluez.MediaTransport1")) {
+		char *str;
+		struct btp_device *device;
+
+		if (!l_dbus_proxy_get_property(proxy, "Device", "o", &str))
+			return;
+
+		device = find_device_by_path(str);
+		if (!device)
+			return;
+
+		if (ascs_is_service_registered())
+			ascs_proxy_added(proxy, device);
+	}
 }
 
 static bool device_match_by_proxy(const void *a, const void *b)
diff --git a/client/btpclient/btpclient.h b/client/btpclient/btpclient.h
index 2ba612ade..6fab3d701 100644
--- a/client/btpclient/btpclient.h
+++ b/client/btpclient/btpclient.h
@@ -23,6 +23,7 @@ struct btp_adapter {
 	uint32_t source_locations;
 
 	uint8_t target_latency;
+	bool desync;
 };
 
 struct btp_device {
@@ -36,12 +37,25 @@ struct btp_device {
 	struct l_queue *endpoints;
 };
 
+enum ase_transport_state {
+	ASE_TRANSPORT_READY = 0,
+	ASE_TRANSPORT_ACQUIRING,
+	ASE_TRANSPORT_ACQUIRED,
+};
+
 struct btp_ase {
 	struct btp_device *device;
 	bt_uuid_t uuid;
 	uint8_t dir;
 	uint8_t ase_id;
+	uint8_t cig_id;
+	uint8_t cis_id;
 	struct l_dbus_proxy *ep_proxy;
+	struct l_dbus_proxy *transport_proxy;
+	enum ase_transport_state transport_state;
+	struct l_io *io;
+	uint16_t rx_mtu;
+	uint16_t tx_mtu;
 };
 
 struct btp_agent {
@@ -67,6 +81,8 @@ struct btp_device *find_device_by_path(const char *path);
 struct btp_adapter *find_adapter_by_device(struct btp_device *device);
 struct btp_device *find_device_by_proxy(struct l_dbus_proxy *proxy);
 struct btp_device *find_device_by_service_path(const char *path);
+struct btp_ase *find_ase(struct btp_device *device, uint8_t cig, uint8_t cis,
+							uint8_t dir);
 struct btp_ase *find_ase_by_uuid(struct btp_device *device, char *uuid);
 
 struct btp_agent *get_agent(void);
diff --git a/client/btpclient/vendor.c b/client/btpclient/vendor.c
index 30ce5649c..f421035db 100644
--- a/client/btpclient/vendor.c
+++ b/client/btpclient/vendor.c
@@ -65,6 +65,7 @@ static void btp_vendor_ascs_setup(uint8_t index, const void *param,
 	const struct btp_vendor_ascs_setup_cp *cp = param;
 
 	adapter->target_latency = cp->target_latency;
+	adapter->desync = cp->desync;
 
 	ascs_setup(adapter);
 
diff --git a/src/shared/btp.h b/src/shared/btp.h
index fe93158aa..8cae0e765 100644
--- a/src/shared/btp.h
+++ b/src/shared/btp.h
@@ -450,6 +450,56 @@ struct btp_ascs_configure_codec_cp {
 	uint8_t cc_ltvs[];
 } __packed;
 
+#define BTP_OP_ASCS_CONFIGURE_QOS		0x03
+struct btp_ascs_configure_qos_cp {
+	uint8_t address_type;
+	bdaddr_t address;
+	uint8_t ase_id;
+	uint8_t cig_id;
+	uint8_t cis_id;
+	uint8_t sdu_interval[3];
+	uint8_t framing;
+	uint16_t max_sdu;
+	uint8_t retransmission_num;
+	uint16_t max_transport_latency;
+	uint8_t presentation_delay[3];
+} __packed;
+
+#define BTP_OP_ASCS_ENABLE			0x04
+struct btp_ascs_enable_cp {
+	uint8_t address_type;
+	bdaddr_t address;
+	uint8_t ase_id;
+} __packed;
+
+#define BTP_OP_ASCS_RECEIVER_START_READY	0x05
+struct btp_ascs_receiver_start_ready_cp {
+	uint8_t address_type;
+	bdaddr_t address;
+	uint8_t ase_id;
+} __packed;
+
+#define BTP_OP_ASCS_ADD_ASE_TO_CIS		0x0a
+struct btp_ascs_add_ase_to_cis_cp {
+	uint8_t address_type;
+	bdaddr_t address;
+	uint8_t ase_id;
+	uint8_t cig_id;
+	uint8_t cis_id;
+} __packed;
+
+#define BTP_OP_ASCS_PRECONFIGURE_QOS		0x0b
+struct btp_ascs_preconfigure_qos_cp {
+	uint8_t cig_id;
+	uint8_t cis_id;
+	uint8_t sdu_interval[3];
+	uint8_t framing;
+	uint16_t max_sdu;
+	uint8_t retransmission_num;
+	uint16_t max_transport_latency;
+	uint8_t presentation_delay[3];
+} __packed;
+
 #define BTP_EV_ASCS_OPERATION_COMPLETED		0x80
 struct btp_ascs_operation_completed_ev {
 	uint8_t address_type;
@@ -470,6 +520,14 @@ struct btp_ascs_ase_state_changed_ev {
 	uint8_t state;
 } __packed;
 
+#define BTP_EV_ASCS_CIS_CONNECTED		0x83
+struct btp_ascs_cis_connected_ev {
+	uint8_t address_type;
+	bdaddr_t address;
+	uint8_t ase_id;
+	uint8_t cis_id;
+} __packed;
+
 #define BTP_BAP_DIR_SINK			0x01
 #define BTP_BAP_DIR_SOURCE			0x02
 
@@ -521,11 +579,21 @@ struct btp_bap_ase_found_ev {
 	uint8_t ase_id;
 } __packed;
 
+#define BTP_EV_BAP_STREAM_RECEIVED		0x83
+struct btp_bap_stream_received_ev {
+	uint8_t address_type;
+	bdaddr_t address;
+	uint8_t ase_id;
+	uint8_t data_len;
+	uint8_t data[];
+} __packed;
+
 #define BTP_OP_VENDOR_READ_SUPPORTED_COMMANDS	0x01
 
 #define BTP_OP_VENDOR_ASCS_SETUP		0x02
 struct btp_vendor_ascs_setup_cp {
 	uint8_t target_latency;
+	uint8_t desync;
 } __packed;
 
 struct btp;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH BlueZ 7/8] client/btpclient: Defer CIG/CIS assignment based on Client/Server role
  2026-09-04 13:51 [PATCH BlueZ 0/8] client/btpclient: Add BAP/ASCS/PACS support for auto-pts BAP tests Frédéric Danis
                   ` (5 preceding siblings ...)
  2026-09-04 13:51 ` [PATCH BlueZ 6/8] client/btpclient: Add ASCS support for BAP/UCL/STR/* tests Frédéric Danis
@ 2026-09-04 13:51 ` Frédéric Danis
  2026-09-04 13:51 ` [PATCH BlueZ 8/8] client/btpclient: Add stream auto acquire for BAP/USR/STR/* tests Frédéric Danis
  7 siblings, 0 replies; 10+ messages in thread
From: Frédéric Danis @ 2026-09-04 13:51 UTC (permalink / raw)
  To: linux-bluetooth

Previously, CIG/CIS ids were eagerly assigned to each ASE as soon as
it was discovered (CIG 0, next available CIS), regardless of whether
the btpclient was acting as the Unicast Client or Server for that ASE.
This did not match how CIG/CIS ids are actually negotiated, nor what
auto-pts expects: unicast client tests expect CIG/CIS to start at 0/0,
while unicast server tests expect them to start at 1/1.

- As Unicast Client, the CIG/CIS ids are chosen locally when building
  the QoS properties returned from SelectProperties(), starting at 0.
- As Unicast Server, the CIG/CIS ids are only known once the transport
  reports them; if not yet available, all of the device's ASEs should
  share a common CIG (1) with CIS ids starting at 1 as well.

Move get_next_cis() from bap.c to ascs.c and extend it to take a base
CIS value, so it can be reused both for the Client and Server code
paths (base 0 and base 1 respectively) and skips ASEs whose CIS id is
still unset. Leave ase->cig_id and ase->cis_id unset
(BT_ISO_QOS_CIG_UNSET/BT_ISO_QOS_CIS_UNSET) at ASE discovery time, and
resolve them lazily:

- In get_properties_reply(), assign CIG 0 and the next CIS starting at
  0 when building the QoS reply for SelectProperties() (Client role).
- In ascs_proxy_added(), when the transport does not yet report a
  CIG/CIS, assign CIG 1 to all of the device's ASEs and derive CIS ids
  starting at 1 via the new set_cig_cis()/set_cig_cis_data helpers
  (Server role).
---
 client/btpclient/ascs.c | 144 ++++++++++++++++++++++++++++++++++------
 client/btpclient/bap.c  |  74 ++-------------------
 2 files changed, 127 insertions(+), 91 deletions(-)

diff --git a/client/btpclient/ascs.c b/client/btpclient/ascs.c
index ca4c6db07..25f561858 100644
--- a/client/btpclient/ascs.c
+++ b/client/btpclient/ascs.c
@@ -918,6 +918,76 @@ static void ltv_find(size_t i, uint8_t l, uint8_t t, uint8_t *v,
 	*found = true;
 }
 
+static uint8_t get_next_cis(struct btp_device *device, uint8_t dir,
+							uint8_t base)
+{
+	const struct l_queue_entry *adapter_entry;
+	const struct l_queue_entry *ase_entry;
+	uint8_t cis = base;
+	bool found = false;
+
+	/* For the same device, reuse the opposite cis_id if there is no ASE
+	 * in the requested direction already using that same cis_id
+	 */
+	for (ase_entry = l_queue_get_entries(device->ases); ase_entry;
+					ase_entry = ase_entry->next) {
+		struct btp_ase *ase = ase_entry->data;
+		const struct l_queue_entry *entry;
+		bool has_same_dir = false;
+
+		if (ase->dir == dir)
+			continue;
+
+		for (entry = l_queue_get_entries(device->ases); entry;
+					entry = entry->next) {
+			struct btp_ase *peer = entry->data;
+
+			if (peer->dir == dir && peer->cis_id == ase->cis_id) {
+				has_same_dir = true;
+				break;
+			}
+		}
+
+		if (!has_same_dir)
+			return ase->cis_id;
+	}
+
+	/* Else returns the global highest cis_id + 1 across all ASEs of all
+	 * devices, or 'base' if none has already been assigned
+	 */
+	for (adapter_entry = l_queue_get_entries(get_adapters_list());
+					adapter_entry;
+					adapter_entry = adapter_entry->next) {
+		struct btp_adapter *adapter = adapter_entry->data;
+		const struct l_queue_entry *device_entry;
+
+		for (device_entry = l_queue_get_entries(adapter->devices);
+					device_entry;
+					device_entry = device_entry->next) {
+			struct btp_device *dev = device_entry->data;
+
+			for (ase_entry = l_queue_get_entries(dev->ases);
+						ase_entry;
+						ase_entry = ase_entry->next) {
+				struct btp_ase *ase = ase_entry->data;
+
+				if (ase->cis_id == BT_ISO_QOS_CIS_UNSET)
+					continue;
+
+				if (!found || ase->cis_id > cis)
+					cis = ase->cis_id;
+
+				found = true;
+			}
+		}
+	}
+
+	if (!found)
+		return cis;
+
+	return cis + 1;
+}
+
 static struct l_dbus_message *get_properties_reply(
 						struct l_dbus_message *message,
 						struct btp_adapter *adapter,
@@ -1019,27 +1089,31 @@ static struct l_dbus_message *get_properties_reply(
 	l_dbus_message_builder_enter_variant(builder, "a{sv}");
 	l_dbus_message_builder_enter_array(builder, "{sv}");
 
-	if (ase->cig_id != BT_ISO_QOS_CIG_UNSET) {
-		l_dbus_message_builder_enter_dict(builder, "sv");
-		l_dbus_message_builder_append_basic(builder, 's',
-						"CIG");
-		l_dbus_message_builder_enter_variant(builder, "y");
-		l_dbus_message_builder_append_basic(builder, 'y',
-						&ase->cig_id);
-		l_dbus_message_builder_leave_variant(builder);
-		l_dbus_message_builder_leave_dict(builder);
-	}
+	/* SelectProperties() is only invoked when acting as the Unicast Client,
+	 * so assign CIG 0 here and pick the next available CIS for this ASE.
+	 */
+	if (ase->cig_id == BT_ISO_QOS_CIG_UNSET)
+		ase->cig_id = 0;
+	if (ase->cis_id == BT_ISO_QOS_CIS_UNSET)
+		ase->cis_id = get_next_cis(ase->device, ase->dir, 0);
 
-	if (ase->cis_id != BT_ISO_QOS_CIS_UNSET) {
-		l_dbus_message_builder_enter_dict(builder, "sv");
-		l_dbus_message_builder_append_basic(builder, 's',
-						"CIS");
-		l_dbus_message_builder_enter_variant(builder, "y");
-		l_dbus_message_builder_append_basic(builder, 'y',
-						&ase->cis_id);
-		l_dbus_message_builder_leave_variant(builder);
-		l_dbus_message_builder_leave_dict(builder);
-	}
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's',
+					"CIG");
+	l_dbus_message_builder_enter_variant(builder, "y");
+	l_dbus_message_builder_append_basic(builder, 'y',
+					&ase->cig_id);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
+
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's',
+					"CIS");
+	l_dbus_message_builder_enter_variant(builder, "y");
+	l_dbus_message_builder_append_basic(builder, 'y',
+					&ase->cis_id);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
 
 	l_dbus_message_builder_enter_dict(builder, "sv");
 	l_dbus_message_builder_append_basic(builder, 's',
@@ -1299,6 +1373,24 @@ static bool transport_get_cig_cis(struct l_dbus_proxy *proxy, uint8_t *cig,
 	return true;
 }
 
+struct set_cig_cis_data {
+	struct btp_device *device;
+	uint8_t cig;
+};
+
+static void set_cig_cis(void *data, void *user_data)
+{
+	struct btp_ase *ase = data;
+	struct set_cig_cis_data *param = user_data;
+	uint8_t cis = get_next_cis(param->device, ase->dir, 1);
+
+	if (ase->cig_id != BT_ISO_QOS_CIG_UNSET)
+		return;
+
+	ase->cig_id = param->cig;
+	ase->cis_id = cis;
+}
+
 void ascs_proxy_added(struct l_dbus_proxy *proxy, void *user_data)
 {
 	char *str, *state;
@@ -1323,8 +1415,18 @@ void ascs_proxy_added(struct l_dbus_proxy *proxy, void *user_data)
 	else
 		dir = BTP_BAP_DIR_SINK;
 
-	if (!transport_get_cig_cis(proxy, &cig, &cis))
+	if (!transport_get_cig_cis(proxy, &cig, &cis)) {
+		struct set_cig_cis_data data;
+
+		/* No CIG/CIS reported on the transport yet, meaning this ASE
+		 * is acting as the Unicast Server: assign CIG 1 to all of the
+		 * device's ASEs and let set_cig_cis() derive their CIS ids.
+		 */
+		data.device = device;
+		data.cig = 1;
+		l_queue_foreach(device->ases, set_cig_cis, &data);
 		return;
+	}
 
 	ase = find_ase(device, cig, cis, dir);
 	if (!ase)
diff --git a/client/btpclient/bap.c b/client/btpclient/bap.c
index b1d70a50d..edb220f54 100644
--- a/client/btpclient/bap.c
+++ b/client/btpclient/bap.c
@@ -317,72 +317,6 @@ failed:
 	btp_send_error(btp, BTP_BAP_SERVICE, adapter->index, BTP_ERROR_FAIL);
 }
 
-static uint8_t get_next_cis(struct btp_device *device, uint8_t dir)
-{
-	const struct l_queue_entry *adapter_entry;
-	const struct l_queue_entry *ase_entry;
-	uint8_t cis = 0;
-	bool found = false;
-
-	/* For the same device, reuse the opposite cis_id if there is no ASE
-	 * in the requested direction already using that same cis_id
-	 */
-	for (ase_entry = l_queue_get_entries(device->ases); ase_entry;
-					ase_entry = ase_entry->next) {
-		struct btp_ase *ase = ase_entry->data;
-		const struct l_queue_entry *entry;
-		bool has_same_dir = false;
-
-		if (ase->dir == dir)
-			continue;
-
-		for (entry = l_queue_get_entries(device->ases); entry;
-					entry = entry->next) {
-			struct btp_ase *peer = entry->data;
-
-			if (peer->dir == dir && peer->cis_id == ase->cis_id) {
-				has_same_dir = true;
-				break;
-			}
-		}
-
-		if (!has_same_dir)
-			return ase->cis_id;
-	}
-
-	/* Else returns the global highest cis_id + 1 across all ASEs of all
-	 * devices, or 0 if no ASE exists
-	 */
-	for (adapter_entry = l_queue_get_entries(get_adapters_list());
-					adapter_entry;
-					adapter_entry = adapter_entry->next) {
-		struct btp_adapter *adapter = adapter_entry->data;
-		const struct l_queue_entry *device_entry;
-
-		for (device_entry = l_queue_get_entries(adapter->devices);
-					device_entry;
-					device_entry = device_entry->next) {
-			struct btp_device *dev = device_entry->data;
-
-			for (ase_entry = l_queue_get_entries(dev->ases);
-						ase_entry;
-						ase_entry = ase_entry->next) {
-				struct btp_ase *ase = ase_entry->data;
-
-				if (!found || ase->cis_id > cis)
-					cis = ase->cis_id;
-
-				found = true;
-			}
-		}
-	}
-
-	if (!found)
-		return 0;
-
-	return cis + 1;
-}
-
 void bap_proxy_added(struct l_dbus_proxy *proxy, void *user_data)
 {
 	struct btp_device *device = user_data;
@@ -403,8 +337,8 @@ void bap_proxy_added(struct l_dbus_proxy *proxy, void *user_data)
 			ase->device = device;
 			ase->dir = BTP_BAP_DIR_SINK;
 			ase->uuid = uuid;
-			ase->cig_id = 0;
-			ase->cis_id = get_next_cis(device, ase->dir);
+			ase->cig_id = BT_ISO_QOS_CIG_UNSET;
+			ase->cis_id = BT_ISO_QOS_CIS_UNSET;
 			l_queue_push_tail(device->ases, ase);
 
 			l_dbus_proxy_method_call(proxy, "ReadValue",
@@ -420,8 +354,8 @@ void bap_proxy_added(struct l_dbus_proxy *proxy, void *user_data)
 			ase->device = device;
 			ase->dir = BTP_BAP_DIR_SOURCE;
 			ase->uuid = uuid;
-			ase->cig_id = 0;
-			ase->cis_id = get_next_cis(device, ase->dir);
+			ase->cig_id = BT_ISO_QOS_CIG_UNSET;
+			ase->cis_id = BT_ISO_QOS_CIS_UNSET;
 			l_queue_push_tail(device->ases, ase);
 
 			l_dbus_proxy_method_call(proxy, "ReadValue",
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH BlueZ 8/8] client/btpclient: Add stream auto acquire for BAP/USR/STR/* tests
  2026-09-04 13:51 [PATCH BlueZ 0/8] client/btpclient: Add BAP/ASCS/PACS support for auto-pts BAP tests Frédéric Danis
                   ` (6 preceding siblings ...)
  2026-09-04 13:51 ` [PATCH BlueZ 7/8] client/btpclient: Defer CIG/CIS assignment based on Client/Server role Frédéric Danis
@ 2026-09-04 13:51 ` Frédéric Danis
  7 siblings, 0 replies; 10+ messages in thread
From: Frédéric Danis @ 2026-09-04 13:51 UTC (permalink / raw)
  To: linux-bluetooth

The sink stream should be auto acquired and data sent on it.
---
 client/btpclient/ascs.c      |  10 +++-
 client/btpclient/bap.c       | 108 +++++++++++++++++++++++++++++++++++
 client/btpclient/bap.h       |   2 +
 client/btpclient/btpclient.c |  68 ++++++++++++++++++++++
 client/btpclient/btpclient.h |   2 +
 5 files changed, 189 insertions(+), 1 deletion(-)

diff --git a/client/btpclient/ascs.c b/client/btpclient/ascs.c
index 25f561858..45b866634 100644
--- a/client/btpclient/ascs.c
+++ b/client/btpclient/ascs.c
@@ -1506,7 +1506,15 @@ void ascs_property_changed(struct l_dbus_proxy *proxy, const char *name,
 
 			if (!strcmp(state, "active"))
 				ase_state = BT_BAP_STREAM_STATE_STREAMING;
-			else {
+			else if (!strcmp(state, "pending") &&
+				ase->transport_state == ASE_TRANSPORT_READY) {
+				l_dbus_proxy_method_call(proxy, "Acquire",
+							NULL,
+							ascs_acquire_reply,
+							ase, NULL);
+				ase_change_state(ase, ASE_TRANSPORT_ACQUIRING);
+				ase_state = BT_BAP_STREAM_STATE_ENABLING;
+			} else {
 				if (ase->io) {
 					l_io_destroy(ase->io);
 					ase->io = NULL;
diff --git a/client/btpclient/bap.c b/client/btpclient/bap.c
index edb220f54..f2c4a7b23 100644
--- a/client/btpclient/bap.c
+++ b/client/btpclient/bap.c
@@ -366,6 +366,114 @@ void bap_proxy_added(struct l_dbus_proxy *proxy, void *user_data)
 	}
 }
 
+static bool transport_get_cig_cis(struct l_dbus_proxy *proxy, uint8_t *cig,
+								uint8_t *cis)
+{
+	struct l_dbus_message_iter iter, var;
+	const char *key;
+
+	*cig = BT_ISO_QOS_CIG_UNSET;
+	*cis = BT_ISO_QOS_CIS_UNSET;
+
+	if (!l_dbus_proxy_get_property(proxy, "QoS", "a{sv}", &iter))
+		return false;
+
+	while (l_dbus_message_iter_next_entry(&iter, &key, &var)) {
+		if (!strcmp(key, "CIG")) {
+			if (!l_dbus_message_iter_get_variant(&var, "y", cig))
+				return false;
+		}
+
+		if (!strcmp(key, "CIS")) {
+			if (!l_dbus_message_iter_get_variant(&var, "y", cis))
+				return false;
+		}
+	}
+
+	return true;
+}
+
+static void send_data(struct l_idle *idle, void *user_data)
+{
+	struct btp_ase *ase = user_data;
+	ssize_t bytes_written;
+	uint8_t *data;
+
+	if (!ase->idle_writer || !ase->io) {
+		l_idle_remove(idle);
+		ase->idle_writer = NULL;
+		return;
+	}
+
+	data = l_malloc(ase->tx_mtu);
+	bytes_written = write(l_io_get_fd(ase->io), data, ase->tx_mtu);
+	if (bytes_written < 0) {
+		l_error("Failed to write (%d)", errno);
+		l_idle_remove(idle);
+		ase->idle_writer = NULL;
+	}
+	l_free(data);
+}
+
+void bap_property_changed(struct l_dbus_proxy *proxy, const char *name,
+				struct l_dbus_message *msg, void *user_data)
+{
+	const char *interface = l_dbus_proxy_get_interface(proxy);
+
+	if (!strcmp(interface, "org.bluez.MediaTransport1")) {
+		if (!strcmp(name, "State")) {
+			const char *state, *path, *uuid;
+			struct btp_device *dev;
+			struct btp_adapter *adapter;
+			uint8_t dir, cig, cis;
+			struct btp_ase *ase;
+
+			if (!l_dbus_message_get_arguments(msg, "s", &state))
+				return;
+
+			if (!l_dbus_proxy_get_property(proxy, "Device", "o",
+								&path))
+				return;
+
+			dev = find_device_by_path(path);
+			if (!dev)
+				return;
+
+			adapter = find_adapter_by_device(dev);
+			if (!adapter)
+				return;
+
+			if (!l_dbus_proxy_get_property(proxy, "UUID", "s",
+								&uuid))
+				return;
+
+			if (!bt_uuid_strcmp(uuid, PAC_SINK_UUID))
+				dir = BTP_BAP_DIR_SOURCE;
+			else
+				dir = BTP_BAP_DIR_SINK;
+
+			if (!transport_get_cig_cis(proxy, &cig, &cis))
+				return;
+
+			ase = find_ase(dev, cig, cis, dir);
+			if (!ase)
+				return;
+
+			if (!strcmp(state, "active") && ase->auto_send &&
+					(ase->dir == BTP_BAP_DIR_SINK)) {
+				ase->idle_writer = l_idle_create(send_data,
+								ase, NULL);
+			} else if (!strcmp(state, "pending")) {
+				ase->auto_send = true;
+			} else if (!strcmp(state, "idle") &&
+							ase->idle_writer) {
+				l_idle_remove(ase->idle_writer);
+				ase->idle_writer = NULL;
+			}
+		}
+	}
+}
+
 bool bap_register_service(struct btp *btp_, struct l_dbus *dbus_,
 					struct l_dbus_client *client)
 {
diff --git a/client/btpclient/bap.h b/client/btpclient/bap.h
index 7fea19f8b..3c676e0ae 100644
--- a/client/btpclient/bap.h
+++ b/client/btpclient/bap.h
@@ -13,3 +13,5 @@ void bap_unregister_service(struct btp *btp);
 bool bap_is_service_registered(void);
 
 void bap_proxy_added(struct l_dbus_proxy *proxy, void *user_data);
+void bap_property_changed(struct l_dbus_proxy *proxy, const char *name,
+				struct l_dbus_message *msg, void *user_data);
diff --git a/client/btpclient/btpclient.c b/client/btpclient/btpclient.c
index 6e084e2c8..760c6e89f 100644
--- a/client/btpclient/btpclient.c
+++ b/client/btpclient/btpclient.c
@@ -445,6 +445,30 @@ static void extract_settings(struct l_dbus_proxy *proxy, uint32_t *current,
 		*current |=  BTP_GAP_SETTING_BONDABLE;
 }
 
+static bool transport_get_cig_cis(struct l_dbus_message_iter *iter,
+						uint8_t *cig, uint8_t *cis)
+{
+	struct l_dbus_message_iter var;
+	const char *key;
+
+	*cig = BT_ISO_QOS_CIG_UNSET;
+	*cis = BT_ISO_QOS_CIS_UNSET;
+
+	while (l_dbus_message_iter_next_entry(iter, &key, &var)) {
+		if (!strcmp(key, "CIG")) {
+			if (!l_dbus_message_iter_get_variant(&var, "y", cig))
+				return false;
+		}
+
+		if (!strcmp(key, "CIS")) {
+			if (!l_dbus_message_iter_get_variant(&var, "y", cis))
+				return false;
+		}
+	}
+
+	return true;
+}
+
 static void proxy_added(struct l_dbus_proxy *proxy, void *user_data)
 {
 	const char *interface = l_dbus_proxy_get_interface(proxy);
@@ -773,11 +797,55 @@ static void property_changed(struct l_dbus_proxy *proxy, const char *name,
 
 	l_info("Property changed: %s %s %s", name, path, interface);
 
+	if (!strcmp(interface, "org.bluez.MediaTransport1")) {
+		if (!strcmp(name, "QoS")) {
+			struct btp_device *dev;
+			const char *dev_path, *uuid;
+			uint8_t dir;
+			struct l_dbus_message_iter iter;
+			uint8_t cig = BT_ISO_QOS_CIG_UNSET;
+			uint8_t cis = BT_ISO_QOS_CIS_UNSET;
+			struct btp_ase *ase;
+
+			if (!l_dbus_proxy_get_property(proxy, "Device", "s",
+								&dev_path))
+				return;
+
+			dev = find_device_by_path(path);
+			if (!dev)
+				return;
+
+			if (!l_dbus_proxy_get_property(proxy, "UUID", "s",
+								&uuid))
+				return;
+
+			if (!bt_uuid_strcmp(uuid, PAC_SINK_UUID))
+				dir = BTP_BAP_DIR_SOURCE;
+			else
+				dir = BTP_BAP_DIR_SINK;
+
+			if (!l_dbus_message_get_arguments(msg, "a{sv}", &iter))
+				return;
+
+			if (!transport_get_cig_cis(&iter, &cig, &cis))
+				return;
+
+			ase = find_ase(dev, cig, cis, dir);
+			if (!ase)
+				return;
+
+			ase->transport_proxy = proxy;
+		}
+	}
+
 	if (gap_is_service_registered())
 		gap_property_changed(proxy, name, msg, user_data);
 
 	if (ascs_is_service_registered())
 		ascs_property_changed(proxy, name, msg, user_data);
+
+	if (bap_is_service_registered())
+		bap_property_changed(proxy, name, msg, user_data);
 }
 
 static void client_connected(struct l_dbus *dbus, void *user_data)
diff --git a/client/btpclient/btpclient.h b/client/btpclient/btpclient.h
index 6fab3d701..54f44ae9a 100644
--- a/client/btpclient/btpclient.h
+++ b/client/btpclient/btpclient.h
@@ -56,6 +56,8 @@ struct btp_ase {
 	struct l_io *io;
 	uint16_t rx_mtu;
 	uint16_t tx_mtu;
+	bool auto_send;
+	struct l_idle *idle_writer;
 };
 
 struct btp_agent {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* RE: client/btpclient: Add BAP/ASCS/PACS support for auto-pts BAP tests
  2026-09-04 13:51 ` [PATCH BlueZ 1/8] client/btpclient: Add BTP_EV_BAP_ASE_FOUND support Frédéric Danis
@ 2026-09-04 17:38   ` bluez.test.bot
  0 siblings, 0 replies; 10+ messages in thread
From: bluez.test.bot @ 2026-09-04 17:38 UTC (permalink / raw)
  To: linux-bluetooth, frederic.danis

[-- Attachment #1: Type: text/plain, Size: 23920 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/project/bluetooth/list/?series=1158120

---Test result---

Test Summary:
CheckPatch                    PASS      3.99 seconds
GitLint                       FAIL      1.84 seconds
BuildEll                      PASS      16.00 seconds
BluezMake                     PASS      488.30 seconds
MakeCheck                     PASS      18.12 seconds
MakeDistcheck                 PASS      122.64 seconds
CheckValgrind                 PASS      176.17 seconds
CheckSmatch                   PASS      233.12 seconds
bluezmakeextell               PASS      74.74 seconds
IncrementalBuild              FAIL      530.91 seconds
ScanBuild                     FAIL      81.94 seconds

Details
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[BlueZ,7/8] client/btpclient: Defer CIG/CIS assignment based on Client/Server role

1: T1 Title exceeds max length (82>80): "[BlueZ,7/8] client/btpclient: Defer CIG/CIS assignment based on Client/Server role"
##############################
Test: IncrementalBuild - FAIL
Desc: Incremental build with the patches in the series
Output:

client/btpclient/bap.c: In function ‘btp_bap_send’:
client/btpclient/bap.c:125:8: error: implicit declaration of function ‘find_ase_by_dir’; did you mean ‘find_ase_by_uuid’? [-Werror=implicit-function-declaration]
  125 |  ase = find_ase_by_dir(dev, BTP_BAP_DIR_SINK);
      |        ^~~~~~~~~~~~~~~
      |        find_ase_by_uuid
client/btpclient/bap.c:125:6: error: assignment to ‘struct btp_ase *’ from ‘int’ makes pointer from integer without a cast [-Werror=int-conversion]
  125 |  ase = find_ase_by_dir(dev, BTP_BAP_DIR_SINK);
      |      ^
client/btpclient/bap.c:126:18: error: ‘struct btp_ase’ has no member named ‘io’
  126 |  if (!ase || !ase->io)
      |                  ^~
client/btpclient/bap.c:129:39: error: ‘struct btp_ase’ has no member named ‘io’
  129 |  bytes_written = write(l_io_get_fd(ase->io), cp->data,
      |                                       ^~
client/btpclient/bap.c:130:22: error: ‘struct btp_ase’ has no member named ‘tx_mtu’
  130 |   cp->data_len <= ase->tx_mtu ? cp->data_len : ase->tx_mtu);
      |                      ^~
client/btpclient/bap.c:130:51: error: ‘struct btp_ase’ has no member named ‘tx_mtu’
  130 |   cp->data_len <= ase->tx_mtu ? cp->data_len : ase->tx_mtu);
      |                                                   ^~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7197: client/btpclient/bap.o] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:4264: all] Error 2
[BlueZ,5/8] client/btpclient: Add BTP_OP_BAP_SEND support

client/btpclient/bap.c: In function ‘btp_bap_send’:
client/btpclient/bap.c:125:8: error: implicit declaration of function ‘find_ase_by_dir’; did you mean ‘find_ase_by_uuid’? [-Werror=implicit-function-declaration]
  125 |  ase = find_ase_by_dir(dev, BTP_BAP_DIR_SINK);
      |        ^~~~~~~~~~~~~~~
      |        find_ase_by_uuid
client/btpclient/bap.c:125:6: error: assignment to ‘struct btp_ase *’ from ‘int’ makes pointer from integer without a cast [-Werror=int-conversion]
  125 |  ase = find_ase_by_dir(dev, BTP_BAP_DIR_SINK);
      |      ^
client/btpclient/bap.c:126:18: error: ‘struct btp_ase’ has no member named ‘io’
  126 |  if (!ase || !ase->io)
      |                  ^~
client/btpclient/bap.c:129:39: error: ‘struct btp_ase’ has no member named ‘io’
  129 |  bytes_written = write(l_io_get_fd(ase->io), cp->data,
      |                                       ^~
client/btpclient/bap.c:130:22: error: ‘struct btp_ase’ has no member named ‘tx_mtu’
  130 |   cp->data_len <= ase->tx_mtu ? cp->data_len : ase->tx_mtu);
      |                      ^~
client/btpclient/bap.c:130:51: error: ‘struct btp_ase’ has no member named ‘tx_mtu’
  130 |   cp->data_len <= ase->tx_mtu ? cp->data_len : ase->tx_mtu);
      |                                                   ^~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7197: client/btpclient/bap.o] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:4264: all] Error 2
##############################
Test: ScanBuild - FAIL
Desc: Run Scan Build
Output:

src/shared/gatt-client.c:447:21: warning: Use of memory after it is freed
        gatt_db_unregister(op->client->db, op->db_id);
                           ^~~~~~~~~~
src/shared/gatt-client.c:692:2: warning: Use of memory after it is freed
        discovery_op_complete(op, false, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:992:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1098:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1292:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1357:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1632:6: warning: Use of memory after it is freed
        if (read_db_hash(op)) {
            ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1637:2: warning: Use of memory after it is freed
        discover_all(op);
        ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1693:56: warning: Use of memory after it is freed
        notify_data->chrc->ccc_write_id = notify_data->att_id = att_id;
                                          ~~~~~~~~~~~~~~~~~~~ ^
src/shared/gatt-client.c:2146:6: warning: Use of memory after it is freed
        if (read_db_hash(op)) {
            ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:2154:8: warning: Use of memory after it is freed
                                                        discovery_op_ref(op),
                                                        ^~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:3332:2: warning: Use of memory after it is freed
        complete_write_long_op(req, success, 0, false);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:3354:2: warning: Use of memory after it is freed
        request_unref(req);
        ^~~~~~~~~~~~~~~~~~
13 warnings generated.
src/shared/rap.c:3554:13: warning: Use of memory after it is freed
        attached = queue_find(sessions, NULL, rap);
                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
src/shared/bap.c:1543:8: warning: Use of memory after it is freed
        bap = bt_bap_ref_safe(bap);
              ^~~~~~~~~~~~~~~~~~~~
src/shared/bap.c:2354:20: warning: Use of memory after it is freed
        return queue_find(stream->bap->streams, NULL, stream);
                          ^~~~~~~~~~~~~~~~~~~~
2 warnings generated.
src/shared/gatt-client.c:447:21: warning: Use of memory after it is freed
        gatt_db_unregister(op->client->db, op->db_id);
                           ^~~~~~~~~~
src/shared/gatt-client.c:692:2: warning: Use of memory after it is freed
        discovery_op_complete(op, false, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:992:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1098:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1292:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1357:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1632:6: warning: Use of memory after it is freed
        if (read_db_hash(op)) {
            ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1637:2: warning: Use of memory after it is freed
        discover_all(op);
        ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1693:56: warning: Use of memory after it is freed
        notify_data->chrc->ccc_write_id = notify_data->att_id = att_id;
                                          ~~~~~~~~~~~~~~~~~~~ ^
src/shared/gatt-client.c:2146:6: warning: Use of memory after it is freed
        if (read_db_hash(op)) {
            ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:2154:8: warning: Use of memory after it is freed
                                                        discovery_op_ref(op),
                                                        ^~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:3332:2: warning: Use of memory after it is freed
        complete_write_long_op(req, success, 0, false);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:3354:2: warning: Use of memory after it is freed
        request_unref(req);
        ^~~~~~~~~~~~~~~~~~
13 warnings generated.
src/shared/rap.c:3554:13: warning: Use of memory after it is freed
        attached = queue_find(sessions, NULL, rap);
                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
tools/hciattach.c:817:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n'
        if ((n = read_hci_event(fd, resp, 10)) < 0) {
             ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/hciattach.c:865:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n'
        if ((n = read_hci_event(fd, resp, 4)) < 0) {
             ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/hciattach.c:887:8: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n'
                if ((n = read_hci_event(fd, resp, 10)) < 0) {
                     ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/hciattach.c:909:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n'
        if ((n = read_hci_event(fd, resp, 4)) < 0) {
             ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/hciattach.c:930:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n'
        if ((n = read_hci_event(fd, resp, 4)) < 0) {
             ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/hciattach.c:974:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n'
        if ((n = read_hci_event(fd, resp, 6)) < 0) {
             ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
6 warnings generated.
src/shared/bap.c:1543:8: warning: Use of memory after it is freed
        bap = bt_bap_ref_safe(bap);
              ^~~~~~~~~~~~~~~~~~~~
src/shared/bap.c:2354:20: warning: Use of memory after it is freed
        return queue_find(stream->bap->streams, NULL, stream);
                          ^~~~~~~~~~~~~~~~~~~~
2 warnings generated.
src/oui.c:50:2: warning: Value stored to 'hwdb' is never read
        hwdb = udev_hwdb_unref(hwdb);
        ^      ~~~~~~~~~~~~~~~~~~~~~
src/oui.c:53:2: warning: Value stored to 'udev' is never read
        udev = udev_unref(udev);
        ^      ~~~~~~~~~~~~~~~~
2 warnings generated.
tools/rfcomm.c:234:3: warning: Value stored to 'i' is never read
                i = execvp(cmdargv[0], cmdargv);
                ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/rfcomm.c:234:7: warning: Null pointer passed to 1st parameter expecting 'nonnull'
                i = execvp(cmdargv[0], cmdargv);
                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/rfcomm.c:354:8: warning: Although the value stored to 'fd' is used in the enclosing expression, the value is never actually read from 'fd'
                if ((fd = open(devname, O_RDONLY | O_NOCTTY)) < 0) {
                     ^    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/rfcomm.c:497:14: warning: Assigned value is garbage or undefined
        req.channel = raddr.rc_channel;
                    ^ ~~~~~~~~~~~~~~~~
tools/rfcomm.c:515:8: warning: Although the value stored to 'fd' is used in the enclosing expression, the value is never actually read from 'fd'
                if ((fd = open(devname, O_RDONLY | O_NOCTTY)) < 0) {
                     ^    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 warnings generated.
tools/ciptool.c:351:7: warning: 5th function call argument is an uninitialized value
        sk = do_connect(ctl, dev_id, &src, &dst, psm, (1 << CMTP_LOOPBACK));
             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
src/sdp-xml.c:129:10: warning: Assigned value is garbage or undefined
                buf[1] = data[i + 1];
                       ^ ~~~~~~~~~~~
src/sdp-xml.c:309:11: warning: Assigned value is garbage or undefined
                        buf[1] = data[i + 1];
                               ^ ~~~~~~~~~~~
src/sdp-xml.c:347:11: warning: Assigned value is garbage or undefined
                        buf[1] = data[i + 1];
                               ^ ~~~~~~~~~~~
3 warnings generated.
tools/sdptool.c:941:26: warning: Result of 'malloc' is converted to a pointer of type 'uint32_t', which is incompatible with sizeof operand type 'int'
                        uint32_t *value_int = malloc(sizeof(int));
                        ~~~~~~~~~~            ^~~~~~ ~~~~~~~~~~~
tools/sdptool.c:980:4: warning: 1st function call argument is an uninitialized value
                        free(allocArray[i]);
                        ^~~~~~~~~~~~~~~~~~~
tools/sdptool.c:3777:2: warning: Potential leak of memory pointed to by 'si.name'
        return add_service(0, &si);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~
tools/sdptool.c:4112:4: warning: Potential leak of memory pointed to by 'context.svc'
                        return -1;
                        ^~~~~~~~~
4 warnings generated.
tools/avtest.c:243:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 3);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:253:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 4);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:262:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 3);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:276:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf,
                                ^     ~~~~~~~~~~~~~~
tools/avtest.c:283:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf,
                                ^     ~~~~~~~~~~~~~~
tools/avtest.c:290:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf,
                                ^     ~~~~~~~~~~~~~~
tools/avtest.c:297:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf,
                                ^     ~~~~~~~~~~~~~~
tools/avtest.c:309:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 4);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:313:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 2);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:322:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 3);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:326:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 2);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:335:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 3);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:342:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 2);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:364:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 4);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:368:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 2);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:377:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 3);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:381:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 2);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:394:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 4);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:398:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 2);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:405:4: warning: Value stored to 'len' is never read
                        len = write(sk, buf, 2);
                        ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:415:4: warning: Value stored to 'len' is never read
                        len = write(sk, buf, 2);
                        ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:580:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 2);
                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:588:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, invalid ? 2 : 3);
                ^     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/avtest.c:602:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 4 + media_transport_size);
                ^     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/avtest.c:615:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 3);
                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:625:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 3);
                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:637:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 3);
                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:652:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 3);
                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:664:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 3);
                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:673:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 3);
                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:680:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 2);
                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:716:2: warning: Value stored to 'len' is never read
        len = write(sk, buf, AVCTP_HEADER_LENGTH + sizeof(play_pressed));
        ^     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
32 warnings generated.
tools/btproxy.c:836:15: warning: Null pointer passed to 1st parameter expecting 'nonnull'
                        tcp_port = atoi(optarg);
                                   ^~~~~~~~~~~~
tools/btproxy.c:839:8: warning: Null pointer passed to 1st parameter expecting 'nonnull'
                        if (strlen(optarg) > 3 && !strncmp(optarg, "hci", 3))
                            ^~~~~~~~~~~~~~
2 warnings generated.
tools/create-image.c:76:3: warning: Value stored to 'fd' is never read
                fd = -1;
                ^    ~~
tools/create-image.c:84:3: warning: Value stored to 'fd' is never read
                fd = -1;
                ^    ~~
tools/create-image.c:92:3: warning: Value stored to 'fd' is never read
                fd = -1;
                ^    ~~
tools/create-image.c:105:2: warning: Value stored to 'fd' is never read
        fd = -1;
        ^    ~~
4 warnings generated.
tools/check-selftest.c:42:3: warning: Value stored to 'ptr' is never read
                ptr = fgets(result, sizeof(result), fp);
                ^     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
tools/btgatt-server.c:1204:2: warning: Value stored to 'argv' is never read
        argv -= optind;
        ^       ~~~~~~
1 warning generated.
tools/btgatt-client.c:1822:2: warning: Value stored to 'argv' is never read
        argv += optind;
        ^       ~~~~~~
1 warning generated.
tools/gatt-service.c:294:2: warning: 2nd function call argument is an uninitialized value
        chr_write(chr, value, len);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
tools/obex-server-tool.c:133:13: warning: Null pointer passed to 1st parameter expecting 'nonnull'
        data->fd = open(name, O_WRONLY | O_CREAT | O_NOCTTY, 0600);
                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/obex-server-tool.c:192:13: warning: Null pointer passed to 1st parameter expecting 'nonnull'
        data->fd = open(name, O_RDONLY | O_NOCTTY, 0);
                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 warnings generated.
client/btpclient/bap.c: In function ‘btp_bap_send’:
client/btpclient/bap.c:125:8: error: implicit declaration of function ‘find_ase_by_dir’; did you mean ‘find_ase_by_uuid’? [-Werror=implicit-function-declaration]
  125 |  ase = find_ase_by_dir(dev, BTP_BAP_DIR_SINK);
      |        ^~~~~~~~~~~~~~~
      |        find_ase_by_uuid
client/btpclient/bap.c:125:6: error: assignment to ‘struct btp_ase *’ from ‘int’ makes pointer from integer without a cast [-Werror=int-conversion]
  125 |  ase = find_ase_by_dir(dev, BTP_BAP_DIR_SINK);
      |      ^
client/btpclient/bap.c:126:18: error: ‘struct btp_ase’ has no member named ‘io’
  126 |  if (!ase || !ase->io)
      |                  ^~
client/btpclient/bap.c:129:39: error: ‘struct btp_ase’ has no member named ‘io’
  129 |  bytes_written = write(l_io_get_fd(ase->io), cp->data,
      |                                       ^~
client/btpclient/bap.c:130:22: error: ‘struct btp_ase’ has no member named ‘tx_mtu’
  130 |   cp->data_len <= ase->tx_mtu ? cp->data_len : ase->tx_mtu);
      |                      ^~
client/btpclient/bap.c:130:51: error: ‘struct btp_ase’ has no member named ‘tx_mtu’
  130 |   cp->data_len <= ase->tx_mtu ? cp->data_len : ase->tx_mtu);
      |                                                   ^~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7197: client/btpclient/bap.o] Error 1
make[1]: *** Waiting for unfinished jobs....
tools/test-runner.c:1367:2: warning: Address of stack memory associated with local variable 'kernel_path' is still referred to by the global variable 'kernel_image' upon returning to the caller.  This will be a dangling reference
        return EXIT_SUCCESS;
        ^~~~~~~~~~~~~~~~~~~
1 warning generated.
make: *** [Makefile:4264: all] Error 2


https://github.com/bluez/bluez/pull/2491

---
Regards,
Linux Bluetooth


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-09-04 17:38 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-04 13:51 [PATCH BlueZ 0/8] client/btpclient: Add BAP/ASCS/PACS support for auto-pts BAP tests Frédéric Danis
2026-09-04 13:51 ` [PATCH BlueZ 1/8] client/btpclient: Add BTP_EV_BAP_ASE_FOUND support Frédéric Danis
2026-09-04 17:38   ` client/btpclient: Add BAP/ASCS/PACS support for auto-pts BAP tests bluez.test.bot
2026-09-04 13:51 ` [PATCH BlueZ 2/8] client/btpclient: Get Codec capabilities on ASE reply Frédéric Danis
2026-09-04 13:51 ` [PATCH BlueZ 3/8] client/btpclient: Add ASCS BTP support Frédéric Danis
2026-09-04 13:51 ` [PATCH BlueZ 4/8] client/btpclient: Add BTP_OP_PACS_SET_LOCATION support Frédéric Danis
2026-09-04 13:51 ` [PATCH BlueZ 5/8] client/btpclient: Add BTP_OP_BAP_SEND support Frédéric Danis
2026-09-04 13:51 ` [PATCH BlueZ 6/8] client/btpclient: Add ASCS support for BAP/UCL/STR/* tests Frédéric Danis
2026-09-04 13:51 ` [PATCH BlueZ 7/8] client/btpclient: Defer CIG/CIS assignment based on Client/Server role Frédéric Danis
2026-09-04 13:51 ` [PATCH BlueZ 8/8] client/btpclient: Add stream auto acquire for BAP/USR/STR/* tests Frédéric Danis

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox