diff mbox series

[v3,03/11] shared/bap: Add initial code for handling BAP

Message ID 20220827000540.113414-4-luiz.dentz@gmail.com
State New
Headers show
Series Initial BAP support | expand

Commit Message

Luiz Augusto von Dentz Aug. 27, 2022, 12:05 a.m. UTC
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

This adds initial code for Basic Audio Profile.
---
 Makefile.am       |    1 +
 src/device.c      |   10 +-
 src/shared/ascs.h |  196 ++
 src/shared/bap.c  | 4776 +++++++++++++++++++++++++++++++++++++++++++++
 src/shared/bap.h  |  269 +++
 5 files changed, 5249 insertions(+), 3 deletions(-)
 create mode 100644 src/shared/ascs.h
 create mode 100644 src/shared/bap.c
 create mode 100644 src/shared/bap.h
diff mbox series

Patch

diff --git a/Makefile.am b/Makefile.am
index ae317bf63b6b..92758ca55816 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -230,6 +230,7 @@  shared_sources = src/shared/io.h src/shared/timeout.h \
 			src/shared/gatt-db.h src/shared/gatt-db.c \
 			src/shared/gap.h src/shared/gap.c \
 			src/shared/log.h src/shared/log.c \
+			src/shared/bap.h src/shared/bap.c src/shared/ascs.h \
 			src/shared/tty.h
 
 if READLINE
diff --git a/src/device.c b/src/device.c
index 44b9033355ce..995d39f2ccee 100644
--- a/src/device.c
+++ b/src/device.c
@@ -3731,9 +3731,12 @@  static void device_add_gatt_services(struct btd_device *device)
 static void device_accept_gatt_profiles(struct btd_device *device)
 {
 	GSList *l;
+	bool initiator = get_initiator(device);
+
+	DBG("initiator %s", initiator ? "true" : "false");
 
 	for (l = device->services; l != NULL; l = g_slist_next(l))
-		service_accept(l->data, get_initiator(device));
+		service_accept(l->data, initiator);
 }
 
 static void device_remove_gatt_service(struct btd_device *device,
@@ -5424,6 +5427,9 @@  int device_connect_le(struct btd_device *dev)
 
 	DBG("Connection attempt to: %s", addr);
 
+	/* Set as initiator */
+	dev->le_state.initiator = true;
+
 	if (dev->le_state.paired)
 		sec_level = BT_IO_SEC_MEDIUM;
 	else
@@ -5461,8 +5467,6 @@  int device_connect_le(struct btd_device *dev)
 
 	/* Keep this, so we can cancel the connection */
 	dev->att_io = io;
-	/* Set as initiator */
-	dev->le_state.initiator = true;
 
 	return 0;
 }
diff --git a/src/shared/ascs.h b/src/shared/ascs.h
new file mode 100644
index 000000000000..fc032048cd9d
--- /dev/null
+++ b/src/shared/ascs.h
@@ -0,0 +1,196 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2020  Intel Corporation. All rights reserved.
+ *
+ */
+
+/* Response Status Code */
+#define BT_ASCS_RSP_SUCCESS		0x00
+#define BT_ASCS_RSP_NOT_SUPPORTED	0x01
+#define BT_ASCS_RSP_TRUNCATED		0x02
+#define BT_ASCS_RSP_INVALID_ASE		0x03
+#define BT_ASCS_RSP_INVALID_ASE_STATE	0x04
+#define BT_ASCS_RSP_INVALID_DIR		0x05
+#define BT_ASCS_RSP_CAP_UNSUPPORTED	0x06
+#define BT_ASCS_RSP_CONF_UNSUPPORTED	0x07
+#define BT_ASCS_RSP_CONF_REJECTED	0x08
+#define BT_ASCS_RSP_CONF_INVALID	0x09
+#define BT_ASCS_RSP_METADATA_UNSUPPORTED 0x0a
+#define BT_ASCS_RSP_METADATA_REJECTED	0x0b
+#define BT_ASCS_RSP_METADATA_INVALID	0x0c
+#define BT_ASCS_RSP_NO_MEM		0x0d
+#define BT_ASCS_RSP_UNSPECIFIED		0x0e
+
+/* Response Reasons */
+#define BT_ASCS_REASON_NONE		0x00
+#define BT_ASCS_REASON_CODEC		0x01
+#define BT_ASCS_REASON_CODEC_DATA	0x02
+#define BT_ASCS_REASON_INTERVAL		0x03
+#define BT_ASCS_REASON_FRAMING		0x04
+#define BT_ASCS_REASON_PHY		0x05
+#define BT_ASCS_REASON_SDU		0x06
+#define BT_ASCS_REASON_RTN		0x07
+#define BT_ASCS_REASON_LATENCY		0x08
+#define BT_ASCS_REASON_PD		0x09
+#define BT_ASCS_REASON_CIS		0x0a
+
+/* Transport QoS Packing */
+#define BT_ASCS_QOS_PACKING_SEQ		0x00
+#define BT_ASCS_QOS_PACKING_INT		0x01
+
+/* Transport QoS Framing */
+#define BT_ASCS_QOS_FRAMING_UNFRAMED	0x00
+#define BT_ASCS_QOS_FRAMING_FRAMED	0x01
+
+/* ASE characteristic states */
+#define BT_ASCS_ASE_STATE_IDLE		0x00
+#define BT_ASCS_ASE_STATE_CONFIG	0x01
+#define BT_ASCS_ASE_STATE_QOS		0x02
+#define BT_ASCS_ASE_STATE_ENABLING	0x03
+#define BT_ASCS_ASE_STATE_STREAMING	0x04
+#define BT_ASCS_ASE_STATE_DISABLING	0x05
+#define BT_ASCS_ASE_STATE_RELEASING	0x06
+
+struct bt_ascs_ase_rsp {
+	uint8_t  ase;
+	uint8_t  code;
+	uint8_t  reason;
+} __packed;
+
+struct bt_ascs_cp_rsp {
+	uint8_t  op;
+	uint8_t  num_ase;
+	struct bt_ascs_ase_rsp rsp[0];
+} __packed;
+
+struct bt_ascs_ase_status {
+	uint8_t  id;
+	uint8_t  state;
+	uint8_t  params[0];
+} __packed;
+
+/* ASE_State = 0x01 (Codec Configured), defined in Table 4.7. */
+struct bt_ascs_ase_status_config {
+	uint8_t  framing;
+	uint8_t  phy;
+	uint8_t  rtn;
+	uint16_t latency;
+	uint8_t  pd_min[3];
+	uint8_t  pd_max[3];
+	uint8_t  ppd_min[3];
+	uint8_t  ppd_max[3];
+	struct bt_bap_codec codec;
+	uint8_t  cc_len;
+	/* LTV-formatted Codec-Specific Configuration */
+	struct bt_ltv cc[0];
+} __packed;
+
+/* ASE_State = 0x02 (QoS Configured), defined in Table 4.8. */
+struct bt_ascs_ase_status_qos {
+	uint8_t  cig_id;
+	uint8_t  cis_id;
+	uint8_t  interval[3];
+	uint8_t  framing;
+	uint8_t  phy;
+	uint16_t sdu;
+	uint8_t  rtn;
+	uint16_t latency;
+	uint8_t  pd[3];
+} __packed;
+
+/* ASE_Status = 0x03 (Enabling), 0x04 (Streaming), or 0x05 (Disabling)
+ * defined in Table 4.9.
+ */
+struct bt_ascs_ase_status_metadata {
+	uint8_t  cig_id;
+	uint8_t  cis_id;
+	uint8_t  len;
+	uint8_t  data[0];
+} __packed;
+
+struct bt_ascs_ase_hdr {
+	uint8_t  op;
+	uint8_t  num;
+} __packed;
+
+#define BT_ASCS_CONFIG			0x01
+
+#define BT_ASCS_CONFIG_LATENCY_LOW	0x01
+#define BT_ASCS_CONFIG_LATENCY_MEDIUM	0x02
+#define BT_ASCS_CONFIG_LATENCY_HIGH	0x03
+
+#define BT_ASCS_CONFIG_PHY_LE_1M	0x01
+#define BT_ASCS_CONFIG_PHY_LE_2M	0x02
+#define BT_ASCS_CONFIG_PHY_LE_CODED	0x03
+
+struct bt_ascs_codec_config {
+	uint8_t len;
+	uint8_t type;
+	uint8_t data[0];
+} __packed;
+
+struct bt_ascs_config {
+	uint8_t  ase;			/* ASE ID */
+	uint8_t  latency;		/* Target Latency */
+	uint8_t  phy;			/* Target PHY */
+	struct bt_bap_codec codec;	/* Codec ID */
+	uint8_t  cc_len;		/* Codec Specific Config Length */
+	/* LTV-formatted Codec-Specific Configuration */
+	struct bt_ascs_codec_config cc[0];
+} __packed;
+
+#define BT_ASCS_QOS			0x02
+
+struct bt_ascs_qos {
+	uint8_t  ase;			/* ASE ID */
+	uint8_t  cig;			/* CIG ID*/
+	uint8_t  cis;			/* CIG ID*/
+	uint8_t  interval[3];		/* Frame interval */
+	uint8_t  framing;		/* Frame framing */
+	uint8_t  phy;			/* PHY */
+	uint16_t sdu;			/* Maximum SDU Size */
+	uint8_t  rtn;			/* Retransmission Effort */
+	uint16_t latency;		/* Transport Latency */
+	uint8_t  pd[3];			/* Presentation Delay */
+} __packed;
+
+#define BT_ASCS_ENABLE			0x03
+
+struct bt_ascs_metadata {
+	uint8_t  ase;			/* ASE ID */
+	uint8_t  len;			/* Metadata length */
+	uint8_t  data[0];		/* LTV-formatted Metadata */
+} __packed;
+
+struct bt_ascs_enable {
+	struct bt_ascs_metadata meta;	/* Metadata */
+} __packed;
+
+#define BT_ASCS_START			0x04
+
+struct bt_ascs_start {
+	uint8_t  ase;			/* ASE ID */
+} __packed;
+
+#define BT_ASCS_DISABLE			0x05
+
+struct bt_ascs_disable {
+	uint8_t  ase;			/* ASE ID */
+} __packed;
+
+#define BT_ASCS_STOP			0x06
+
+struct bt_ascs_stop {
+	uint8_t  ase;			/* ASE ID */
+} __packed;
+
+#define BT_ASCS_METADATA		0x07
+
+#define BT_ASCS_RELEASE			0x08
+
+struct bt_ascs_release {
+	uint8_t  ase;			/* ASE ID */
+} __packed;
diff --git a/src/shared/bap.c b/src/shared/bap.c
new file mode 100644
index 000000000000..29b05db02702
--- /dev/null
+++ b/src/shared/bap.c
@@ -0,0 +1,4776 @@ 
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2020  Intel Corporation. All rights reserved.
+ *
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#define _GNU_SOURCE
+#include <inttypes.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "lib/bluetooth.h"
+#include "lib/uuid.h"
+
+#include "src/shared/queue.h"
+#include "src/shared/util.h"
+#include "src/shared/timeout.h"
+#include "src/shared/att.h"
+#include "src/shared/gatt-db.h"
+#include "src/shared/gatt-server.h"
+#include "src/shared/gatt-client.h"
+#include "src/shared/bap.h"
+#include "src/shared/ascs.h"
+
+/* Maximum number of ASE(s) */
+#define NUM_SINKS 2
+#define NUM_SOURCE 2
+#define NUM_ASES (NUM_SINKS + NUM_SOURCE)
+#define ASE_UUID(_id) (_id < NUM_SINKS ? ASE_SINK_UUID : ASE_SOURCE_UUID)
+#define DBG(_bap, fmt, arg...) \
+	bap_debug(_bap, "%s:%s() " fmt, __FILE__, __func__, ## arg)
+
+#define LTV(_type, _bytes...) \
+	{ \
+		.len = 1 + sizeof((uint8_t []) { _bytes }), \
+		.type = _type, \
+		.data = { _bytes }, \
+	}
+
+#define BAP_PROCESS_TIMEOUT 10
+
+struct bt_bap_pac_changed {
+	bt_bap_pac_func_t added;
+	bt_bap_pac_func_t removed;
+	bt_bap_destroy_func_t destroy;
+	void *data;
+};
+
+struct bt_bap_ready {
+	unsigned int id;
+	bt_bap_ready_func_t func;
+	bt_bap_destroy_func_t destroy;
+	void *data;
+};
+
+struct bt_bap_state {
+	unsigned int id;
+	bt_bap_state_func_t func;
+	bt_bap_connecting_func_t connecting;
+	bt_bap_destroy_func_t destroy;
+	void *data;
+};
+
+struct bt_bap_cb {
+	unsigned int id;
+	bt_bap_func_t attached;
+	bt_bap_func_t detached;
+	void *user_data;
+};
+
+struct bt_pacs {
+	struct bt_bap_db *bdb;
+	struct gatt_db_attribute *service;
+	struct gatt_db_attribute *sink;
+	struct gatt_db_attribute *sink_ccc;
+	struct gatt_db_attribute *sink_loc;
+	struct gatt_db_attribute *sink_loc_ccc;
+	struct gatt_db_attribute *source;
+	struct gatt_db_attribute *source_ccc;
+	struct gatt_db_attribute *source_loc;
+	struct gatt_db_attribute *source_loc_ccc;
+	struct gatt_db_attribute *context;
+	struct gatt_db_attribute *context_ccc;
+	struct gatt_db_attribute *supported_context;
+	struct gatt_db_attribute *supported_context_ccc;
+};
+
+struct bt_ase {
+	struct bt_ascs *ascs;
+	uint8_t  id;
+	struct gatt_db_attribute *attr;
+	struct gatt_db_attribute *ccc;
+};
+
+struct bt_ascs {
+	struct bt_bap_db *bdb;
+	struct gatt_db_attribute *service;
+	struct bt_ase *ase[NUM_ASES];
+	struct gatt_db_attribute *ase_cp;
+	struct gatt_db_attribute *ase_cp_ccc;
+};
+
+struct bt_bap_db {
+	struct gatt_db *db;
+	struct bt_pacs *pacs;
+	struct bt_ascs *ascs;
+	struct queue *sinks;
+	struct queue *sources;
+	struct queue *endpoints;
+};
+
+struct bt_bap_req {
+	unsigned int id;
+	struct bt_bap_stream *stream;
+	uint8_t op;
+	struct queue *group;
+	struct iovec *iov;
+	size_t len;
+	bt_bap_stream_func_t func;
+	void *user_data;
+};
+
+typedef void (*bap_func_t)(struct bt_bap *bap, bool success, uint8_t att_ecode,
+					const uint8_t *value, uint16_t length,
+					void *user_data);
+
+struct bt_bap_pending {
+	unsigned int id;
+	struct bt_bap *bap;
+	bap_func_t func;
+	void *user_data;
+};
+
+typedef void (*bap_notify_t)(struct bt_bap *bap, uint16_t value_handle,
+				const uint8_t *value, uint16_t length,
+				void *user_data);
+
+struct bt_bap_notify {
+	unsigned int id;
+	struct bt_bap *bap;
+	bap_notify_t func;
+	void *user_data;
+};
+
+struct bt_bap {
+	int ref_count;
+	struct bt_bap_db *ldb;
+	struct bt_bap_db *rdb;
+	struct bt_gatt_client *client;
+	struct bt_att *att;
+	struct bt_bap_req *req;
+	unsigned int cp_id;
+
+	unsigned int process_id;
+	struct queue *reqs;
+	struct queue *pending;
+	struct queue *notify;
+	struct queue *streams;
+
+	struct queue *ready_cbs;
+	struct queue *state_cbs;
+
+	bt_bap_debug_func_t debug_func;
+	bt_bap_destroy_func_t debug_destroy;
+	void *debug_data;
+	void *user_data;
+};
+
+struct bt_bap_pac {
+	struct bt_bap_db *bdb;
+	char *name;
+	uint8_t type;
+	uint32_t locations;
+	uint16_t contexts;
+	struct bt_bap_codec codec;
+	struct bt_bap_pac_qos qos;
+	struct iovec *data;
+	struct iovec *metadata;
+	struct bt_bap_pac_ops *ops;
+	void *user_data;
+};
+
+struct bt_bap_endpoint {
+	struct bt_bap_db *bdb;
+	struct bt_bap_stream *stream;
+	struct gatt_db_attribute *attr;
+	uint8_t id;
+	uint8_t dir;
+	uint8_t old_state;
+	uint8_t state;
+	unsigned int state_id;
+};
+
+struct bt_bap_stream_io {
+	struct bt_bap *bap;
+	int ref_count;
+	struct io *io;
+	bool connecting;
+};
+
+struct bt_bap_stream {
+	struct bt_bap *bap;
+	struct bt_bap_endpoint *ep;
+	struct queue *pacs;
+	struct bt_bap_pac *lpac;
+	struct bt_bap_pac *rpac;
+	struct iovec *cc;
+	struct iovec *meta;
+	struct bt_bap_qos qos;
+	struct queue *links;
+	struct bt_bap_stream_io *io;
+	bool client;
+	void *user_data;
+};
+
+/* TODO: Figure out the capabilities types */
+#define BT_CODEC_CAP_PARAMS		0x01
+#define BT_CODEC_CAP_DRM		0x0a
+#define BT_CODEC_CAP_DRM_VALUE		0x0b
+
+struct bt_pac_metadata {
+	uint8_t  len;
+	uint8_t  data[0];
+} __packed;
+
+struct bt_pac {
+	struct bt_bap_codec codec;		/* Codec ID */
+	uint8_t  cc_len;		/* Codec Capabilities Length */
+	struct bt_ltv cc[0];		/* Codec Specific Capabilities */
+	struct bt_pac_metadata meta[0];	/* Metadata */
+} __packed;
+
+struct bt_pacs_read_rsp {
+	uint8_t  num_pac;
+	struct bt_pac pac[0];
+} __packed;
+
+struct bt_pacs_context {
+	uint16_t  snk;
+	uint16_t  src;
+} __packed;
+
+/* Contains local bt_bap_db */
+static struct queue *bap_db;
+static struct queue *pac_cbs;
+static struct queue *bap_cbs;
+static struct queue *sessions;
+
+static bool bap_db_match(const void *data, const void *match_data)
+{
+	const struct bt_bap_db *bdb = data;
+	const struct gatt_db *db = match_data;
+
+	return (bdb->db == db);
+}
+
+static void *iov_add(struct iovec *iov, size_t len)
+{
+	void *data;
+
+	data = iov->iov_base + iov->iov_len;
+	iov->iov_len += len;
+
+	return data;
+}
+
+static void *iov_add_mem(struct iovec *iov, size_t len, const void *d)
+{
+	void *data;
+
+	data = iov->iov_base + iov->iov_len;
+	iov->iov_len += len;
+
+	memcpy(data, d, len);
+
+	return data;
+}
+
+static void iov_free(void *data)
+{
+	struct iovec *iov = data;
+
+	if (!iov)
+		return;
+
+	free(iov->iov_base);
+	free(iov);
+}
+
+static void iov_memcpy(struct iovec *iov, void *src, size_t len)
+{
+	iov->iov_base = realloc(iov->iov_base, len);
+	iov->iov_len = len;
+	memcpy(iov->iov_base, src, len);
+}
+
+static int iov_memcmp(struct iovec *iov1, struct iovec *iov2)
+{
+	if (!iov1)
+		return 1;
+
+	if (!iov2)
+		return -1;
+
+	if (iov1->iov_len != iov2->iov_len)
+		return iov1->iov_len - iov2->iov_len;
+
+	return memcmp(iov1->iov_base, iov2->iov_base, iov1->iov_len);
+}
+
+static struct iovec *iov_dup(struct iovec *iov, size_t len)
+{
+	struct iovec *dup;
+	size_t i;
+
+	if (!iov)
+		return NULL;
+
+	dup = new0(struct iovec, len);
+
+	for (i = 0; i < len; i++)
+		iov_memcpy(&dup[i], iov[i].iov_base, iov[i].iov_len);
+
+	return dup;
+}
+
+unsigned int bt_bap_pac_register(bt_bap_pac_func_t added,
+				bt_bap_pac_func_t removed, void *user_data,
+				bt_bap_destroy_func_t destroy)
+{
+	struct bt_bap_pac_changed *changed;
+
+	changed = new0(struct bt_bap_pac_changed, 1);
+	changed->added = added;
+	changed->removed = removed;
+	changed->destroy = destroy;
+	changed->data = user_data;
+
+	if (!pac_cbs)
+		pac_cbs = queue_new();
+
+	queue_push_tail(pac_cbs, changed);
+
+	return queue_length(pac_cbs);
+}
+
+static void pac_changed_free(void *data)
+{
+	struct bt_bap_pac_changed *changed = data;
+
+	if (changed->destroy)
+		changed->destroy(changed->data);
+
+	free(changed);
+}
+
+struct match_pac_id {
+	unsigned int id;
+	unsigned int index;
+};
+
+static bool match_index(const void *data, const void *match_data)
+{
+	struct match_pac_id *match = (void *)match_data;
+
+	match->index++;
+
+	return match->id == match->index;
+}
+
+bool bt_bap_pac_unregister(unsigned int id)
+{
+	struct bt_bap_pac_changed *changed;
+	struct match_pac_id match;
+
+	memset(&match, 0, sizeof(match));
+	match.id = id;
+
+	changed = queue_remove_if(pac_cbs, match_index, &match);
+	if (!changed)
+		return false;
+
+	pac_changed_free(changed);
+
+	if (queue_isempty(pac_cbs)) {
+		queue_destroy(pac_cbs, NULL);
+		pac_cbs = NULL;
+	}
+
+	return true;
+}
+
+static void pac_foreach(void *data, void *user_data)
+{
+	struct bt_bap_pac *pac = data;
+	struct iovec *iov = user_data;
+	struct bt_pacs_read_rsp *rsp;
+	struct bt_pac *p;
+	struct bt_pac_metadata *meta;
+
+	if (!iov->iov_len) {
+		rsp = iov_add(iov, sizeof(*rsp));
+		rsp->num_pac = 0;
+	} else
+		rsp = iov->iov_base;
+
+	rsp->num_pac++;
+
+	p = iov_add(iov, sizeof(*p));
+	p->codec.id = pac->codec.id;
+
+	if (pac->data) {
+		p->cc_len = pac->data->iov_len;
+		iov_add_mem(iov, p->cc_len, pac->data->iov_base);
+	} else
+		p->cc_len = 0;
+
+	meta = iov_add(iov, sizeof(*meta));
+
+	if (pac->metadata) {
+		meta->len = pac->metadata->iov_len;
+		iov_add_mem(iov, meta->len, pac->metadata->iov_base);
+	} else
+		meta->len = 0;
+}
+
+static void pacs_sink_read(struct gatt_db_attribute *attrib,
+				unsigned int id, uint16_t offset,
+				uint8_t opcode, struct bt_att *att,
+				void *user_data)
+{
+	struct bt_pacs *pacs = user_data;
+	struct bt_bap_db *bdb = pacs->bdb;
+	struct iovec iov;
+	uint8_t value[512];
+
+	memset(value, 0, sizeof(value));
+
+	iov.iov_base = value;
+	iov.iov_len = 0;
+
+	queue_foreach(bdb->sinks, pac_foreach, &iov);
+
+	gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base,
+							iov.iov_len);
+}
+
+static void pacs_sink_loc_read(struct gatt_db_attribute *attrib,
+				unsigned int id, uint16_t offset,
+				uint8_t opcode, struct bt_att *att,
+				void *user_data)
+{
+	uint32_t value = 0x00000003;
+
+	gatt_db_attribute_read_result(attrib, id, 0, (void *) &value,
+							sizeof(value));
+}
+
+static void pacs_source_read(struct gatt_db_attribute *attrib,
+				unsigned int id, uint16_t offset,
+				uint8_t opcode, struct bt_att *att,
+				void *user_data)
+{
+	struct bt_pacs *pacs = user_data;
+	struct bt_bap_db *bdb = pacs->bdb;
+	struct iovec iov;
+	uint8_t value[512];
+
+	memset(value, 0, sizeof(value));
+
+	iov.iov_base = value;
+	iov.iov_len = 0;
+
+	queue_foreach(bdb->sources, pac_foreach, &iov);
+
+	gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base,
+							iov.iov_len);
+}
+
+static void pacs_source_loc_read(struct gatt_db_attribute *attrib,
+				unsigned int id, uint16_t offset,
+				uint8_t opcode, struct bt_att *att,
+				void *user_data)
+{
+	uint32_t value = 0x00000001;
+
+	gatt_db_attribute_read_result(attrib, id, 0, (void *) &value,
+							sizeof(value));
+}
+
+static void pacs_context_read(struct gatt_db_attribute *attrib,
+				unsigned int id, uint16_t offset,
+				uint8_t opcode, struct bt_att *att,
+				void *user_data)
+{
+	struct bt_pacs_context ctx = {
+		.snk = 0x0fff,
+		.src = 0x000e
+	};
+
+	gatt_db_attribute_read_result(attrib, id, 0, (void *) &ctx,
+						sizeof(ctx));
+}
+
+static void pacs_supported_context_read(struct gatt_db_attribute *attrib,
+					unsigned int id, uint16_t offset,
+					uint8_t opcode, struct bt_att *att,
+					void *user_data)
+{
+	struct bt_pacs_context ctx = {
+		.snk = 0x0fff,
+		.src = 0x000e
+	};
+
+	gatt_db_attribute_read_result(attrib, id, 0, (void *) &ctx,
+						sizeof(ctx));
+}
+
+static struct bt_pacs *pacs_new(struct gatt_db *db)
+{
+	struct bt_pacs *pacs;
+	bt_uuid_t uuid;
+
+	if (!db)
+		return NULL;
+
+	pacs = new0(struct bt_pacs, 1);
+
+	/* Populate DB with PACS attributes */
+	bt_uuid16_create(&uuid, PACS_UUID);
+	pacs->service = gatt_db_add_service(db, &uuid, true, 19);
+
+	bt_uuid16_create(&uuid, PAC_SINK_CHRC_UUID);
+	pacs->sink = gatt_db_service_add_characteristic(pacs->service, &uuid,
+					BT_ATT_PERM_READ,
+					BT_GATT_CHRC_PROP_READ |
+					BT_GATT_CHRC_PROP_NOTIFY,
+					pacs_sink_read, NULL,
+					pacs);
+
+	pacs->sink_ccc = gatt_db_service_add_ccc(pacs->service,
+					BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+
+	bt_uuid16_create(&uuid, PAC_SINK_LOC_CHRC_UUID);
+	pacs->sink_loc = gatt_db_service_add_characteristic(pacs->service,
+					&uuid, BT_ATT_PERM_READ,
+					BT_GATT_CHRC_PROP_READ |
+					BT_GATT_CHRC_PROP_NOTIFY,
+					pacs_sink_loc_read, NULL,
+					pacs);
+
+	pacs->sink_loc_ccc = gatt_db_service_add_ccc(pacs->service,
+					BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+
+	bt_uuid16_create(&uuid, PAC_SOURCE_CHRC_UUID);
+	pacs->sink = gatt_db_service_add_characteristic(pacs->service, &uuid,
+					BT_ATT_PERM_READ,
+					BT_GATT_CHRC_PROP_READ |
+					BT_GATT_CHRC_PROP_NOTIFY,
+					pacs_source_read, NULL,
+					pacs);
+
+	pacs->sink_ccc = gatt_db_service_add_ccc(pacs->service,
+					BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+
+	bt_uuid16_create(&uuid, PAC_SOURCE_LOC_CHRC_UUID);
+	pacs->source_loc = gatt_db_service_add_characteristic(pacs->service,
+					&uuid, BT_ATT_PERM_READ,
+					BT_GATT_CHRC_PROP_READ |
+					BT_GATT_CHRC_PROP_NOTIFY,
+					pacs_source_loc_read, NULL,
+					pacs);
+
+	pacs->source_loc_ccc = gatt_db_service_add_ccc(pacs->service,
+					BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+
+	bt_uuid16_create(&uuid, PAC_CONTEXT);
+	pacs->context = gatt_db_service_add_characteristic(pacs->service,
+					&uuid, BT_ATT_PERM_READ,
+					BT_GATT_CHRC_PROP_READ |
+					BT_GATT_CHRC_PROP_NOTIFY,
+					pacs_context_read, NULL, pacs);
+
+	pacs->context_ccc = gatt_db_service_add_ccc(pacs->service,
+					BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+
+	bt_uuid16_create(&uuid, PAC_SUPPORTED_CONTEXT);
+	pacs->supported_context =
+		gatt_db_service_add_characteristic(pacs->service, &uuid,
+					BT_ATT_PERM_READ,
+					BT_GATT_CHRC_PROP_READ |
+					BT_GATT_CHRC_PROP_NOTIFY,
+					pacs_supported_context_read, NULL,
+					pacs);
+
+	pacs->supported_context_ccc = gatt_db_service_add_ccc(pacs->service,
+					BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+
+	gatt_db_service_set_active(pacs->service, true);
+
+	return pacs;
+}
+
+static void bap_debug(struct bt_bap *bap, const char *format, ...)
+{
+	va_list ap;
+
+	if (!bap || !format || !bap->debug_func)
+		return;
+
+	va_start(ap, format);
+	util_debug_va(bap->debug_func, bap->debug_data, format, ap);
+	va_end(ap);
+}
+
+static void bap_disconnected(int err, void *user_data)
+{
+	struct bt_bap *bap = user_data;
+
+	DBG(bap, "bap %p disconnected err %d", bap, err);
+
+	bt_bap_detach(bap);
+}
+
+static struct bt_bap *bap_get_session(struct bt_att *att, struct gatt_db *db)
+{
+	const struct queue_entry *entry;
+	struct bt_bap *bap;
+
+	for (entry = queue_get_entries(sessions); entry; entry = entry->next) {
+		struct bt_bap *bap = entry->data;
+
+		if (att == bt_bap_get_att(bap))
+			return bap;
+	}
+
+	bap = bt_bap_new(db, NULL);
+	bap->att = att;
+
+	bt_att_register_disconnect(att, bap_disconnected, bap, NULL);
+
+	bt_bap_attach(bap, NULL);
+
+	return bap;
+}
+
+static bool bap_endpoint_match(const void *data, const void *match_data)
+{
+	const struct bt_bap_endpoint *ep = data;
+	const struct gatt_db_attribute *attr = match_data;
+
+	return (ep->attr == attr);
+}
+
+static struct bt_bap_endpoint *bap_endpoint_new(struct bt_bap_db *bdb,
+						struct gatt_db_attribute *attr)
+{
+	struct bt_bap_endpoint *ep;
+	bt_uuid_t uuid, source, sink;
+
+	if (!gatt_db_attribute_get_char_data(attr, NULL, NULL, NULL, NULL,
+								&uuid))
+		return NULL;
+
+	ep = new0(struct bt_bap_endpoint, 1);
+	ep->bdb = bdb;
+	ep->attr = attr;
+
+	bt_uuid16_create(&source, ASE_SOURCE_UUID);
+	bt_uuid16_create(&sink, ASE_SINK_UUID);
+
+	if (!bt_uuid_cmp(&source, &uuid))
+		ep->dir = BT_BAP_SOURCE;
+	else if (!bt_uuid_cmp(&sink, &uuid))
+		ep->dir = BT_BAP_SINK;
+
+	return ep;
+}
+
+static struct bt_bap_endpoint *bap_get_endpoint(struct bt_bap_db *db,
+						struct gatt_db_attribute *attr)
+{
+	struct bt_bap_endpoint *ep;
+
+	if (!db || !attr)
+		return NULL;
+
+	ep = queue_find(db->endpoints, bap_endpoint_match, attr);
+	if (ep)
+		return ep;
+
+	ep = bap_endpoint_new(db, attr);
+	if (!ep)
+		return NULL;
+
+	queue_push_tail(db->endpoints, ep);
+
+	return ep;
+}
+
+static bool bap_endpoint_match_id(const void *data, const void *match_data)
+{
+	const struct bt_bap_endpoint *ep = data;
+	uint8_t id = PTR_TO_UINT(match_data);
+
+	return (ep->id == id);
+}
+
+static struct bt_bap_endpoint *bap_get_endpoint_id(struct bt_bap *bap,
+						struct bt_bap_db *db,
+						uint8_t id)
+{
+	struct bt_bap_endpoint *ep;
+	struct gatt_db_attribute *attr = NULL;
+	size_t i;
+
+	if (!bap || !db)
+		return NULL;
+
+	ep = queue_find(db->endpoints, bap_endpoint_match_id, UINT_TO_PTR(id));
+	if (ep)
+		return ep;
+
+	for (i = 0; i < ARRAY_SIZE(db->ascs->ase); i++) {
+		struct bt_ase *ase = db->ascs->ase[i];
+
+		if (id) {
+			if (ase->id != id)
+				continue;
+			attr = ase->attr;
+			break;
+		}
+
+		ep = queue_find(db->endpoints, bap_endpoint_match, ase->attr);
+		if (!ep) {
+			attr = ase->attr;
+			break;
+		}
+	}
+
+	if (!attr)
+		return NULL;
+
+	ep = bap_endpoint_new(db, attr);
+	if (!ep)
+		return NULL;
+
+	ep->id = id;
+	queue_push_tail(db->endpoints, ep);
+
+	return ep;
+}
+
+static void ascs_ase_read(struct gatt_db_attribute *attrib,
+				unsigned int id, uint16_t offset,
+				uint8_t opcode, struct bt_att *att,
+				void *user_data)
+{
+	struct bt_ase *ase = user_data;
+	struct bt_bap *bap = bap_get_session(att, ase->ascs->bdb->db);
+	struct bt_bap_endpoint *ep = bap_get_endpoint(bap->ldb, attrib);
+	struct bt_ascs_ase_status rsp;
+
+	if (!ase || !bap || !ep) {
+		gatt_db_attribute_read_result(attrib, id, BT_ATT_ERROR_UNLIKELY,
+								NULL, 0);
+		return;
+	}
+
+	memset(&rsp, 0, sizeof(rsp));
+
+	/* Initialize Endpoint ID with ASE ID */
+	if (ase->id != ep->id)
+		ep->id = ase->id;
+
+	rsp.id = ep->id;
+	rsp.state = ep->state;
+
+	gatt_db_attribute_read_result(attrib, id, 0, (void *) &rsp,
+							sizeof(rsp));
+}
+
+static void ase_new(struct bt_ascs *ascs, int i)
+{
+	struct bt_ase *ase;
+	bt_uuid_t uuid;
+
+	if (!ascs)
+		return;
+
+	ase = new0(struct bt_ase, 1);
+	ase->ascs = ascs;
+	ase->id = i + 1;
+
+	bt_uuid16_create(&uuid, ASE_UUID(i));
+	ase->attr = gatt_db_service_add_characteristic(ascs->service, &uuid,
+					BT_ATT_PERM_READ,
+					BT_GATT_CHRC_PROP_READ |
+					BT_GATT_CHRC_PROP_NOTIFY,
+					ascs_ase_read, NULL,
+					ase);
+
+	ase->ccc = gatt_db_service_add_ccc(ascs->service,
+					BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+
+	ascs->ase[i] = ase;
+}
+
+static void *iov_pull_mem(struct iovec *iov, size_t len)
+{
+	void *data = iov->iov_base;
+
+	if (iov->iov_len < len)
+		return NULL;
+
+	iov->iov_base += len;
+	iov->iov_len -= len;
+
+	return data;
+}
+
+static bool bap_codec_equal(const struct bt_bap_codec *c1,
+				const struct bt_bap_codec *c2)
+{
+	/* Compare CID and VID if id is 0xff */
+	if (c1->id == 0xff)
+		return !memcmp(c1, c2, sizeof(*c1));
+
+	return c1->id == c2->id;
+}
+
+static struct bt_bap_stream *bap_stream_new(struct bt_bap *bap,
+						struct bt_bap_endpoint *ep,
+						struct bt_bap_pac *lpac,
+						struct bt_bap_pac *rpac,
+						struct iovec *data,
+						bool client)
+{
+	struct bt_bap_stream *stream;
+
+	stream = new0(struct bt_bap_stream, 1);
+	stream->bap = bap;
+	stream->ep = ep;
+	ep->stream = stream;
+	stream->lpac = lpac;
+	stream->rpac = rpac;
+	stream->cc = iov_dup(data, 1);
+	stream->client = client;
+
+	queue_push_tail(bap->streams, stream);
+
+	return stream;
+}
+
+static void stream_notify_config(struct bt_bap_stream *stream)
+{
+	struct bt_bap_endpoint *ep = stream->ep;
+	struct bt_bap_pac *lpac = stream->lpac;
+	struct bt_ascs_ase_status *status;
+	struct bt_ascs_ase_status_config *config;
+	size_t len;
+
+	DBG(stream->bap, "stream %p", stream);
+
+	len = sizeof(*status) + sizeof(*config) + stream->cc->iov_len;
+	status = malloc(len);
+
+	memset(status, 0, len);
+	status->id = ep->id;
+	status->state = ep->state;
+
+	/* Initialize preffered settings if not set */
+	if (!lpac->qos.phy)
+		lpac->qos.phy = 0x02;
+
+	if (!lpac->qos.rtn)
+		lpac->qos.rtn = 0x05;
+
+	if (!lpac->qos.latency)
+		lpac->qos.latency = 10;
+
+	if (!lpac->qos.pd_min)
+		lpac->qos.pd_min = 20000;
+
+	if (!lpac->qos.pd_max)
+		lpac->qos.pd_max = 40000;
+
+	if (!lpac->qos.ppd_min)
+		lpac->qos.ppd_min = lpac->qos.pd_min;
+
+	if (!lpac->qos.ppd_max)
+		lpac->qos.ppd_max = lpac->qos.pd_max;
+
+	/* TODO:Add support for setting preffered settings on bt_bap_pac */
+	config = (void *)status->params;
+	config->framing = lpac->qos.framing;
+	config->phy = lpac->qos.phy;
+	config->rtn = lpac->qos.rtn;
+	config->latency = cpu_to_le16(lpac->qos.latency);
+	put_le24(lpac->qos.pd_min, config->pd_min);
+	put_le24(lpac->qos.pd_max, config->pd_max);
+	put_le24(lpac->qos.ppd_min, config->ppd_min);
+	put_le24(lpac->qos.ppd_max, config->ppd_max);
+	config->codec = lpac->codec;
+	config->cc_len = stream->cc->iov_len;
+	memcpy(config->cc, stream->cc->iov_base, stream->cc->iov_len);
+
+	gatt_db_attribute_notify(ep->attr, (void *) status, len,
+					bt_bap_get_att(stream->bap));
+
+	free(status);
+}
+
+static void stream_notify_qos(struct bt_bap_stream *stream)
+{
+	struct bt_bap_endpoint *ep = stream->ep;
+	struct bt_ascs_ase_status *status;
+	struct bt_ascs_ase_status_qos *qos;
+	size_t len;
+
+	DBG(stream->bap, "stream %p", stream);
+
+	len = sizeof(*status) + sizeof(*qos);
+	status = malloc(len);
+
+	memset(status, 0, len);
+	status->id = ep->id;
+	status->state = ep->state;
+
+	qos = (void *)status->params;
+	qos->cis_id = stream->qos.cis_id;
+	qos->cig_id = stream->qos.cig_id;
+	put_le24(stream->qos.interval, qos->interval);
+	qos->framing = stream->qos.framing;
+	qos->phy = stream->qos.phy;
+	qos->sdu = cpu_to_le16(stream->qos.sdu);
+	qos->rtn = stream->qos.rtn;
+	qos->latency = cpu_to_le16(stream->qos.latency);
+	put_le24(stream->qos.delay, qos->pd);
+
+	gatt_db_attribute_notify(ep->attr, (void *) status, len,
+					bt_bap_get_att(stream->bap));
+
+	free(status);
+}
+
+static void stream_notify_metadata(struct bt_bap_stream *stream)
+{
+	struct bt_bap_endpoint *ep = stream->ep;
+	struct bt_ascs_ase_status *status;
+	struct bt_ascs_ase_status_metadata *meta;
+	size_t len;
+
+	DBG(stream->bap, "stream %p", stream);
+
+	len = sizeof(*status) + sizeof(*meta) + sizeof(stream->meta->iov_len);
+	status = malloc(len);
+
+	memset(status, 0, len);
+	status->id = ep->id;
+	status->state = ep->state;
+
+	meta = (void *)status->params;
+	meta->cis_id = stream->qos.cis_id;
+	meta->cig_id = stream->qos.cig_id;
+
+	if (stream->meta) {
+		meta->len = stream->meta->iov_len;
+		memcpy(meta->data, stream->meta->iov_base, meta->len);
+	}
+
+	gatt_db_attribute_notify(ep->attr, (void *) status, len,
+					bt_bap_get_att(stream->bap));
+
+	free(status);
+}
+
+static void bap_stream_clear_cfm(struct bt_bap_stream *stream)
+{
+	if (!stream->lpac->ops || !stream->lpac->ops->clear)
+		return;
+
+	stream->lpac->ops->clear(stream, stream->lpac->user_data);
+}
+
+static int stream_io_get_fd(struct bt_bap_stream_io *io)
+{
+	if (!io)
+		return -1;
+
+	return io_get_fd(io->io);
+}
+
+static void stream_io_free(void *data)
+{
+	struct bt_bap_stream_io *io = data;
+	int fd;
+
+	fd = stream_io_get_fd(io);
+
+	DBG(io->bap, "fd %d", fd);
+
+	io_destroy(io->io);
+	free(io);
+
+	/* Shutdown using SHUT_WR as SHUT_RDWR cause the socket to HUP
+	 * immediately instead of waiting for Disconnect Complete event.
+	 */
+	shutdown(fd, SHUT_WR);
+}
+
+static void stream_io_unref(struct bt_bap_stream_io *io)
+{
+	if (!io)
+		return;
+
+	if (__sync_sub_and_fetch(&io->ref_count, 1))
+		return;
+
+	stream_io_free(io);
+}
+
+static void bap_stream_unlink(void *data, void *user_data)
+{
+	struct bt_bap_stream *link = data;
+	struct bt_bap_stream *stream = user_data;
+
+	queue_remove(link->links, stream);
+}
+
+static void bap_stream_free(void *data)
+{
+	struct bt_bap_stream *stream = data;
+
+	if (stream->ep)
+		stream->ep->stream = NULL;
+
+	queue_foreach(stream->links, bap_stream_unlink, stream);
+	queue_destroy(stream->links, NULL);
+	stream_io_unref(stream->io);
+	iov_free(stream->cc);
+	iov_free(stream->meta);
+	free(stream);
+}
+
+static void bap_ep_detach(struct bt_bap_endpoint *ep)
+{
+	struct bt_bap_stream *stream = ep->stream;
+
+	if (!stream)
+		return;
+
+	queue_remove(stream->bap->streams, stream);
+	bap_stream_clear_cfm(stream);
+
+	stream->ep = NULL;
+	ep->stream = NULL;
+	bap_stream_free(stream);
+}
+
+static void bap_stream_io_link(void *data, void *user_data)
+{
+	struct bt_bap_stream *stream = data;
+	struct bt_bap_stream *link = user_data;
+
+	bt_bap_stream_io_link(stream, link);
+}
+
+static void bap_stream_update_io_links(struct bt_bap_stream *stream)
+{
+	struct bt_bap *bap = stream->bap;
+
+	DBG(bap, "stream %p", stream);
+
+	queue_foreach(bap->streams, bap_stream_io_link, stream);
+}
+
+static struct bt_bap_stream_io *stream_io_ref(struct bt_bap_stream_io *io)
+{
+	if (!io)
+		return NULL;
+
+	__sync_fetch_and_add(&io->ref_count, 1);
+
+	return io;
+}
+
+static struct bt_bap_stream_io *stream_io_new(struct bt_bap *bap, int fd)
+{
+	struct io *io;
+	struct bt_bap_stream_io *sio;
+
+	io = io_new(fd);
+	if (!io)
+		return NULL;
+
+	DBG(bap, "fd %d", fd);
+
+	sio = new0(struct bt_bap_stream_io, 1);
+	sio->bap = bap;
+	sio->io = io;
+
+	return stream_io_ref(sio);
+}
+
+static void stream_find_io(void *data, void *user_data)
+{
+	struct bt_bap_stream *stream = data;
+	struct bt_bap_stream_io **io = user_data;
+
+	if (*io)
+		return;
+
+	*io = stream->io;
+}
+
+static struct bt_bap_stream_io *stream_get_io(struct bt_bap_stream *stream)
+{
+	struct bt_bap_stream_io *io;
+
+	if (!stream)
+		return NULL;
+
+	if (stream->io)
+		return stream->io;
+
+	io = NULL;
+	queue_foreach(stream->links, stream_find_io, &io);
+
+	return io;
+}
+
+static bool stream_io_disconnected(struct io *io, void *user_data);
+
+static bool bap_stream_io_attach(struct bt_bap_stream *stream, int fd,
+							bool connecting)
+{
+	struct bt_bap_stream_io *io;
+
+	io = stream_get_io(stream);
+	if (io) {
+		if (fd == stream_io_get_fd(io)) {
+			if (!stream->io)
+				stream->io = stream_io_ref(io);
+
+			io->connecting = connecting;
+			return true;
+		}
+
+		DBG(stream->bap, "stream %p io already set", stream);
+		return false;
+	}
+
+	DBG(stream->bap, "stream %p connecting %s", stream,
+				connecting ? "true" : "false");
+
+	io = stream_io_new(stream->bap, fd);
+	if (!io)
+		return false;
+
+	io->connecting = connecting;
+	stream->io = io;
+	io_set_disconnect_handler(io->io, stream_io_disconnected, stream, NULL);
+
+	return true;
+}
+
+static bool match_stream_io(const void *data, const void *user_data)
+{
+	const struct bt_bap_stream *stream = data;
+	const struct bt_bap_stream_io *io = user_data;
+
+	if (!stream->io)
+		return false;
+
+	return stream->io == io;
+}
+
+static bool bap_stream_io_detach(struct bt_bap_stream *stream)
+{
+	struct bt_bap_stream *link;
+	struct bt_bap_stream_io *io;
+
+	if (!stream->io)
+		return false;
+
+	DBG(stream->bap, "stream %p", stream);
+
+	io = stream->io;
+	stream->io = NULL;
+
+	link = queue_find(stream->links, match_stream_io, io);
+	if (link) {
+		/* Detach link if in QoS state */
+		if (link->ep->state == BT_ASCS_ASE_STATE_QOS)
+			bap_stream_io_detach(link);
+	}
+
+	stream_io_unref(io);
+
+	return true;
+}
+
+static void bap_stream_set_io(void *data, void *user_data)
+{
+	struct bt_bap_stream *stream = data;
+	int fd = PTR_TO_INT(user_data);
+	bool ret;
+
+	if (fd >= 0)
+		ret = bap_stream_io_attach(stream, fd, false);
+	else
+		ret = bap_stream_io_detach(stream);
+
+	if (!ret)
+		return;
+
+	switch (stream->ep->state) {
+	case BT_BAP_STREAM_STATE_ENABLING:
+		if (fd < 0)
+			bt_bap_stream_disable(stream, false, NULL, NULL);
+		else
+			bt_bap_stream_start(stream, NULL, NULL);
+		break;
+	case BT_BAP_STREAM_STATE_DISABLING:
+		if (fd < 0)
+			bt_bap_stream_stop(stream, NULL, NULL);
+		break;
+	}
+}
+
+static void bap_stream_state_changed(struct bt_bap_stream *stream)
+{
+	struct bt_bap *bap = stream->bap;
+	const struct queue_entry *entry;
+
+	DBG(bap, "stream %p dir 0x%02x: %s -> %s", stream,
+			bt_bap_stream_get_dir(stream),
+			bt_bap_stream_statestr(stream->ep->old_state),
+			bt_bap_stream_statestr(stream->ep->state));
+
+	bt_bap_ref(bap);
+
+	/* Pre notification updates */
+	switch (stream->ep->state) {
+	case BT_ASCS_ASE_STATE_IDLE:
+		break;
+	case BT_ASCS_ASE_STATE_CONFIG:
+		bap_stream_update_io_links(stream);
+		break;
+	case BT_ASCS_ASE_STATE_DISABLING:
+		bap_stream_io_detach(stream);
+		break;
+	case BT_ASCS_ASE_STATE_QOS:
+		if (stream->io && !stream->io->connecting)
+			bap_stream_io_detach(stream);
+		else
+			bap_stream_update_io_links(stream);
+		break;
+	case BT_ASCS_ASE_STATE_ENABLING:
+	case BT_ASCS_ASE_STATE_STREAMING:
+		break;
+	}
+
+	for (entry = queue_get_entries(bap->state_cbs); entry;
+							entry = entry->next) {
+		struct bt_bap_state *state = entry->data;
+
+		if (state->func)
+			state->func(stream, stream->ep->old_state,
+					stream->ep->state, state->data);
+	}
+
+	/* Post notification updates */
+	switch (stream->ep->state) {
+	case BT_ASCS_ASE_STATE_IDLE:
+		bap_ep_detach(stream->ep);
+		break;
+	case BT_ASCS_ASE_STATE_QOS:
+		break;
+	case BT_ASCS_ASE_STATE_ENABLING:
+		if (bt_bap_stream_get_io(stream))
+			bt_bap_stream_start(stream, NULL, NULL);
+		break;
+	case BT_ASCS_ASE_STATE_DISABLING:
+		if (!bt_bap_stream_get_io(stream))
+			bt_bap_stream_stop(stream, NULL, NULL);
+		break;
+	}
+
+	bt_bap_unref(bap);
+}
+
+static void stream_set_state(struct bt_bap_stream *stream, uint8_t state)
+{
+	struct bt_bap_endpoint *ep = stream->ep;
+
+	ep->old_state = ep->state;
+	ep->state = state;
+
+	if (stream->client)
+		goto done;
+
+	switch (ep->state) {
+	case BT_ASCS_ASE_STATE_IDLE:
+		break;
+	case BT_ASCS_ASE_STATE_CONFIG:
+		stream_notify_config(stream);
+		break;
+	case BT_ASCS_ASE_STATE_QOS:
+		stream_notify_qos(stream);
+		break;
+	case BT_ASCS_ASE_STATE_ENABLING:
+	case BT_ASCS_ASE_STATE_STREAMING:
+	case BT_ASCS_ASE_STATE_DISABLING:
+		stream_notify_metadata(stream);
+		break;
+	}
+
+done:
+	bap_stream_state_changed(stream);
+}
+
+static void ascs_ase_rsp_add(struct iovec *iov, uint8_t id,
+					uint8_t code, uint8_t reason)
+{
+	struct bt_ascs_cp_rsp *cp;
+	struct bt_ascs_ase_rsp *rsp;
+
+	if (!iov)
+		return;
+
+	cp = iov->iov_base;
+
+	if (cp->num_ase == 0xff)
+		return;
+
+	switch (code) {
+	/* If the Response_Code value is 0x01 or 0x02, Number_of_ASEs shall be
+	 * set to 0xFF.
+	 */
+	case BT_ASCS_RSP_NOT_SUPPORTED:
+	case BT_ASCS_RSP_TRUNCATED:
+		cp->num_ase = 0xff;
+		break;
+	default:
+		cp->num_ase++;
+		break;
+	}
+
+	iov->iov_len += sizeof(*rsp);
+	iov->iov_base = realloc(iov->iov_base, iov->iov_len);
+
+	rsp = iov->iov_base + (iov->iov_len - sizeof(*rsp));
+	rsp->ase = id;
+	rsp->code = code;
+	rsp->reason = reason;
+}
+
+static void ascs_ase_rsp_add_errno(struct iovec *iov, uint8_t id, int err)
+{
+	struct bt_ascs_cp_rsp *rsp = iov->iov_base;
+
+	switch (err) {
+	case -ENOBUFS:
+	case -ENOMEM:
+		return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_NO_MEM,
+						BT_ASCS_REASON_NONE);
+	case -EINVAL:
+		switch (rsp->op) {
+		case BT_ASCS_CONFIG:
+		/* Fallthrough */
+		case BT_ASCS_QOS:
+			return ascs_ase_rsp_add(iov, id,
+						BT_ASCS_RSP_CONF_INVALID,
+						BT_ASCS_REASON_NONE);
+		case BT_ASCS_ENABLE:
+		/* Fallthrough */
+		case BT_ASCS_METADATA:
+			return ascs_ase_rsp_add(iov, id,
+						BT_ASCS_RSP_METADATA_INVALID,
+						BT_ASCS_REASON_NONE);
+		default:
+			return ascs_ase_rsp_add(iov, id,
+						BT_ASCS_RSP_UNSPECIFIED,
+						BT_ASCS_REASON_NONE);
+		}
+	case -ENOTSUP:
+		switch (rsp->op) {
+		case BT_ASCS_CONFIG:
+		/* Fallthrough */
+		case BT_ASCS_QOS:
+			return ascs_ase_rsp_add(iov, id,
+						BT_ASCS_RSP_CONF_UNSUPPORTED,
+						BT_ASCS_REASON_NONE);
+		case BT_ASCS_ENABLE:
+		/* Fallthrough */
+		case BT_ASCS_METADATA:
+			return ascs_ase_rsp_add(iov, id,
+					BT_ASCS_RSP_METADATA_UNSUPPORTED,
+					BT_ASCS_REASON_NONE);
+		default:
+			return ascs_ase_rsp_add(iov, id,
+						BT_ASCS_RSP_NOT_SUPPORTED,
+						BT_ASCS_REASON_NONE);
+		}
+	case -EBADMSG:
+		return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_INVALID_ASE_STATE,
+						BT_ASCS_REASON_NONE);
+	case -ENOMSG:
+		return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_TRUNCATED,
+						BT_ASCS_REASON_NONE);
+	default:
+		return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_UNSPECIFIED,
+						BT_ASCS_REASON_NONE);
+	}
+}
+
+static void ascs_ase_rsp_success(struct iovec *iov, uint8_t id)
+{
+	return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_SUCCESS,
+					BT_ASCS_REASON_NONE);
+}
+
+static void ep_config_cb(struct bt_bap_stream *stream, int err)
+{
+	if (err)
+		return;
+
+	stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG);
+}
+
+static uint8_t stream_config(struct bt_bap_stream *stream, struct iovec *cc,
+							struct iovec *rsp)
+{
+	struct bt_bap_pac *pac = stream->lpac;
+
+	DBG(stream->bap, "stream %p", stream);
+
+	/* TODO: Wait for pac->ops response */
+	ascs_ase_rsp_success(rsp, stream->ep->id);
+
+	if (!iov_memcmp(stream->cc, cc)) {
+		stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG);
+		return 0;
+	}
+
+	iov_free(stream->cc);
+	stream->cc = iov_dup(cc, 1);
+
+	if (pac->ops && pac->ops->config)
+		pac->ops->config(stream, cc, NULL, ep_config_cb,
+						pac->user_data);
+
+	return 0;
+}
+
+static uint8_t ep_config(struct bt_bap_endpoint *ep, struct bt_bap *bap,
+				 struct bt_ascs_config *req,
+				 struct iovec *iov, struct iovec *rsp)
+{
+	struct iovec cc;
+	const struct queue_entry *e;
+
+	DBG(bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir);
+
+	switch (ep->state) {
+	/* Valid only if ASE_State field = 0x00 (Idle) */
+	case BT_ASCS_ASE_STATE_IDLE:
+	 /* or 0x01 (Codec Configured) */
+	case BT_ASCS_ASE_STATE_CONFIG:
+	 /* or 0x02 (QoS Configured) */
+	case BT_ASCS_ASE_STATE_QOS:
+		break;
+	default:
+		DBG(bap, "Invalid state %s", bt_bap_stream_statestr(ep->state));
+		ascs_ase_rsp_add(rsp, req->ase,
+				BT_ASCS_RSP_INVALID_ASE_STATE,
+				BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	if (iov->iov_len < req->cc_len)
+		return BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
+
+	cc.iov_base = iov_pull_mem(iov, req->cc_len);
+	cc.iov_len = req->cc_len;
+
+	if (!bap_print_cc(cc.iov_base, cc.iov_len, bap->debug_func,
+						bap->debug_data)) {
+		ascs_ase_rsp_add(rsp, req->ase,
+				BT_ASCS_RSP_CONF_INVALID,
+				BT_ASCS_REASON_CODEC_DATA);
+		return 0;
+	}
+
+	switch (ep->dir) {
+	case BT_BAP_SINK:
+		e = queue_get_entries(bap->ldb->sinks);
+		break;
+	case BT_BAP_SOURCE:
+		e = queue_get_entries(bap->ldb->sources);
+		break;
+	default:
+		e = NULL;
+	}
+
+	for (; e; e = e->next) {
+		struct bt_bap_pac *pac = e->data;
+
+		if (!bap_codec_equal(&req->codec, &pac->codec))
+			continue;
+
+		if (!ep->stream)
+			ep->stream = bap_stream_new(bap, ep, pac, NULL, NULL,
+									false);
+
+		break;
+	}
+
+	if (!e) {
+		ascs_ase_rsp_add(rsp, req->ase,
+				BT_ASCS_RSP_CONF_INVALID,
+				BT_ASCS_REASON_CODEC);
+		return 0;
+	}
+
+	return stream_config(ep->stream, &cc, rsp);
+}
+
+static uint8_t ascs_config(struct bt_ascs *ascs, struct bt_bap *bap,
+				struct iovec *iov, struct iovec *rsp)
+{
+	struct bt_bap_endpoint *ep;
+	struct bt_ascs_config *req;
+
+	req = iov_pull_mem(iov, sizeof(*req));
+
+	DBG(bap, "codec 0x%02x phy 0x%02x latency %u", req->codec.id, req->phy,
+							req->latency);
+
+	ep = bap_get_endpoint_id(bap, bap->ldb, req->ase);
+	if (!ep) {
+		DBG(bap, "Invalid ASE ID 0x%02x", req->ase);
+		ascs_ase_rsp_add(rsp, req->ase,
+				BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	return ep_config(ep, bap, req, iov, rsp);
+}
+
+static uint8_t stream_qos(struct bt_bap_stream *stream, struct bt_bap_qos *qos,
+							struct iovec *rsp)
+{
+	DBG(stream->bap, "stream %p", stream);
+
+	ascs_ase_rsp_success(rsp, stream->ep->id);
+
+	if (memcmp(&stream->qos, qos, sizeof(*qos)))
+		stream->qos = *qos;
+
+	stream_set_state(stream, BT_BAP_STREAM_STATE_QOS);
+
+	return 0;
+}
+
+static uint8_t ep_qos(struct bt_bap_endpoint *ep, struct bt_bap *bap,
+			 struct bt_bap_qos *qos, struct iovec *rsp)
+{
+	DBG(bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir);
+
+	switch (ep->state) {
+	/* Valid only if ASE_State field = 0x01 (Codec Configured) */
+	case BT_ASCS_ASE_STATE_CONFIG:
+	 /* or 0x02 (QoS Configured) */
+	case BT_ASCS_ASE_STATE_QOS:
+		break;
+	default:
+		DBG(bap, "Invalid state %s", bt_bap_stream_statestr(ep->state));
+		ascs_ase_rsp_add(rsp, ep->id,
+				BT_ASCS_RSP_INVALID_ASE_STATE,
+				BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	if (!ep->stream) {
+		DBG(bap, "No stream found");
+		ascs_ase_rsp_add(rsp, ep->id,
+				BT_ASCS_RSP_INVALID_ASE_STATE,
+				BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	return stream_qos(ep->stream, qos, rsp);
+}
+
+static uint8_t ascs_qos(struct bt_ascs *ascs, struct bt_bap *bap,
+					struct iovec *iov, struct iovec *rsp)
+{
+	struct bt_bap_endpoint *ep;
+	struct bt_ascs_qos *req;
+	struct bt_bap_qos qos;
+
+	req = iov_pull_mem(iov, sizeof(*req));
+
+	memset(&qos, 0, sizeof(qos));
+
+	qos.cig_id = req->cig;
+	qos.cis_id = req->cis;
+	qos.interval = get_le24(req->interval);
+	qos.framing = req->framing;
+	qos.phy = req->phy;
+	qos.sdu = le16_to_cpu(req->sdu);
+	qos.rtn = req->rtn;
+	qos.latency = le16_to_cpu(req->latency);
+	qos.delay = get_le24(req->pd);
+
+	DBG(bap, "CIG 0x%02x CIS 0x%02x interval %u framing 0x%02x "
+			"phy 0x%02x SDU %u rtn %u latency %u pd %u",
+			req->cig, req->cis, qos.interval, qos.framing, qos.phy,
+			qos.sdu, qos.rtn, qos.latency, qos.delay);
+
+	ep = bap_get_endpoint_id(bap, bap->ldb, req->ase);
+	if (!ep) {
+		DBG(bap, "%s: Invalid ASE ID 0x%02x", req->ase);
+		ascs_ase_rsp_add(rsp, req->ase,
+				BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	return ep_qos(ep, bap, &qos, rsp);
+}
+
+static uint8_t stream_enable(struct bt_bap_stream *stream, struct iovec *meta,
+							struct iovec *rsp)
+{
+	DBG(stream->bap, "stream %p", stream);
+
+	ascs_ase_rsp_success(rsp, stream->ep->id);
+
+	iov_free(stream->meta);
+	stream->meta = iov_dup(meta, 1);
+
+	stream_set_state(stream, BT_BAP_STREAM_STATE_ENABLING);
+
+	/* Sink can autonomously for to Streaming state if io already exits */
+	if (stream->io && stream->ep->dir == BT_BAP_SINK)
+		stream_set_state(stream, BT_BAP_STREAM_STATE_STREAMING);
+
+	return 0;
+}
+
+static bool bap_print_ltv(const char *label, void *data, size_t len,
+				util_debug_func_t func, void *user_data)
+{
+	struct iovec iov = {
+		.iov_base = data,
+		.iov_len = len,
+	};
+	int i;
+
+	util_debug(func, user_data, "Length %zu", iov.iov_len);
+
+	for (i = 0; iov.iov_len > 1; i++) {
+		struct bt_ltv *ltv = iov_pull_mem(&iov, sizeof(*ltv));
+		uint8_t *data;
+
+		if (!ltv) {
+			util_debug(func, user_data, "Unable to parse %s",
+								label);
+			return false;
+		}
+
+		util_debug(func, user_data, "%s #%u: len %u type %u",
+					label, i, ltv->len, ltv->type);
+
+		data = iov_pull_mem(&iov, ltv->len - 1);
+		if (!data) {
+			util_debug(func, user_data, "Unable to parse %s",
+								label);
+			return false;
+		}
+
+		util_hexdump(' ', ltv->value, ltv->len - 1, func, user_data);
+	}
+
+	return true;
+}
+
+static bool bap_print_metadata(void *data, size_t len, util_debug_func_t func,
+						void *user_data)
+{
+	return bap_print_ltv("Metadata", data, len, func, user_data);
+}
+
+static uint8_t ep_enable(struct bt_bap_endpoint *ep, struct bt_bap *bap,
+			struct bt_ascs_enable *req, struct iovec *iov,
+			struct iovec *rsp)
+{
+	struct iovec meta;
+
+	DBG(bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir);
+
+	switch (ep->state) {
+	/* Valid only if ASE_State field = 0x02 (QoS Configured) */
+	case BT_ASCS_ASE_STATE_QOS:
+		break;
+	default:
+		DBG(bap, "Invalid state %s", bt_bap_stream_statestr(ep->state));
+		ascs_ase_rsp_add(rsp, ep->id,
+				BT_ASCS_RSP_INVALID_ASE_STATE,
+				BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	meta.iov_base = iov_pull_mem(iov, req->meta.len);
+	meta.iov_len = req->meta.len;
+
+	if (!bap_print_metadata(meta.iov_base, meta.iov_len, bap->debug_func,
+							bap->debug_data)) {
+		ascs_ase_rsp_add(rsp, ep->id,
+				BT_ASCS_RSP_METADATA_INVALID,
+				BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	if (!ep->stream) {
+		DBG(bap, "No stream found");
+		ascs_ase_rsp_add(rsp, ep->id,
+				BT_ASCS_RSP_INVALID_ASE_STATE,
+				BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	return stream_enable(ep->stream, iov, rsp);
+}
+
+static uint8_t ascs_enable(struct bt_ascs *ascs, struct bt_bap *bap,
+				struct iovec *iov, struct iovec *rsp)
+{
+	struct bt_bap_endpoint *ep;
+	struct bt_ascs_enable *req;
+
+	req = iov_pull_mem(iov, sizeof(*req));
+
+	ep = bap_get_endpoint_id(bap, bap->ldb, req->meta.ase);
+	if (!ep) {
+		DBG(bap, "Invalid ASE ID 0x%02x", req->meta.ase);
+		ascs_ase_rsp_add(rsp, req->meta.ase,
+				BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	return ep_enable(ep, bap, req, iov, rsp);
+}
+
+static uint8_t stream_start(struct bt_bap_stream *stream, struct iovec *rsp)
+{
+	DBG(stream->bap, "stream %p", stream);
+
+	ascs_ase_rsp_success(rsp, stream->ep->id);
+
+	stream_set_state(stream, BT_BAP_STREAM_STATE_STREAMING);
+
+	return 0;
+}
+
+static uint8_t ep_start(struct bt_bap_endpoint *ep, struct iovec *rsp)
+{
+	struct bt_bap_stream *stream = ep->stream;
+
+	DBG(stream->bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir);
+
+	switch (ep->state) {
+	/* Valid only if ASE_State field = 0x03 (Enabling) */
+	case BT_ASCS_ASE_STATE_ENABLING:
+		break;
+	default:
+		DBG(ep->stream->bap, "Invalid state %s",
+				bt_bap_stream_statestr(ep->state));
+		ascs_ase_rsp_add(rsp, ep->id,
+				BT_ASCS_RSP_INVALID_ASE_STATE,
+				BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	/* If the ASE_ID  written by the client represents a Sink ASE, the
+	 * server shall not accept the Receiver Start Ready operation for that
+	 * ASE. The server shall send a notification of the ASE Control Point
+	 * characteristic to the client, and the server shall set the
+	 * Response_Code value for that ASE to 0x05 (Invalid ASE direction).
+	 */
+	if (ep->dir == BT_BAP_SINK) {
+		ascs_ase_rsp_add(rsp, ep->id,
+				BT_ASCS_RSP_INVALID_DIR, BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	return stream_start(ep->stream, rsp);
+}
+
+static uint8_t ascs_start(struct bt_ascs *ascs, struct bt_bap *bap,
+				struct iovec *iov, struct iovec *rsp)
+{
+	struct bt_bap_endpoint *ep;
+	struct bt_ascs_start *req;
+
+	req = iov_pull_mem(iov, sizeof(*req));
+
+	ep = bap_get_endpoint_id(bap, bap->ldb, req->ase);
+	if (!ep) {
+		DBG(bap, "Invalid ASE ID 0x%02x", req->ase);
+		ascs_ase_rsp_add(rsp, req->ase,
+				BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	if (!ep->stream) {
+		DBG(bap, "No stream found for %p", ep);
+		ascs_ase_rsp_add(rsp, ep->id,
+				BT_ASCS_RSP_INVALID_ASE_STATE,
+				BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	return ep_start(ep, rsp);
+}
+
+static uint8_t stream_disable(struct bt_bap_stream *stream, struct iovec *rsp)
+{
+	DBG(stream->bap, "stream %p", stream);
+
+	if (!stream || stream->ep->state == BT_BAP_STREAM_STATE_QOS)
+		return 0;
+
+	ascs_ase_rsp_success(rsp, stream->ep->id);
+
+	/* Sink can autonomously transit to QOS while source needs to go to
+	 * Disabling until BT_ASCS_STOP is received.
+	 */
+	if (stream->ep->dir == BT_BAP_SINK)
+		stream_set_state(stream, BT_BAP_STREAM_STATE_QOS);
+	else
+		stream_set_state(stream, BT_BAP_STREAM_STATE_DISABLING);
+
+	return 0;
+}
+
+static uint8_t ep_disable(struct bt_bap_endpoint *ep, struct iovec *rsp)
+{
+	struct bt_bap_stream *stream = ep->stream;
+
+	DBG(stream->bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir);
+
+	switch (ep->state) {
+	/* Valid only if ASE_State field = 0x03 (Enabling) */
+	case BT_ASCS_ASE_STATE_ENABLING:
+	 /* or 0x04 (Streaming) */
+	case BT_ASCS_ASE_STATE_STREAMING:
+		break;
+	default:
+		DBG(stream->bap, "Invalid state %s",
+				bt_bap_stream_statestr(ep->state));
+		ascs_ase_rsp_add(rsp, ep->id,
+				BT_ASCS_RSP_INVALID_ASE_STATE,
+				BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	return stream_disable(ep->stream, rsp);
+}
+
+static uint8_t ascs_disable(struct bt_ascs *ascs, struct bt_bap *bap,
+				struct iovec *iov, struct iovec *rsp)
+{
+	struct bt_bap_endpoint *ep;
+	struct bt_ascs_disable *req;
+
+	req = iov_pull_mem(iov, sizeof(*req));
+
+	ep = bap_get_endpoint_id(bap, bap->ldb, req->ase);
+	if (!ep) {
+		DBG(bap, "Invalid ASE ID 0x%02x", req->ase);
+		ascs_ase_rsp_add(rsp, req->ase,
+				BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	if (!ep->stream) {
+		DBG(bap, "No stream found");
+		ascs_ase_rsp_add(rsp, ep->id,
+				BT_ASCS_RSP_INVALID_ASE_STATE,
+				BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	return ep_disable(ep, rsp);
+}
+
+static uint8_t stream_stop(struct bt_bap_stream *stream, struct iovec *rsp)
+{
+	DBG(stream->bap, "stream %p", stream);
+
+	if (!stream)
+		return 0;
+
+	ascs_ase_rsp_success(rsp, stream->ep->id);
+
+	stream_set_state(stream, BT_BAP_STREAM_STATE_QOS);
+
+	return 0;
+}
+
+static uint8_t ep_stop(struct bt_bap_endpoint *ep, struct iovec *rsp)
+{
+	struct bt_bap_stream *stream = ep->stream;
+
+	DBG(stream->bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir);
+
+	switch (ep->state) {
+	/* Valid only if ASE_State field = 0x05 (Disabling) */
+	case BT_ASCS_ASE_STATE_DISABLING:
+		break;
+	default:
+		DBG(stream->bap, "Invalid state %s",
+				bt_bap_stream_statestr(ep->state));
+		ascs_ase_rsp_add(rsp, ep->id,
+				BT_ASCS_RSP_INVALID_ASE_STATE,
+				BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	/* If the ASE_ID  written by the client represents a Sink ASE, the
+	 * server shall not accept the Receiver Stop Ready operation for that
+	 * ASE. The server shall send a notification of the ASE Control Point
+	 * characteristic to the client, and the server shall set the
+	 * Response_Code value for that ASE to 0x05 (Invalid ASE direction).
+	 */
+	if (ep->dir == BT_BAP_SINK) {
+		ascs_ase_rsp_add(rsp, ep->id,
+				BT_ASCS_RSP_INVALID_DIR, BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	return stream_stop(ep->stream, rsp);
+}
+
+static uint8_t ascs_stop(struct bt_ascs *ascs, struct bt_bap *bap,
+				struct iovec *iov, struct iovec *rsp)
+{
+	struct bt_bap_endpoint *ep;
+	struct bt_ascs_stop *req;
+
+	req = iov_pull_mem(iov, sizeof(*req));
+
+	ep = bap_get_endpoint_id(bap, bap->ldb, req->ase);
+	if (!ep) {
+		DBG(bap, "Invalid ASE ID 0x%02x", req->ase);
+		ascs_ase_rsp_add(rsp, req->ase,
+				BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	if (!ep->stream) {
+		DBG(bap, "No stream found");
+		ascs_ase_rsp_add(rsp, ep->id,
+				BT_ASCS_RSP_INVALID_ASE_STATE,
+				BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	return ep_stop(ep, rsp);
+}
+
+static uint8_t stream_metadata(struct bt_bap_stream *stream, struct iovec *meta,
+						struct iovec *rsp)
+{
+	DBG(stream->bap, "stream %p", stream);
+
+	ascs_ase_rsp_success(rsp, stream->ep->id);
+
+	iov_free(stream->meta);
+	stream->meta = iov_dup(meta, 1);
+
+	return 0;
+}
+
+static uint8_t ep_metadata(struct bt_bap_endpoint *ep, struct iovec *meta,
+						struct iovec *rsp)
+{
+	struct bt_bap_stream *stream = ep->stream;
+
+	DBG(stream->bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir);
+
+	switch (ep->state) {
+	/* Valid only if ASE_State field = 0x03 (Enabling) */
+	case BT_ASCS_ASE_STATE_ENABLING:
+	 /* or 0x04 (Streaming) */
+	case BT_ASCS_ASE_STATE_STREAMING:
+		break;
+	default:
+		DBG(stream->bap, "Invalid state %s",
+				bt_bap_stream_statestr(ep->state));
+		ascs_ase_rsp_add(rsp, ep->id,
+				BT_ASCS_RSP_INVALID_ASE_STATE,
+				BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	return stream_metadata(ep->stream, meta, rsp);
+}
+
+static uint8_t ascs_metadata(struct bt_ascs *ascs, struct bt_bap *bap,
+				struct iovec *iov, struct iovec *rsp)
+{
+	struct bt_bap_endpoint *ep;
+	struct bt_ascs_metadata *req;
+
+	req = iov_pull_mem(iov, sizeof(*req));
+
+	ep = bap_get_endpoint_id(bap, bap->ldb, req->ase);
+	if (!ep) {
+		DBG(bap, "Invalid ASE ID 0x%02x", req->ase);
+		ascs_ase_rsp_add(rsp, req->ase,
+				BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	if (!ep->stream) {
+		DBG(bap, "No stream found");
+		ascs_ase_rsp_add(rsp, ep->id,
+				BT_ASCS_RSP_INVALID_ASE_STATE,
+				BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	return ep_metadata(ep, iov, rsp);
+}
+
+static uint8_t stream_release(struct bt_bap_stream *stream, struct iovec *rsp)
+{
+	struct bt_bap_pac *pac;
+
+	DBG(stream->bap, "stream %p", stream);
+
+	ascs_ase_rsp_success(rsp, stream->ep->id);
+
+	pac = stream->lpac;
+	if (pac->ops && pac->ops->clear)
+		pac->ops->clear(stream, pac->user_data);
+
+	stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE);
+
+	return 0;
+}
+
+static uint8_t ascs_release(struct bt_ascs *ascs, struct bt_bap *bap,
+				struct iovec *iov, struct iovec *rsp)
+{
+	struct bt_bap_endpoint *ep;
+	struct bt_ascs_release *req;
+
+	req = iov_pull_mem(iov, sizeof(*req));
+
+	ep = bap_get_endpoint_id(bap, bap->ldb, req->ase);
+	if (!ep) {
+		DBG(bap, "Invalid ASE ID 0x%02x", req->ase);
+		ascs_ase_rsp_add(rsp, req->ase,
+				BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	if (!ep->stream) {
+		DBG(bap, "No stream found");
+		ascs_ase_rsp_add(rsp, ep->id,
+				BT_ASCS_RSP_INVALID_ASE_STATE,
+				BT_ASCS_REASON_NONE);
+		return 0;
+	}
+
+	return stream_release(ep->stream, rsp);
+}
+
+#define ASCS_OP(_str, _op, _size, _func) \
+	{ \
+		.str = _str, \
+		.op = _op, \
+		.size = _size, \
+		.func = _func, \
+	}
+
+struct ascs_op_handler {
+	const char *str;
+	uint8_t  op;
+	size_t   size;
+	uint8_t  (*func)(struct bt_ascs *ascs, struct bt_bap *bap,
+			struct iovec *iov, struct iovec *rsp);
+} handlers[] = {
+	ASCS_OP("Codec Config", BT_ASCS_CONFIG,
+		sizeof(struct bt_ascs_config), ascs_config),
+	ASCS_OP("QoS Config", BT_ASCS_QOS,
+		sizeof(struct bt_ascs_qos), ascs_qos),
+	ASCS_OP("Enable", BT_ASCS_ENABLE, sizeof(struct bt_ascs_enable),
+		ascs_enable),
+	ASCS_OP("Receiver Start Ready", BT_ASCS_START,
+		sizeof(struct bt_ascs_start), ascs_start),
+	ASCS_OP("Disable", BT_ASCS_DISABLE,
+		sizeof(struct bt_ascs_disable), ascs_disable),
+	ASCS_OP("Receiver Stop Ready", BT_ASCS_STOP,
+		sizeof(struct bt_ascs_stop), ascs_stop),
+	ASCS_OP("Update Metadata", BT_ASCS_METADATA,
+		sizeof(struct bt_ascs_metadata), ascs_metadata),
+	ASCS_OP("Release", BT_ASCS_RELEASE,
+		sizeof(struct bt_ascs_release), ascs_release),
+	{}
+};
+
+static struct iovec *ascs_ase_cp_rsp_new(uint8_t op)
+{
+	struct bt_ascs_cp_rsp *rsp;
+	struct iovec *iov;
+
+	iov = new0(struct iovec, 1);
+	rsp = new0(struct bt_ascs_cp_rsp, 1);
+	rsp->op = op;
+	iov->iov_base = rsp;
+	iov->iov_len = sizeof(*rsp);
+
+	return iov;
+}
+
+static void ascs_ase_cp_write(struct gatt_db_attribute *attrib,
+				unsigned int id, uint16_t offset,
+				const uint8_t *value, size_t len,
+				uint8_t opcode, struct bt_att *att,
+				void *user_data)
+{
+	struct bt_ascs *ascs = user_data;
+	struct bt_bap *bap = bap_get_session(att, ascs->bdb->db);
+	struct iovec iov = {
+		.iov_base = (void *) value,
+		.iov_len = len,
+	};
+	struct bt_ascs_ase_hdr *hdr;
+	struct ascs_op_handler *handler;
+	uint8_t ret = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED;
+	struct iovec *rsp;
+
+	if (offset) {
+		DBG(bap, "invalid offset %u", offset);
+		gatt_db_attribute_write_result(attrib, id,
+						BT_ATT_ERROR_INVALID_OFFSET);
+		return;
+	}
+
+	if (len < sizeof(*hdr)) {
+		DBG(bap, "invalid len %u < %u sizeof(*hdr)", len,
+							sizeof(*hdr));
+		gatt_db_attribute_write_result(attrib, id,
+				BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN);
+		return;
+	}
+
+	hdr = iov_pull_mem(&iov, sizeof(*hdr));
+	rsp = ascs_ase_cp_rsp_new(hdr->op);
+
+	for (handler = handlers; handler && handler->str; handler++) {
+		if (handler->op != hdr->op)
+			continue;
+
+		if (iov.iov_len < hdr->num * handler->size) {
+			DBG(bap, "invalid len %u < %u "
+				  "hdr->num * handler->size", len,
+				  hdr->num * handler->size);
+			ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
+			goto respond;
+		}
+
+		break;
+	}
+
+	if (handler && handler->str) {
+		int i;
+
+		DBG(bap, "%s", handler->str);
+
+		for (i = 0; i < hdr->num; i++)
+			ret = handler->func(ascs, bap, &iov, rsp);
+	} else {
+		DBG(bap, "Unknown opcode 0x%02x", hdr->op);
+		ascs_ase_rsp_add_errno(rsp, 0x00, -ENOTSUP);
+	}
+
+respond:
+	if (ret == BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN)
+		ascs_ase_rsp_add_errno(rsp, 0x00, -ENOMSG);
+
+	gatt_db_attribute_notify(attrib, rsp->iov_base, rsp->iov_len, att);
+	gatt_db_attribute_write_result(attrib, id, ret);
+
+	iov_free(rsp);
+}
+
+static struct bt_ascs *ascs_new(struct gatt_db *db)
+{
+	struct bt_ascs *ascs;
+	bt_uuid_t uuid;
+	int i;
+
+	if (!db)
+		return NULL;
+
+	ascs = new0(struct bt_ascs, 1);
+
+	/* Populate DB with ASCS attributes */
+	bt_uuid16_create(&uuid, ASCS_UUID);
+	ascs->service = gatt_db_add_service(db, &uuid, true,
+						4 + (NUM_ASES * 3));
+
+	for (i = 0; i < NUM_ASES; i++)
+		ase_new(ascs, i);
+
+	bt_uuid16_create(&uuid, ASE_CP_UUID);
+	ascs->ase_cp = gatt_db_service_add_characteristic(ascs->service,
+					&uuid,
+					BT_ATT_PERM_WRITE,
+					BT_GATT_CHRC_PROP_WRITE |
+					BT_GATT_CHRC_PROP_NOTIFY,
+					NULL, ascs_ase_cp_write,
+					ascs);
+
+	ascs->ase_cp_ccc = gatt_db_service_add_ccc(ascs->service,
+					BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+
+	gatt_db_service_set_active(ascs->service, true);
+
+	return ascs;
+}
+
+static struct bt_bap_db *bap_db_new(struct gatt_db *db)
+{
+	struct bt_bap_db *bdb;
+
+	if (!db)
+		return NULL;
+
+	bdb = new0(struct bt_bap_db, 1);
+	bdb->db = gatt_db_ref(db);
+	bdb->sinks = queue_new();
+	bdb->sources = queue_new();
+	bdb->endpoints = queue_new();
+
+	if (!bap_db)
+		bap_db = queue_new();
+
+	bdb->pacs = pacs_new(db);
+	bdb->pacs->bdb = bdb;
+
+	bdb->ascs = ascs_new(db);
+	bdb->ascs->bdb = bdb;
+
+	queue_push_tail(bap_db, bdb);
+
+	return bdb;
+}
+
+static struct bt_bap_db *bap_get_db(struct gatt_db *db)
+{
+	struct bt_bap_db *bdb;
+
+	bdb = queue_find(bap_db, bap_db_match, db);
+	if (bdb)
+		return bdb;
+
+	return bap_db_new(db);
+}
+
+static struct bt_pacs *bap_get_pacs(struct bt_bap *bap)
+{
+	if (!bap)
+		return NULL;
+
+	if (bap->rdb->pacs)
+		return bap->rdb->pacs;
+
+	bap->rdb->pacs = new0(struct bt_pacs, 1);
+	bap->rdb->pacs->bdb = bap->rdb;
+
+	return bap->rdb->pacs;
+}
+
+static struct bt_ascs *bap_get_ascs(struct bt_bap *bap)
+{
+	if (!bap)
+		return NULL;
+
+	if (bap->rdb->ascs)
+		return bap->rdb->ascs;
+
+	bap->rdb->ascs = new0(struct bt_ascs, 1);
+	bap->rdb->ascs->bdb = bap->rdb;
+
+	return bap->rdb->ascs;
+}
+
+static struct bt_bap_pac *bap_pac_new(struct bt_bap_db *bdb, const char *name,
+					uint8_t type,
+					struct bt_bap_codec *codec,
+					struct bt_bap_pac_qos *qos,
+					struct iovec *data,
+					struct iovec *metadata)
+{
+	struct bt_bap_pac *pac;
+
+	pac = new0(struct bt_bap_pac, 1);
+	pac->bdb = bdb;
+	pac->name = name ? strdup(name) : NULL;
+	pac->type = type;
+	pac->codec = *codec;
+	pac->data = iov_dup(data, 1);
+	pac->metadata = iov_dup(metadata, 1);
+
+	if (qos)
+		pac->qos = *qos;
+
+	return pac;
+}
+
+static void bap_pac_free(void *data)
+{
+	struct bt_bap_pac *pac = data;
+
+	free(pac->name);
+	iov_free(pac->metadata);
+	iov_free(pac->data);
+	free(pac);
+}
+
+static void bap_add_sink(struct bt_bap_pac *pac)
+{
+	struct iovec iov;
+	uint8_t value[512];
+
+	queue_push_tail(pac->bdb->sinks, pac);
+
+	memset(value, 0, sizeof(value));
+
+	iov.iov_base = value;
+	iov.iov_len = 0;
+
+	queue_foreach(pac->bdb->sinks, pac_foreach, &iov);
+
+	gatt_db_attribute_notify(pac->bdb->pacs->sink, iov.iov_base,
+				iov.iov_len, NULL);
+}
+
+static void bap_add_source(struct bt_bap_pac *pac)
+{
+	struct iovec iov;
+	uint8_t value[512];
+
+	queue_push_tail(pac->bdb->sources, pac);
+
+	memset(value, 0, sizeof(value));
+
+	iov.iov_base = value;
+	iov.iov_len = 0;
+
+	queue_foreach(pac->bdb->sinks, pac_foreach, &iov);
+
+	gatt_db_attribute_notify(pac->bdb->pacs->source, iov.iov_base,
+				iov.iov_len, NULL);
+}
+
+static void notify_pac_added(void *data, void *user_data)
+{
+	struct bt_bap_pac_changed *changed = data;
+	struct bt_bap_pac *pac = user_data;
+
+	if (changed->added)
+		changed->added(pac, changed->data);
+}
+
+struct bt_bap_pac *bt_bap_add_vendor_pac(struct gatt_db *db,
+					const char *name, uint8_t type,
+					uint8_t id, uint16_t cid, uint16_t vid,
+					struct bt_bap_pac_qos *qos,
+					struct iovec *data,
+					struct iovec *metadata)
+{
+	struct bt_bap_db *bdb;
+	struct bt_bap_pac *pac;
+	struct bt_bap_codec codec;
+
+	if (!db)
+		return NULL;
+
+	bdb = bap_get_db(db);
+	if (!bdb)
+		return NULL;
+
+	codec.id = id;
+	codec.cid = cid;
+	codec.vid = vid;
+
+	pac = bap_pac_new(bdb, name, type, &codec, qos, data, metadata);
+
+	switch (type) {
+	case BT_BAP_SINK:
+		bap_add_sink(pac);
+		break;
+	case BT_BAP_SOURCE:
+		bap_add_source(pac);
+		break;
+	default:
+		bap_pac_free(pac);
+		return NULL;
+	}
+
+	queue_foreach(pac_cbs, notify_pac_added, pac);
+
+	return pac;
+}
+
+struct bt_bap_pac *bt_bap_add_pac(struct gatt_db *db, const char *name,
+					uint8_t type, uint8_t id,
+					struct bt_bap_pac_qos *qos,
+					struct iovec *data,
+					struct iovec *metadata)
+{
+	return bt_bap_add_vendor_pac(db, name, type, id, 0x0000, 0x0000, qos,
+							data, metadata);
+}
+
+uint8_t bt_bap_pac_get_type(struct bt_bap_pac *pac)
+{
+	if (!pac)
+		return 0x00;
+
+	return pac->type;
+}
+
+static void notify_pac_removed(void *data, void *user_data)
+{
+	struct bt_bap_pac_changed *changed = data;
+	struct bt_bap_pac *pac = user_data;
+
+	if (changed->removed)
+		changed->removed(pac, changed->data);
+}
+
+bool bt_bap_pac_set_ops(struct bt_bap_pac *pac, struct bt_bap_pac_ops *ops,
+					void *user_data)
+{
+	if (!pac)
+		return false;
+
+	pac->ops = ops;
+	pac->user_data = user_data;
+
+	return true;
+}
+
+static bool match_stream_lpac(const void *data, const void *user_data)
+{
+	const struct bt_bap_stream *stream = data;
+	const struct bt_bap_pac *pac = user_data;
+
+	return stream->lpac == pac;
+}
+
+static void remove_streams(void *data, void *user_data)
+{
+	struct bt_bap *bap = data;
+	struct bt_bap_pac *pac = user_data;
+	struct bt_bap_stream *stream;
+
+	stream = queue_remove_if(bap->streams, match_stream_lpac, pac);
+	if (stream)
+		bt_bap_stream_release(stream, NULL, NULL);
+}
+
+bool bt_bap_remove_pac(struct bt_bap_pac *pac)
+{
+	if (!pac)
+		return false;
+
+	if (queue_remove_if(pac->bdb->sinks, NULL, pac))
+		goto found;
+
+	if (queue_remove_if(pac->bdb->sources, NULL, pac))
+		goto found;
+
+	return false;
+
+found:
+	queue_foreach(sessions, remove_streams, pac);
+	queue_foreach(pac_cbs, notify_pac_removed, pac);
+	bap_pac_free(pac);
+	return true;
+}
+
+static void bap_db_free(void *data)
+{
+	struct bt_bap_db *bdb = data;
+
+	if (!bdb)
+		return;
+
+	queue_destroy(bdb->sinks, bap_pac_free);
+	queue_destroy(bdb->sources, bap_pac_free);
+	queue_destroy(bdb->endpoints, free);
+	gatt_db_unref(bdb->db);
+
+	free(bdb->pacs);
+	free(bdb->ascs);
+	free(bdb);
+}
+
+static void bap_ready_free(void *data)
+{
+	struct bt_bap_ready *ready = data;
+
+	if (ready->destroy)
+		ready->destroy(ready->data);
+
+	free(ready);
+}
+
+static void bap_state_free(void *data)
+{
+	struct bt_bap_state *state = data;
+
+	if (state->destroy)
+		state->destroy(state->data);
+
+	free(state);
+}
+
+static void bap_req_free(void *data)
+{
+	struct bt_bap_req *req = data;
+	size_t i;
+
+	queue_destroy(req->group, bap_req_free);
+
+	for (i = 0; i < req->len; i++)
+		free(req->iov[i].iov_base);
+
+	free(req->iov);
+	free(req);
+}
+
+static void bap_detached(void *data, void *user_data)
+{
+	struct bt_bap_cb *cb = data;
+	struct bt_bap *bap = user_data;
+
+	cb->detached(bap, cb->user_data);
+}
+
+static void bap_free(void *data)
+{
+	struct bt_bap *bap = data;
+
+	bt_bap_detach(bap);
+
+	bap_db_free(bap->rdb);
+
+	queue_destroy(bap->ready_cbs, bap_ready_free);
+	queue_destroy(bap->state_cbs, bap_state_free);
+
+	queue_destroy(bap->reqs, bap_req_free);
+	queue_destroy(bap->pending, NULL);
+	queue_destroy(bap->notify, NULL);
+	queue_destroy(bap->streams, bap_stream_free);
+
+	free(bap);
+}
+
+unsigned int bt_bap_register(bt_bap_func_t attached, bt_bap_func_t detached,
+							void *user_data)
+{
+	struct bt_bap_cb *cb;
+	static unsigned int id;
+
+	if (!attached && !detached)
+		return 0;
+
+	if (!bap_cbs)
+		bap_cbs = queue_new();
+
+	cb = new0(struct bt_bap_cb, 1);
+	cb->id = ++id ? id : ++id;
+	cb->attached = attached;
+	cb->detached = detached;
+	cb->user_data = user_data;
+
+	queue_push_tail(bap_cbs, cb);
+
+	return cb->id;
+}
+
+static bool match_id(const void *data, const void *match_data)
+{
+	const struct bt_bap_cb *cb = data;
+	unsigned int id = PTR_TO_UINT(match_data);
+
+	return (cb->id == id);
+}
+
+bool bt_bap_unregister(unsigned int id)
+{
+	struct bt_bap_cb *cb;
+
+	cb = queue_remove_if(bap_cbs, match_id, UINT_TO_PTR(id));
+	if (!cb)
+		return false;
+
+	free(cb);
+
+	return true;
+}
+
+static void bap_attached(void *data, void *user_data)
+{
+	struct bt_bap_cb *cb = data;
+	struct bt_bap *bap = user_data;
+
+	cb->attached(bap, cb->user_data);
+}
+
+struct bt_bap *bt_bap_new(struct gatt_db *ldb, struct gatt_db *rdb)
+{
+	struct bt_bap *bap;
+	struct bt_bap_db *bdb;
+
+	if (!ldb)
+		return NULL;
+
+	bdb = bap_get_db(ldb);
+	if (!bdb)
+		return NULL;
+
+	bap = new0(struct bt_bap, 1);
+	bap->ldb = bdb;
+	bap->reqs = queue_new();
+	bap->pending = queue_new();
+	bap->notify = queue_new();
+	bap->ready_cbs = queue_new();
+	bap->streams = queue_new();
+	bap->state_cbs = queue_new();
+
+	if (!rdb)
+		goto done;
+
+	bdb = new0(struct bt_bap_db, 1);
+	bdb->db = gatt_db_ref(rdb);
+	bdb->sinks = queue_new();
+	bdb->sources = queue_new();
+	bdb->endpoints = queue_new();
+
+	bap->rdb = bdb;
+
+done:
+	return bt_bap_ref(bap);
+}
+
+bool bt_bap_set_user_data(struct bt_bap *bap, void *user_data)
+{
+	if (!bap)
+		return false;
+
+	bap->user_data = user_data;
+
+	return true;
+}
+
+void *bt_bap_get_user_data(struct bt_bap *bap)
+{
+	if (!bap)
+		return NULL;
+
+	return bap->user_data;
+}
+
+struct bt_att *bt_bap_get_att(struct bt_bap *bap)
+{
+	if (!bap)
+		return NULL;
+
+	if (bap->att)
+		return bap->att;
+
+	return bt_gatt_client_get_att(bap->client);
+}
+
+struct bt_bap *bt_bap_ref(struct bt_bap *bap)
+{
+	if (!bap)
+		return NULL;
+
+	__sync_fetch_and_add(&bap->ref_count, 1);
+
+	return bap;
+}
+
+void bt_bap_unref(struct bt_bap *bap)
+{
+	if (!bap)
+		return;
+
+	if (__sync_sub_and_fetch(&bap->ref_count, 1))
+		return;
+
+	bap_free(bap);
+}
+
+static void bap_notify_ready(struct bt_bap *bap)
+{
+	const struct queue_entry *entry;
+
+	if (!queue_isempty(bap->pending))
+		return;
+
+	bt_bap_ref(bap);
+
+	for (entry = queue_get_entries(bap->ready_cbs); entry;
+							entry = entry->next) {
+		struct bt_bap_ready *ready = entry->data;
+
+		ready->func(bap, ready->data);
+	}
+
+	bt_bap_unref(bap);
+}
+
+bool bap_print_cc(void *data, size_t len, util_debug_func_t func,
+						void *user_data)
+{
+	return bap_print_ltv("CC", data, len, func, user_data);
+}
+
+static void bap_parse_pacs(struct bt_bap *bap, uint8_t type,
+				struct queue *queue,
+				const uint8_t *value,
+				uint16_t len)
+{
+	struct bt_pacs_read_rsp *rsp;
+	struct iovec iov = {
+		.iov_base = (void *) value,
+		.iov_len = len,
+	};
+	int i;
+
+	rsp = iov_pull_mem(&iov, sizeof(*rsp));
+	if (!rsp) {
+		DBG(bap, "Unable to parse PAC");
+		return;
+	}
+
+	DBG(bap, "PAC(s) %u", rsp->num_pac);
+
+	for (i = 0; i < rsp->num_pac; i++) {
+		struct bt_bap_pac *pac;
+		struct bt_pac *p;
+		struct bt_ltv *cc;
+		struct bt_pac_metadata *meta;
+		struct iovec data, metadata;
+
+		p = iov_pull_mem(&iov, sizeof(*p));
+		if (!p) {
+			DBG(bap, "Unable to parse PAC");
+			return;
+		}
+
+		pac = NULL;
+
+		if (!bap_print_cc(iov.iov_base, p->cc_len, bap->debug_func,
+					bap->debug_data))
+			return;
+
+		cc = iov_pull_mem(&iov, p->cc_len);
+		if (!cc) {
+			DBG(bap, "Unable to parse PAC codec capabilities");
+			return;
+		}
+
+		meta = iov_pull_mem(&iov, sizeof(*meta));
+		if (!meta) {
+			DBG(bap, "Unable to parse PAC metadata");
+			return;
+		}
+
+		data.iov_len = p->cc_len;
+		data.iov_base = cc;
+
+		metadata.iov_len = meta->len;
+		metadata.iov_base = meta->data;
+
+		iov_pull_mem(&iov, meta->len);
+
+		pac = bap_pac_new(bap->rdb, NULL, type, &p->codec, NULL, &data,
+								&metadata);
+		if (!pac)
+			continue;
+
+		DBG(bap, "PAC #%u: type %u codec 0x%02x cc_len %u meta_len %u",
+			i, type, p->codec.id, p->cc_len, meta->len);
+
+		queue_push_tail(queue, pac);
+	}
+}
+
+static void read_source_pac(struct bt_bap *bap, bool success, uint8_t att_ecode,
+				const uint8_t *value, uint16_t length,
+				void *user_data)
+{
+	if (!success) {
+		DBG(bap, "Unable to read Source PAC: error 0x%02x", att_ecode);
+		return;
+	}
+
+	bap_parse_pacs(bap, BT_BAP_SOURCE, bap->rdb->sources, value, length);
+}
+
+static void read_sink_pac(struct bt_bap *bap, bool success, uint8_t att_ecode,
+				const uint8_t *value, uint16_t length,
+				void *user_data)
+{
+	if (!success) {
+		DBG(bap, "Unable to read Sink PAC: error 0x%02x", att_ecode);
+		return;
+	}
+
+	bap_parse_pacs(bap, BT_BAP_SINK, bap->rdb->sinks, value, length);
+}
+
+static void read_source_pac_loc(struct bt_bap *bap, bool success,
+				uint8_t att_ecode, const uint8_t *value,
+				uint16_t length, void *user_data)
+{
+	struct bt_pacs *pacs = bap_get_pacs(bap);
+
+	if (!success) {
+		DBG(bap, "Unable to read Source PAC Location: error 0x%02x",
+								att_ecode);
+		return;
+	}
+
+	gatt_db_attribute_write(pacs->source_loc, 0, value, length, 0, NULL,
+							NULL, NULL);
+}
+
+static void read_sink_pac_loc(struct bt_bap *bap, bool success,
+				uint8_t att_ecode, const uint8_t *value,
+				uint16_t length, void *user_data)
+{
+	struct bt_pacs *pacs = bap_get_pacs(bap);
+
+	if (!success) {
+		DBG(bap, "Unable to read Sink PAC Location: error 0x%02x",
+								att_ecode);
+		return;
+	}
+
+	gatt_db_attribute_write(pacs->sink_loc, 0, value, length, 0, NULL,
+							NULL, NULL);
+}
+
+static void read_pac_context(struct bt_bap *bap, bool success,
+				uint8_t att_ecode, const uint8_t *value,
+				uint16_t length, void *user_data)
+{
+	struct bt_pacs *pacs = bap_get_pacs(bap);
+
+	if (!success) {
+		DBG(bap, "Unable to read PAC Context: error 0x%02x", att_ecode);
+		return;
+	}
+
+	gatt_db_attribute_write(pacs->context, 0, value, length, 0, NULL,
+							NULL, NULL);
+}
+
+static void read_pac_supported_context(struct bt_bap *bap, bool success,
+					uint8_t att_ecode, const uint8_t *value,
+					uint16_t length, void *user_data)
+{
+	struct bt_pacs *pacs = bap_get_pacs(bap);
+
+	if (!success) {
+		DBG(bap, "Unable to read PAC Supproted Context: error 0x%02x",
+								att_ecode);
+		return;
+	}
+
+	gatt_db_attribute_write(pacs->supported_context, 0, value, length, 0,
+							NULL, NULL, NULL);
+}
+
+static void bap_pending_destroy(void *data)
+{
+	struct bt_bap_pending *pending = data;
+	struct bt_bap *bap = pending->bap;
+
+	if (queue_remove_if(bap->pending, NULL, pending))
+		free(pending);
+
+	bap_notify_ready(bap);
+}
+
+static void bap_pending_complete(bool success, uint8_t att_ecode,
+				const uint8_t *value, uint16_t length,
+				void *user_data)
+{
+	struct bt_bap_pending *pending = user_data;
+
+	if (pending->func)
+		pending->func(pending->bap, success, att_ecode, value, length,
+						pending->user_data);
+}
+
+static void bap_read_value(struct bt_bap *bap, uint16_t value_handle,
+				bap_func_t func, void *user_data)
+{
+	struct bt_bap_pending *pending;
+
+	pending = new0(struct bt_bap_pending, 1);
+	pending->bap = bap;
+	pending->func = func;
+	pending->user_data = user_data;
+
+	pending->id = bt_gatt_client_read_value(bap->client, value_handle,
+						bap_pending_complete, pending,
+						bap_pending_destroy);
+	if (!pending->id) {
+		DBG(bap, "Unable to send Read request");
+		free(pending);
+		return;
+	}
+
+	queue_push_tail(bap->pending, pending);
+}
+
+static void foreach_pacs_char(struct gatt_db_attribute *attr, void *user_data)
+{
+	struct bt_bap *bap = user_data;
+	uint16_t value_handle;
+	bt_uuid_t uuid, uuid_sink, uuid_source, uuid_sink_loc, uuid_source_loc;
+	bt_uuid_t uuid_context, uuid_supported_context;
+	struct bt_pacs *pacs;
+
+	if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle,
+						NULL, NULL, &uuid))
+		return;
+
+	bt_uuid16_create(&uuid_sink, PAC_SINK_CHRC_UUID);
+	bt_uuid16_create(&uuid_source, PAC_SOURCE_CHRC_UUID);
+	bt_uuid16_create(&uuid_sink_loc, PAC_SINK_LOC_CHRC_UUID);
+	bt_uuid16_create(&uuid_source_loc, PAC_SOURCE_LOC_CHRC_UUID);
+	bt_uuid16_create(&uuid_context, PAC_CONTEXT);
+	bt_uuid16_create(&uuid_supported_context, PAC_SUPPORTED_CONTEXT);
+
+	if (!bt_uuid_cmp(&uuid, &uuid_sink)) {
+		DBG(bap, "Sink PAC found: handle 0x%04x", value_handle);
+
+		pacs = bap_get_pacs(bap);
+		if (!pacs || pacs->sink)
+			return;
+
+		pacs->sink = attr;
+		bap_read_value(bap, value_handle, read_sink_pac, bap);
+	}
+
+	if (!bt_uuid_cmp(&uuid, &uuid_source)) {
+		DBG(bap, "Source PAC found: handle 0x%04x", value_handle);
+
+		pacs = bap_get_pacs(bap);
+		if (!pacs || pacs->source)
+			return;
+
+		pacs->source = attr;
+		bap_read_value(bap, value_handle, read_source_pac, NULL);
+	}
+
+	if (!bt_uuid_cmp(&uuid, &uuid_sink_loc)) {
+		DBG(bap, "Sink PAC Location found: handle 0x%04x",
+						value_handle);
+
+		pacs = bap_get_pacs(bap);
+		if (!pacs || pacs->sink_loc)
+			return;
+
+		pacs->sink_loc = attr;
+		bap_read_value(bap, value_handle, read_sink_pac_loc, NULL);
+	}
+
+	if (!bt_uuid_cmp(&uuid, &uuid_source_loc)) {
+		DBG(bap, "Source PAC Location found: handle 0x%04x",
+						value_handle);
+
+		pacs = bap_get_pacs(bap);
+		if (!pacs || pacs->source_loc)
+			return;
+
+		pacs->source_loc = attr;
+		bap_read_value(bap, value_handle, read_source_pac_loc, NULL);
+	}
+
+	if (!bt_uuid_cmp(&uuid, &uuid_context)) {
+		DBG(bap, "PAC Context found: handle 0x%04x", value_handle);
+
+		pacs = bap_get_pacs(bap);
+		if (!pacs || pacs->context)
+			return;
+
+		pacs->context = attr;
+		bap_read_value(bap, value_handle, read_pac_context, NULL);
+	}
+
+	if (!bt_uuid_cmp(&uuid, &uuid_supported_context)) {
+		DBG(bap, "PAC Supported Context found: handle 0x%04x",
+							value_handle);
+
+		pacs = bap_get_pacs(bap);
+		if (!pacs || pacs->supported_context)
+			return;
+
+		pacs->supported_context = attr;
+		bap_read_value(bap, value_handle, read_pac_supported_context,
+									NULL);
+	}
+}
+
+static void foreach_pacs_service(struct gatt_db_attribute *attr,
+						void *user_data)
+{
+	struct bt_bap *bap = user_data;
+	struct bt_pacs *pacs = bap_get_pacs(bap);
+
+	pacs->service = attr;
+
+	gatt_db_service_foreach_char(attr, foreach_pacs_char, bap);
+}
+
+struct match_pac {
+	struct bt_bap_codec codec;
+	struct bt_bap_pac *lpac;
+	struct bt_bap_pac *rpac;
+	struct bt_bap_endpoint *ep;
+};
+
+static bool match_stream_pac(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
+							void *user_data)
+{
+	struct match_pac *match = user_data;
+
+	if (!bap_codec_equal(&match->codec, &lpac->codec))
+		return true;
+
+	match->lpac = lpac;
+	match->rpac = rpac;
+
+	return false;
+}
+
+static void ep_status_config(struct bt_bap *bap, struct bt_bap_endpoint *ep,
+							struct iovec *iov)
+{
+	struct bt_ascs_ase_status_config *cfg;
+	struct bt_ltv *cc;
+	uint32_t pd_min, pd_max, ppd_min, ppd_max;
+	int i;
+
+	cfg = iov_pull_mem(iov, sizeof(*cfg));
+	if (!cfg) {
+		DBG(bap, "Unable to parse Config Status");
+		return;
+	}
+
+	pd_min = get_le24(cfg->pd_min);
+	pd_max = get_le24(cfg->pd_max);
+	ppd_min = get_le24(cfg->ppd_min);
+	ppd_max = get_le24(cfg->ppd_max);
+
+	DBG(bap, "codec 0x%02x framing 0x%02x phy 0x%02x rtn %u "
+			"latency %u pd %u - %u ppd %u - %u", cfg->codec.id,
+			cfg->framing, cfg->phy, cfg->rtn,
+			le16_to_cpu(cfg->latency),
+			pd_min, pd_max, ppd_min, ppd_max);
+
+	if (iov->iov_len < cfg->cc_len) {
+		DBG(bap, "Unable to parse Config Status: len %zu < %u cc_len",
+						iov->iov_len, cfg->cc_len);
+		return;
+	}
+
+	for (i = 0; iov->iov_len >= sizeof(*cc); i++) {
+		cc = iov_pull_mem(iov, sizeof(*cc));
+		if (!cc)
+			break;
+
+		DBG(bap, "Codec Config #%u: type 0x%02x len %u", i,
+						cc->type, cc->len);
+
+		iov_pull_mem(iov, cc->len - 1);
+	}
+
+	/* Any previously applied codec configuration may be cached by the
+	 * server.
+	 */
+	if (!ep->stream) {
+		struct match_pac match;
+
+		match.lpac = NULL;
+		match.rpac = NULL;
+		match.codec.id = cfg->codec.id;
+		match.codec.cid = le16_to_cpu(cfg->codec.cid);
+		match.codec.vid = le16_to_cpu(cfg->codec.vid);
+
+		bt_bap_foreach_pac(bap, ep->dir, match_stream_pac, &match);
+		if (!match.lpac || !match.rpac)
+			return;
+
+		bap_stream_new(bap, ep, match.lpac, match.rpac, NULL, true);
+	}
+
+	if (!ep->stream)
+		return;
+
+	/* Set preferred settings */
+	if (ep->stream->rpac) {
+		ep->stream->rpac->qos.framing = cfg->framing;
+		ep->stream->rpac->qos.phy = cfg->phy;
+		ep->stream->rpac->qos.rtn = cfg->rtn;
+		ep->stream->rpac->qos.latency = le16_to_cpu(cfg->latency);
+		ep->stream->rpac->qos.pd_min = pd_min;
+		ep->stream->rpac->qos.pd_max = pd_max;
+		ep->stream->rpac->qos.ppd_min = ppd_min;
+		ep->stream->rpac->qos.ppd_max = ppd_max;
+	}
+
+	if (!ep->stream->cc)
+		ep->stream->cc = new0(struct iovec, 1);
+
+	iov_memcpy(ep->stream->cc, cfg->cc, cfg->cc_len);
+}
+
+static void bap_stream_config_cfm_cb(struct bt_bap_stream *stream, int err)
+{
+	struct bt_bap *bap = stream->bap;
+
+	if (err) {
+		DBG(bap, "Config Confirmation failed: %d", err);
+		bt_bap_stream_release(stream, NULL, NULL);
+		return;
+	}
+}
+
+static void bap_stream_config_cfm(struct bt_bap_stream *stream)
+{
+	int err;
+
+	if (!stream->lpac->ops || !stream->lpac->ops->config)
+		return;
+
+	err = stream->lpac->ops->config(stream, stream->cc, &stream->qos,
+					bap_stream_config_cfm_cb,
+					stream->lpac->user_data);
+	if (err < 0) {
+		DBG(stream->bap, "Unable to send Config Confirmation: %d",
+									err);
+		bt_bap_stream_release(stream, NULL, NULL);
+	}
+}
+
+static void ep_status_qos(struct bt_bap *bap, struct bt_bap_endpoint *ep,
+							struct iovec *iov)
+{
+	struct bt_ascs_ase_status_qos *qos;
+	uint32_t interval;
+	uint32_t pd;
+	uint16_t sdu;
+	uint16_t latency;
+
+	qos = iov_pull_mem(iov, sizeof(*qos));
+	if (!qos) {
+		DBG(bap, "Unable to parse QoS Status");
+		return;
+	}
+
+	interval = get_le24(qos->interval);
+	pd = get_le24(qos->pd);
+	sdu = le16_to_cpu(qos->sdu);
+	latency = le16_to_cpu(qos->latency);
+
+	DBG(bap, "CIG 0x%02x CIS 0x%02x interval %u framing 0x%02x "
+			"phy 0x%02x rtn %u latency %u pd %u", qos->cig_id,
+			qos->cis_id, interval, qos->framing, qos->phy,
+			qos->rtn, latency, pd);
+
+	if (!ep->stream)
+		return;
+
+	ep->stream->qos.interval = interval;
+	ep->stream->qos.framing = qos->framing;
+	ep->stream->qos.phy = qos->phy;
+	ep->stream->qos.sdu = sdu;
+	ep->stream->qos.rtn = qos->rtn;
+	ep->stream->qos.latency = latency;
+	ep->stream->qos.delay = pd;
+
+	if (ep->old_state == BT_ASCS_ASE_STATE_CONFIG)
+		bap_stream_config_cfm(ep->stream);
+}
+
+static void ep_status_metadata(struct bt_bap *bap, struct bt_bap_endpoint *ep,
+							struct iovec *iov)
+{
+	struct bt_ascs_ase_status_metadata *meta;
+
+	meta = iov_pull_mem(iov, sizeof(*meta));
+	if (!meta) {
+		DBG(bap, "Unable to parse Metadata Status");
+		return;
+	}
+
+	DBG(bap, "CIS 0x%02x CIG 0x%02x metadata len %u",
+			meta->cis_id, meta->cig_id, meta->len);
+}
+
+static void bap_ep_set_status(struct bt_bap *bap, struct bt_bap_endpoint *ep,
+					const uint8_t *value, uint16_t length)
+{
+	struct bt_ascs_ase_status *rsp;
+	struct iovec iov = {
+		.iov_base = (void *) value,
+		.iov_len = length,
+	};
+
+	rsp = iov_pull_mem(&iov, sizeof(*rsp));
+	if (!rsp)
+		return;
+
+	ep->id = rsp->id;
+	ep->old_state = ep->state;
+	ep->state = rsp->state;
+
+	DBG(bap, "ASE status: ep %p id 0x%02x handle 0x%04x state %s "
+			"len %zu", ep, ep->id,
+			gatt_db_attribute_get_handle(ep->attr),
+			bt_bap_stream_statestr(ep->state), iov.iov_len);
+
+	switch (ep->state) {
+	case BT_ASCS_ASE_STATE_IDLE:
+		break;
+	case BT_ASCS_ASE_STATE_CONFIG:
+		ep_status_config(bap, ep, &iov);
+		break;
+	case BT_ASCS_ASE_STATE_QOS:
+		ep_status_qos(bap, ep, &iov);
+		break;
+	case BT_ASCS_ASE_STATE_ENABLING:
+	case BT_ASCS_ASE_STATE_STREAMING:
+	case BT_ASCS_ASE_STATE_DISABLING:
+		ep_status_metadata(bap, ep, &iov);
+		break;
+	case BT_ASCS_ASE_STATE_RELEASING:
+		break;
+	}
+
+	/* Only notifify if there is a stream */
+	if (!ep->stream)
+		return;
+
+	bap_stream_state_changed(ep->stream);
+}
+
+static void read_ase_status(struct bt_bap *bap, bool success, uint8_t att_ecode,
+				const uint8_t *value, uint16_t length,
+				void *user_data)
+{
+	struct bt_bap_endpoint *ep = user_data;
+
+	if (!success)
+		return;
+
+	bap_ep_set_status(bap, ep, value, length);
+}
+
+static void bap_register(uint16_t att_ecode, void *user_data)
+{
+	struct bt_bap_notify *notify = user_data;
+
+	if (att_ecode)
+		DBG(notify->bap, "ASE register failed: 0x%04x", att_ecode);
+}
+
+static void bap_endpoint_notify(struct bt_bap *bap, uint16_t value_handle,
+				const uint8_t *value, uint16_t length,
+				void *user_data)
+{
+	struct bt_bap_endpoint *ep = user_data;
+
+	bap_ep_set_status(bap, ep, value, length);
+}
+
+static void bap_notify(uint16_t value_handle, const uint8_t *value,
+				uint16_t length, void *user_data)
+{
+	struct bt_bap_notify *notify = user_data;
+
+	if (notify->func)
+		notify->func(notify->bap, value_handle, value, length,
+						notify->user_data);
+}
+
+static void bap_notify_destroy(void *data)
+{
+	struct bt_bap_notify *notify = data;
+	struct bt_bap *bap = notify->bap;
+
+	if (queue_remove_if(bap->notify, NULL, notify))
+		free(notify);
+}
+
+static unsigned int bap_register_notify(struct bt_bap *bap,
+					uint16_t value_handle,
+					bap_notify_t func,
+					void *user_data)
+{
+	struct bt_bap_notify *notify;
+
+	notify = new0(struct bt_bap_notify, 1);
+	notify->bap = bap;
+	notify->func = func;
+	notify->user_data = user_data;
+
+	notify->id = bt_gatt_client_register_notify(bap->client,
+						value_handle, bap_register,
+						bap_notify, notify,
+						bap_notify_destroy);
+	if (!notify->id) {
+		DBG(bap, "Unable to register for notifications");
+		free(notify);
+		return 0;
+	}
+
+	queue_push_tail(bap->notify, notify);
+
+	return notify->id;
+}
+
+static void bap_endpoint_attach(struct bt_bap *bap, struct bt_bap_endpoint *ep)
+{
+	uint16_t value_handle;
+
+	if (!gatt_db_attribute_get_char_data(ep->attr, NULL, &value_handle,
+						NULL, NULL, NULL))
+		return;
+
+	DBG(bap, "ASE handle 0x%04x", value_handle);
+
+	bap_read_value(bap, value_handle, read_ase_status, ep);
+
+	ep->state_id = bap_register_notify(bap, value_handle,
+						bap_endpoint_notify, ep);
+}
+
+static void append_group(void *data, void *user_data)
+{
+	struct bt_bap_req *req = data;
+	struct iovec *iov = user_data;
+	size_t i;
+
+	for (i = 0; i < req->len; i++)
+		iov_add_mem(iov, req->iov[i].iov_len, req->iov[i].iov_base);
+}
+
+static bool bap_send(struct bt_bap *bap, struct bt_bap_req *req)
+{
+	struct bt_ascs *ascs = bap_get_ascs(bap);
+	int ret;
+	uint16_t handle;
+	uint8_t buf[64];
+	struct bt_ascs_ase_hdr hdr;
+	struct iovec iov  = {
+		.iov_base = buf,
+		.iov_len = 0,
+	};
+	size_t i;
+
+	if (!gatt_db_attribute_get_char_data(ascs->ase_cp, NULL, &handle,
+						NULL, NULL, NULL))
+		return false;
+
+	hdr.op = req->op;
+	hdr.num = 1 + queue_length(req->group);
+
+	iov_add_mem(&iov, sizeof(hdr), &hdr);
+
+	for (i = 0; i < req->len; i++)
+		iov_add_mem(&iov, req->iov[i].iov_len, req->iov[i].iov_base);
+
+	/* Append the request group with the same opcode */
+	queue_foreach(req->group, append_group, &iov);
+
+	ret = bt_gatt_client_write_without_response(bap->client, handle,
+							false, iov.iov_base,
+							iov.iov_len);
+	if (!ret)
+		return false;
+
+	bap->req = req;
+
+	return false;
+}
+
+static bool bap_process_queue(void *data)
+{
+	struct bt_bap *bap = data;
+	struct bt_bap_req *req;
+
+	if (bap->process_id) {
+		timeout_remove(bap->process_id);
+		bap->process_id = 0;
+	}
+
+	while ((req = queue_pop_head(bap->reqs))) {
+		if (!bap_send(bap, req))
+			break;
+	}
+
+	return false;
+}
+
+static bool match_req(const void *data, const void *match_data)
+{
+	const struct bt_bap_req *pend = data;
+	const struct bt_bap_req *req = match_data;
+
+	return pend->op == req->op;
+}
+
+static bool bap_queue_req(struct bt_bap *bap, struct bt_bap_req *req)
+{
+	struct bt_bap_req *pend;
+	struct queue *queue;
+
+	pend = queue_find(bap->reqs, match_req, req);
+	if (pend) {
+		if (!pend->group)
+			pend->group = queue_new();
+		/* Group requests with the same opcode */
+		queue = pend->group;
+	} else {
+		queue = bap->reqs;
+	}
+
+	DBG(bap, "req %p (op 0x%2.2x) queue %p", req, req->op, queue);
+
+	if (!queue_push_tail(queue, req)) {
+		DBG(bap, "Unable to queue request");
+		return false;
+	}
+
+	/* Only attempot to process queue if there is no outstanding request
+	 * and it has not been scheduled.
+	 */
+	if (!bap->req && !bap->process_id)
+		bap->process_id = timeout_add(BAP_PROCESS_TIMEOUT,
+						bap_process_queue, bap, NULL);
+
+	return true;
+}
+
+static void bap_req_complete(struct bt_bap_req *req,
+				const struct bt_ascs_ase_rsp *rsp)
+{
+	struct queue *group;
+
+	if (!req->func)
+		goto done;
+
+	if (rsp)
+		req->func(req->stream, rsp->code, rsp->reason, req->user_data);
+	else
+		req->func(req->stream, BT_ASCS_RSP_UNSPECIFIED, 0x00,
+						req->user_data);
+
+done:
+	/* Detach from request so it can be freed separately */
+	group = req->group;
+	req->group = NULL;
+
+	queue_foreach(group, (queue_foreach_func_t)bap_req_complete,
+							(void *)rsp);
+
+	queue_destroy(group, NULL);
+
+	bap_req_free(req);
+}
+
+static void bap_cp_notify(struct bt_bap *bap, uint16_t value_handle,
+				const uint8_t *value, uint16_t length,
+				void *user_data)
+{
+	const struct bt_ascs_cp_rsp *rsp = (void *)value;
+	const struct bt_ascs_ase_rsp *ase_rsp = NULL;
+	struct bt_bap_req *req;
+	int i;
+
+	if (!bap->req)
+		return;
+
+	req = bap->req;
+	bap->req = NULL;
+
+	if (length < sizeof(*rsp)) {
+		DBG(bap, "Invalid ASE CP notification: length %u < %zu",
+						length, sizeof(*rsp));
+		goto done;
+	}
+
+	if (rsp->op != req->op) {
+		DBG(bap, "Invalid ASE CP notification: op 0x%02x != 0x%02x",
+						rsp->op, req->op);
+		goto done;
+	}
+
+	length -= sizeof(*rsp);
+
+	if (rsp->num_ase == 0xff) {
+		ase_rsp = rsp->rsp;
+		goto done;
+	}
+
+	for (i = 0; i < rsp->num_ase; i++) {
+		if (length < sizeof(*ase_rsp)) {
+			DBG(bap, "Invalid ASE CP notification: length %u < %zu",
+					length, sizeof(*ase_rsp));
+			goto done;
+		}
+
+		ase_rsp = &rsp->rsp[i];
+	}
+
+done:
+	bap_req_complete(req, ase_rsp);
+	bap_process_queue(bap);
+}
+
+static void bap_cp_attach(struct bt_bap *bap)
+{
+	uint16_t value_handle;
+	struct bt_ascs *ascs = bap_get_ascs(bap);
+
+	if (!gatt_db_attribute_get_char_data(ascs->ase_cp, NULL,
+						&value_handle,
+						NULL, NULL, NULL))
+		return;
+
+	DBG(bap, "ASE CP handle 0x%04x", value_handle);
+
+	bap->cp_id = bap_register_notify(bap, value_handle, bap_cp_notify,
+								NULL);
+}
+
+static void foreach_ascs_char(struct gatt_db_attribute *attr, void *user_data)
+{
+	struct bt_bap *bap = user_data;
+	uint16_t value_handle;
+	bt_uuid_t uuid, uuid_sink, uuid_source, uuid_cp;
+	struct bt_ascs *ascs;
+
+	if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle,
+						NULL, NULL, &uuid))
+		return;
+
+	bt_uuid16_create(&uuid_sink, ASE_SINK_UUID);
+	bt_uuid16_create(&uuid_source, ASE_SOURCE_UUID);
+	bt_uuid16_create(&uuid_cp, ASE_CP_UUID);
+
+	if (!bt_uuid_cmp(&uuid, &uuid_sink) ||
+			!bt_uuid_cmp(&uuid, &uuid_source)) {
+		struct bt_bap_endpoint *ep;
+
+		ep = bap_get_endpoint(bap->rdb, attr);
+		if (!ep)
+			return;
+
+		if (!bt_uuid_cmp(&uuid, &uuid_sink))
+			DBG(bap, "ASE Sink found: handle 0x%04x", value_handle);
+		else
+			DBG(bap, "ASE Source found: handle 0x%04x",
+							value_handle);
+
+		bap_endpoint_attach(bap, ep);
+
+		return;
+	}
+
+	if (!bt_uuid_cmp(&uuid, &uuid_cp)) {
+		ascs = bap_get_ascs(bap);
+		if (!ascs || ascs->ase_cp)
+			return;
+
+		ascs->ase_cp = attr;
+
+		DBG(bap, "ASE Control Point found: handle 0x%04x",
+							value_handle);
+
+		bap_cp_attach(bap);
+	}
+}
+
+static void foreach_ascs_service(struct gatt_db_attribute *attr,
+						void *user_data)
+{
+	struct bt_bap *bap = user_data;
+	struct bt_ascs *ascs = bap_get_ascs(bap);
+
+	ascs->service = attr;
+
+	gatt_db_service_set_claimed(attr, true);
+
+	gatt_db_service_foreach_char(attr, foreach_ascs_char, bap);
+}
+
+static void bap_endpoint_foreach(void *data, void *user_data)
+{
+	struct bt_bap_endpoint *ep = data;
+	struct bt_bap *bap = user_data;
+
+	bap_endpoint_attach(bap, ep);
+}
+
+bool bt_bap_attach(struct bt_bap *bap, struct bt_gatt_client *client)
+{
+	bt_uuid_t uuid;
+
+	if (queue_find(sessions, NULL, bap)) {
+		/* If instance already been set but there is no client proceed
+		 * to clone it otherwise considered it already attached.
+		 */
+		if (client && !bap->client)
+			goto clone;
+		return true;
+	}
+
+	if (!sessions)
+		sessions = queue_new();
+
+	queue_push_tail(sessions, bap);
+
+	queue_foreach(bap_cbs, bap_attached, bap);
+
+	if (!client)
+		return true;
+
+	if (bap->client)
+		return false;
+
+clone:
+	bap->client = bt_gatt_client_clone(client);
+	if (!bap->client)
+		return false;
+
+	if (bap->rdb->pacs) {
+		uint16_t value_handle;
+		struct bt_pacs *pacs = bap->rdb->pacs;
+
+		/* Resume reading sinks if supported */
+		if (pacs->sink && queue_isempty(bap->rdb->sinks)) {
+			if (gatt_db_attribute_get_char_data(pacs->sink,
+							NULL, &value_handle,
+							NULL, NULL, NULL)) {
+				bap_read_value(bap, value_handle,
+							read_sink_pac, bap);
+			}
+		}
+
+		/* Resume reading sources if supported */
+		if (pacs->source && queue_isempty(bap->rdb->sources)) {
+			if (gatt_db_attribute_get_char_data(pacs->source,
+							NULL, &value_handle,
+							NULL, NULL, NULL)) {
+				bap_read_value(bap, value_handle,
+							read_source_pac, bap);
+			}
+		}
+
+		queue_foreach(bap->rdb->endpoints, bap_endpoint_foreach, bap);
+
+		bap_cp_attach(bap);
+
+		bap_notify_ready(bap);
+
+		return true;
+	}
+
+	bt_uuid16_create(&uuid, PACS_UUID);
+	gatt_db_foreach_service(bap->rdb->db, &uuid, foreach_pacs_service, bap);
+
+	bt_uuid16_create(&uuid, ASCS_UUID);
+	gatt_db_foreach_service(bap->rdb->db, &uuid, foreach_ascs_service, bap);
+
+	return true;
+}
+
+static void stream_foreach_detach(void *data, void *user_data)
+{
+	struct bt_bap_stream *stream = data;
+
+	stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE);
+}
+
+void bt_bap_detach(struct bt_bap *bap)
+{
+	DBG(bap, "%p", bap);
+
+	if (!queue_remove(sessions, bap))
+		return;
+
+	bt_gatt_client_unref(bap->client);
+	bap->client = NULL;
+
+	queue_foreach(bap->streams, stream_foreach_detach, bap);
+	queue_foreach(bap_cbs, bap_detached, bap);
+}
+
+bool bt_bap_set_debug(struct bt_bap *bap, bt_bap_debug_func_t func,
+			void *user_data, bt_bap_destroy_func_t destroy)
+{
+	if (!bap)
+		return false;
+
+	if (bap->debug_destroy)
+		bap->debug_destroy(bap->debug_data);
+
+	bap->debug_func = func;
+	bap->debug_destroy = destroy;
+	bap->debug_data = user_data;
+
+	return true;
+}
+
+unsigned int bt_bap_ready_register(struct bt_bap *bap,
+				bt_bap_ready_func_t func, void *user_data,
+				bt_bap_destroy_func_t destroy)
+{
+	struct bt_bap_ready *ready;
+	static unsigned int id;
+
+	if (!bap)
+		return 0;
+
+	ready = new0(struct bt_bap_ready, 1);
+	ready->id = ++id ? id : ++id;
+	ready->func = func;
+	ready->destroy = destroy;
+	ready->data = user_data;
+
+	queue_push_tail(bap->ready_cbs, ready);
+
+	return ready->id;
+}
+
+static bool match_ready_id(const void *data, const void *match_data)
+{
+	const struct bt_bap_ready *ready = data;
+	unsigned int id = PTR_TO_UINT(match_data);
+
+	return (ready->id == id);
+}
+
+bool bt_bap_ready_unregister(struct bt_bap *bap, unsigned int id)
+{
+	struct bt_bap_ready *ready;
+
+	ready = queue_remove_if(bap->ready_cbs, match_ready_id,
+						UINT_TO_PTR(id));
+	if (!ready)
+		return false;
+
+	bap_ready_free(ready);
+
+	return true;
+}
+
+unsigned int bt_bap_state_register(struct bt_bap *bap,
+				bt_bap_state_func_t func,
+				bt_bap_connecting_func_t connecting,
+				void *user_data, bt_bap_destroy_func_t destroy)
+{
+	struct bt_bap_state *state;
+	static unsigned int id;
+
+	if (!bap)
+		return 0;
+
+	state = new0(struct bt_bap_state, 1);
+	state->id = ++id ? id : ++id;
+	state->func = func;
+	state->connecting = connecting;
+	state->destroy = destroy;
+	state->data = user_data;
+
+	queue_push_tail(bap->state_cbs, state);
+
+	return state->id;
+}
+
+static bool match_state_id(const void *data, const void *match_data)
+{
+	const struct bt_bap_state *state = data;
+	unsigned int id = PTR_TO_UINT(match_data);
+
+	return (state->id == id);
+}
+
+bool bt_bap_state_unregister(struct bt_bap *bap, unsigned int id)
+{
+	struct bt_bap_state *state;
+
+	if (!bap)
+		return false;
+
+	state = queue_remove_if(bap->state_cbs, match_state_id,
+						UINT_TO_PTR(id));
+	if (!state)
+		return false;
+
+	bap_state_free(state);
+
+	return false;
+}
+
+const char *bt_bap_stream_statestr(uint8_t state)
+{
+	switch (state) {
+	case BT_BAP_STREAM_STATE_IDLE:
+		return "idle";
+	case BT_BAP_STREAM_STATE_CONFIG:
+		return "config";
+	case BT_BAP_STREAM_STATE_QOS:
+		return "qos";
+	case BT_BAP_STREAM_STATE_ENABLING:
+		return "enabling";
+	case BT_BAP_STREAM_STATE_STREAMING:
+		return "streaming";
+	case BT_BAP_STREAM_STATE_DISABLING:
+		return "disabling";
+	case BT_BAP_STREAM_STATE_RELEASING:
+		return "releasing";
+	}
+
+	return "unknown";
+}
+
+static void bap_foreach_pac(struct queue *l, struct queue *r,
+				bt_bap_pac_foreach_t func, void *user_data)
+{
+	const struct queue_entry *el;
+
+	for (el = queue_get_entries(l); el; el = el->next) {
+		struct bt_bap_pac *lpac = el->data;
+		const struct queue_entry *er;
+
+		for (er = queue_get_entries(r); er; er = er->next) {
+			struct bt_bap_pac *rpac = er->data;
+
+			if (!bap_codec_equal(&lpac->codec, &rpac->codec))
+				continue;
+
+			if (!func(lpac, rpac, user_data))
+				return;
+		}
+	}
+}
+
+void bt_bap_foreach_pac(struct bt_bap *bap, uint8_t type,
+			bt_bap_pac_foreach_t func, void *user_data)
+{
+	if (!bap || !func || !bap->rdb || queue_isempty(bap_db))
+		return;
+
+	switch (type) {
+	case BT_BAP_SINK:
+		return bap_foreach_pac(bap->ldb->sources, bap->rdb->sinks,
+							func, user_data);
+	case BT_BAP_SOURCE:
+		return bap_foreach_pac(bap->ldb->sinks, bap->rdb->sources,
+							func, user_data);
+	}
+}
+
+int bt_bap_pac_get_vendor_codec(struct bt_bap_pac *pac, uint8_t *id,
+				uint16_t *cid, uint16_t *vid,
+				struct iovec **data, struct iovec **metadata)
+{
+	if (!pac)
+		return -EINVAL;
+
+	if (id)
+		*id = pac->codec.id;
+
+	if (cid)
+		*cid = pac->codec.cid;
+
+	if (vid)
+		*vid = pac->codec.cid;
+
+	if (data)
+		*data = pac->data;
+
+	if (metadata)
+		*metadata = pac->metadata;
+
+	return 0;
+}
+
+int bt_bap_pac_get_codec(struct bt_bap_pac *pac, uint8_t *id,
+				struct iovec **data, struct iovec **metadata)
+{
+	return bt_bap_pac_get_vendor_codec(pac, id, NULL, NULL, data, metadata);
+}
+
+static bool find_ep_unused(const void *data, const void *user_data)
+{
+	const struct bt_bap_endpoint *ep = data;
+	const struct match_pac *match = user_data;
+
+	if (ep->stream)
+		return false;
+
+	return ep->dir == match->rpac->type;
+}
+
+static bool find_ep_pacs(const void *data, const void *user_data)
+{
+	const struct bt_bap_endpoint *ep = data;
+	const struct match_pac *match = user_data;
+
+	if (!ep->stream)
+		return false;
+
+	if (ep->stream->lpac != match->lpac)
+		return false;
+
+	return ep->stream->rpac == match->rpac;
+}
+
+static struct bt_bap_req *bap_req_new(struct bt_bap_stream *stream,
+					uint8_t op, struct iovec *iov,
+					size_t len,
+					bt_bap_stream_func_t func,
+					void *user_data)
+{
+	struct bt_bap_req *req;
+	static unsigned int id;
+
+	req = new0(struct bt_bap_req, 1);
+	req->id = ++id;
+	req->stream = stream;
+	req->op = op;
+	req->iov = iov_dup(iov, len);
+	req->len = len;
+	req->func = func;
+	req->user_data = user_data;
+
+	return req;
+}
+
+static bool bap_stream_valid(struct bt_bap_stream *stream)
+{
+	if (!stream || !stream->bap)
+		return false;
+
+	return queue_find(stream->bap->streams, NULL, stream);
+}
+
+unsigned int bt_bap_stream_config(struct bt_bap_stream *stream,
+					struct bt_bap_qos *qos,
+					struct iovec *data,
+					bt_bap_stream_func_t func,
+					void *user_data)
+{
+	struct iovec iov[2];
+	struct bt_ascs_config config;
+	uint8_t iovlen = 1;
+	struct bt_bap_req *req;
+
+	if (!bap_stream_valid(stream))
+		return 0;
+
+	if (!stream->client) {
+		stream_config(stream, data, NULL);
+		return 0;
+	}
+
+	memset(&config, 0, sizeof(config));
+
+	config.ase = stream->ep->id;
+	config.latency = qos->target_latency;
+	config.phy = qos->phy;
+	config.codec = stream->rpac->codec;
+
+	iov[0].iov_base = &config;
+	iov[0].iov_len = sizeof(config);
+
+	if (data) {
+		if (!bap_print_cc(data->iov_base, data->iov_len,
+					stream->bap->debug_func,
+					stream->bap->debug_data))
+			return 0;
+
+		config.cc_len = data->iov_len;
+		iov[1] = *data;
+		iovlen++;
+	}
+
+	req = bap_req_new(stream, BT_ASCS_CONFIG, iov, iovlen, func, user_data);
+
+	if (!bap_queue_req(stream->bap, req)) {
+		bap_req_free(req);
+		return 0;
+	}
+
+	stream->qos = *qos;
+
+	return req->id;
+}
+
+static bool match_pac(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
+							void *user_data)
+{
+	struct match_pac *match = user_data;
+
+	if (match->lpac && match->lpac != lpac)
+		return true;
+
+	if (match->rpac && match->rpac != rpac)
+		return true;
+
+	match->lpac = lpac;
+	match->rpac = rpac;
+
+	return false;
+}
+
+int bt_bap_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
+			bt_bap_pac_select_t func, void *user_data)
+{
+	if (!lpac || !rpac || !func)
+		return -EINVAL;
+
+	if (!lpac->ops || !lpac->ops->select)
+		return -EOPNOTSUPP;
+
+	lpac->ops->select(lpac, &rpac->qos, rpac->data, rpac->metadata,
+					func, user_data, lpac->user_data);
+
+	return 0;
+}
+
+struct bt_bap_stream *bt_bap_config(struct bt_bap *bap,
+					struct bt_bap_pac *lpac,
+					struct bt_bap_pac *rpac,
+					struct bt_bap_qos *pqos,
+					struct iovec *data,
+					bt_bap_stream_func_t func,
+					void *user_data)
+{
+	struct bt_bap_stream *stream;
+	struct bt_bap_endpoint *ep;
+	struct match_pac match;
+	int id;
+
+	if (!bap || !bap->rdb || queue_isempty(bap->rdb->endpoints))
+		return NULL;
+
+	if (lpac && rpac) {
+		if (!bap_codec_equal(&lpac->codec, &rpac->codec))
+			return NULL;
+	} else {
+		uint8_t type;
+
+		match.lpac = lpac;
+		match.rpac = rpac;
+		memset(&match.codec, 0, sizeof(match.codec));
+
+		if (rpac)
+			type = rpac->type;
+		else if (lpac) {
+			switch(lpac->type) {
+			case BT_BAP_SINK:
+				type = BT_BAP_SOURCE;
+				break;
+			case BT_BAP_SOURCE:
+				type = BT_BAP_SINK;
+				break;
+			default:
+				return NULL;
+			}
+		} else
+			return NULL;
+
+		bt_bap_foreach_pac(bap, type, match_pac, &match);
+		if (!match.lpac || !match.rpac)
+			return NULL;
+
+		lpac = match.lpac;
+		rpac = match.rpac;
+	}
+
+	match.lpac = lpac;
+	match.rpac = rpac;
+
+	/* Check for existing stream */
+	ep = queue_find(bap->rdb->endpoints, find_ep_pacs, &match);
+	if (!ep) {
+		/* Check for unused ASE */
+		ep = queue_find(bap->rdb->endpoints, find_ep_unused, &match);
+		if (!ep) {
+			DBG(bap, "Unable to find unused ASE");
+			return NULL;
+		}
+	}
+
+	stream = ep->stream;
+	if (!stream)
+		stream = bap_stream_new(bap, ep, lpac, rpac, data, true);
+
+	id = bt_bap_stream_config(stream, pqos, data, func, user_data);
+	if (!id) {
+		DBG(bap, "Unable to config stream");
+		queue_remove(bap->streams, stream);
+		ep->stream = NULL;
+		free(stream);
+		return NULL;
+	}
+
+	return stream;
+}
+
+struct bt_bap *bt_bap_stream_get_session(struct bt_bap_stream *stream)
+{
+	if (!stream)
+		return NULL;
+
+	return stream->bap;
+}
+
+uint8_t bt_bap_stream_get_state(struct bt_bap_stream *stream)
+{
+	if (!stream)
+		return BT_BAP_STREAM_STATE_IDLE;
+
+	return stream->ep->state;
+}
+
+bool bt_bap_stream_set_user_data(struct bt_bap_stream *stream, void *user_data)
+{
+	if (!stream)
+		return false;
+
+	stream->user_data = user_data;
+
+	return true;
+}
+
+void *bt_bap_stream_get_user_data(struct bt_bap_stream *stream)
+{
+	if (!stream)
+		return NULL;
+
+	return stream->user_data;
+}
+
+unsigned int bt_bap_stream_qos(struct bt_bap_stream *stream,
+					struct bt_bap_qos *data,
+					bt_bap_stream_func_t func,
+					void *user_data)
+{
+	struct iovec iov;
+	struct bt_ascs_qos qos;
+	struct bt_bap_req *req;
+
+	if (!bap_stream_valid(stream))
+		return 0;
+
+	if (!stream->client) {
+		stream_qos(stream, data, NULL);
+		return 0;
+	}
+
+	memset(&qos, 0, sizeof(qos));
+
+	/* TODO: Figure out how to pass these values around */
+	qos.ase = stream->ep->id;
+	qos.cig = data->cig_id;
+	qos.cis = data->cis_id;
+	put_le24(data->interval, qos.interval);
+	qos.framing = data->framing;
+	qos.phy = data->phy;
+	qos.sdu = cpu_to_le16(data->sdu);
+	qos.rtn = data->rtn;
+	qos.latency = cpu_to_le16(data->latency);
+	put_le24(data->delay, qos.pd);
+
+	iov.iov_base = &qos;
+	iov.iov_len = sizeof(qos);
+
+	req = bap_req_new(stream, BT_ASCS_QOS, &iov, 1, func, user_data);
+
+	if (!bap_queue_req(stream->bap, req)) {
+		bap_req_free(req);
+		return 0;
+	}
+
+	stream->qos = *data;
+
+	return req->id;
+}
+
+static int bap_stream_metadata(struct bt_bap_stream *stream, uint8_t op,
+					struct iovec *data,
+					bt_bap_stream_func_t func,
+					void *user_data)
+{
+	struct iovec iov[2];
+	struct bt_ascs_metadata meta;
+	struct bt_bap_req *req;
+	struct metadata {
+		uint8_t len;
+		uint8_t type;
+		uint8_t data[2];
+	} ctx = LTV(0x02, 0x01, 0x00); /* Context = Unspecified */
+
+	memset(&meta, 0, sizeof(meta));
+
+	meta.ase = stream->ep->id;
+
+	iov[0].iov_base = &meta;
+	iov[0].iov_len = sizeof(meta);
+
+	if (data)
+		iov[1] = *data;
+	else {
+		iov[1].iov_base = &ctx;
+		iov[1].iov_len = sizeof(ctx);
+	}
+
+	meta.len = iov[1].iov_len;
+
+	req = bap_req_new(stream, op, iov, 2, func, user_data);
+
+	if (!bap_queue_req(stream->bap, req)) {
+		bap_req_free(req);
+		return 0;
+	}
+
+	return req->id;
+}
+
+static void bap_stream_enable_link(void *data, void *user_data)
+{
+	struct bt_bap_stream *stream = data;
+	struct iovec *metadata = user_data;
+
+	bap_stream_metadata(stream, BT_ASCS_ENABLE, metadata, NULL, NULL);
+}
+
+unsigned int bt_bap_stream_enable(struct bt_bap_stream *stream,
+					bool enable_links,
+					struct iovec *metadata,
+					bt_bap_stream_func_t func,
+					void *user_data)
+{
+	int ret;
+
+	if (!bap_stream_valid(stream))
+		return 0;
+
+	if (!stream->client) {
+		stream_enable(stream, metadata, NULL);
+		return 0;
+	}
+
+	ret = bap_stream_metadata(stream, BT_ASCS_ENABLE, metadata, func,
+								user_data);
+	if (!ret || !enable_links)
+		return ret;
+
+	queue_foreach(stream->links, bap_stream_enable_link, metadata);
+
+	return ret;
+}
+
+unsigned int bt_bap_stream_start(struct bt_bap_stream *stream,
+					bt_bap_stream_func_t func,
+					void *user_data)
+{
+	struct iovec iov;
+	struct bt_ascs_start start;
+	struct bt_bap_req *req;
+
+	if (!bap_stream_valid(stream))
+		return 0;
+
+	if (!stream->client) {
+		if (stream->ep->dir == BT_BAP_SINK)
+			stream_start(stream, NULL);
+		return 0;
+	}
+
+	if (stream->ep->dir == BT_BAP_SINK)
+		return 0;
+
+	memset(&start, 0, sizeof(start));
+
+	start.ase = stream->ep->id;
+
+	iov.iov_base = &start;
+	iov.iov_len = sizeof(start);
+
+	req = bap_req_new(stream, BT_ASCS_START, &iov, 1, func, user_data);
+
+	if (!bap_queue_req(stream->bap, req)) {
+		bap_req_free(req);
+		return 0;
+	}
+
+	return req->id;
+}
+
+static void bap_stream_disable_link(void *data, void *user_data)
+{
+	struct bt_bap_stream *stream = data;
+	struct bt_bap_req *req;
+	struct iovec iov;
+	struct bt_ascs_disable disable;
+
+	memset(&disable, 0, sizeof(disable));
+
+	disable.ase = stream->ep->id;
+
+	iov.iov_base = &disable;
+	iov.iov_len = sizeof(disable);
+
+	req = bap_req_new(stream, BT_ASCS_DISABLE, &iov, 1, NULL, NULL);
+
+	if (!bap_queue_req(stream->bap, req))
+		bap_req_free(req);
+}
+
+unsigned int bt_bap_stream_disable(struct bt_bap_stream *stream,
+					bool disable_links,
+					bt_bap_stream_func_t func,
+					void *user_data)
+{
+	struct iovec iov;
+	struct bt_ascs_disable disable;
+	struct bt_bap_req *req;
+
+	if (!bap_stream_valid(stream))
+		return 0;
+
+	if (!stream->client) {
+		stream_disable(stream, NULL);
+		return 0;
+	}
+
+	memset(&disable, 0, sizeof(disable));
+
+	disable.ase = stream->ep->id;
+
+	iov.iov_base = &disable;
+	iov.iov_len = sizeof(disable);
+
+	req = bap_req_new(stream, BT_ASCS_DISABLE, &iov, 1, func, user_data);
+
+	if (!bap_queue_req(stream->bap, req)) {
+		bap_req_free(req);
+		return 0;
+	}
+
+	if (disable_links)
+		queue_foreach(stream->links, bap_stream_disable_link, NULL);
+
+	return req->id;
+}
+
+unsigned int bt_bap_stream_stop(struct bt_bap_stream *stream,
+					bt_bap_stream_func_t func,
+					void *user_data)
+{
+	struct iovec iov;
+	struct bt_ascs_stop stop;
+	struct bt_bap_req *req;
+
+	if (!bap_stream_valid(stream))
+		return 0;
+
+	if (!stream->client) {
+		if (stream->ep->dir == BT_BAP_SINK)
+			stream_stop(stream, NULL);
+		return 0;
+	}
+
+	if (stream->ep->dir == BT_BAP_SINK)
+		return 0;
+
+	memset(&stop, 0, sizeof(stop));
+
+	stop.ase = stream->ep->id;
+
+	iov.iov_base = &stop;
+	iov.iov_len = sizeof(stop);
+
+	req = bap_req_new(stream, BT_ASCS_STOP, &iov, 1, func, user_data);
+
+	if (!bap_queue_req(stream->bap, req)) {
+		bap_req_free(req);
+		return 0;
+	}
+
+	return req->id;
+}
+
+unsigned int bt_bap_stream_metadata(struct bt_bap_stream *stream,
+					struct iovec *metadata,
+					bt_bap_stream_func_t func,
+					void *user_data)
+{
+	if (!stream)
+		return 0;
+
+	if (!stream->client) {
+		stream_metadata(stream, metadata, NULL);
+		return 0;
+	}
+
+	return bap_stream_metadata(stream, BT_ASCS_METADATA, metadata, func,
+								user_data);
+}
+
+unsigned int bt_bap_stream_release(struct bt_bap_stream *stream,
+					bt_bap_stream_func_t func,
+					void *user_data)
+{
+	struct iovec iov;
+	struct bt_ascs_release rel;
+	struct bt_bap_req *req;
+
+	if (!stream)
+		return 0;
+
+	if (!stream->client) {
+		stream_release(stream, NULL);
+		return 0;
+	}
+
+	memset(&req, 0, sizeof(req));
+
+	rel.ase = stream->ep->id;
+
+	iov.iov_base = &rel;
+	iov.iov_len = sizeof(rel);
+
+	req = bap_req_new(stream, BT_ASCS_RELEASE, &iov, 1, func, user_data);
+
+	if (!bap_queue_req(stream->bap, req)) {
+		bap_req_free(req);
+		return 0;
+	}
+
+	return req->id;
+}
+
+uint8_t bt_bap_stream_get_dir(struct bt_bap_stream *stream)
+{
+	if (!stream)
+		return 0x00;
+
+	return stream->ep->dir;
+}
+
+uint32_t bt_bap_stream_get_location(struct bt_bap_stream *stream)
+{
+	struct bt_bap_pac *pac;
+
+	if (!stream)
+		return 0x00000000;
+
+	pac = stream->rpac ? stream->rpac : stream->lpac;
+
+	return pac->locations;
+}
+
+struct iovec *bt_bap_stream_get_config(struct bt_bap_stream *stream)
+{
+	if (!stream)
+		return NULL;
+
+	return stream->cc;
+}
+
+struct bt_bap_qos *bt_bap_stream_get_qos(struct bt_bap_stream *stream)
+{
+	if (!stream)
+		return NULL;
+
+	return &stream->qos;
+}
+
+struct iovec *bt_bap_stream_get_metadata(struct bt_bap_stream *stream)
+{
+	if (!stream)
+		return NULL;
+
+	return stream->meta;
+}
+
+struct io *bt_bap_stream_get_io(struct bt_bap_stream *stream)
+{
+	struct bt_bap_stream_io *io;
+
+	io = stream_get_io(stream);
+	if (!io || io->connecting)
+		return NULL;
+
+	return io->io;
+}
+
+static bool stream_io_disconnected(struct io *io, void *user_data)
+{
+	struct bt_bap_stream *stream = user_data;
+
+	DBG(stream->bap, "stream %p io disconnected", stream);
+
+	bt_bap_stream_set_io(stream, -1);
+
+	return false;
+}
+
+bool bt_bap_stream_set_io(struct bt_bap_stream *stream, int fd)
+{
+	if (!stream || (fd >= 0 && stream->io && !stream->io->connecting))
+		return false;
+
+	bap_stream_set_io(stream, INT_TO_PTR(fd));
+
+	queue_foreach(stream->links, bap_stream_set_io, INT_TO_PTR(fd));
+
+	return true;
+}
+
+static bool match_req_id(const void *data, const void *match_data)
+{
+	const struct bt_bap_req *req = data;
+	unsigned int id = PTR_TO_UINT(match_data);
+
+	return (req->id == id);
+}
+
+int bt_bap_stream_cancel(struct bt_bap_stream *stream, unsigned int id)
+{
+	struct bt_bap_req *req;
+
+	if (!stream)
+		return -EINVAL;
+
+	if (stream->bap->req && stream->bap->req->id == id) {
+		req = stream->bap->req;
+		stream->bap->req = NULL;
+		bap_req_free(req);
+		return 0;
+	}
+
+	req = queue_remove_if(stream->bap->reqs, match_req_id,
+						UINT_TO_PTR(id));
+	if (!req)
+		return 0;
+
+	bap_req_free(req);
+
+	return 0;
+}
+
+int bt_bap_stream_io_link(struct bt_bap_stream *stream,
+				struct bt_bap_stream *link)
+{
+	struct bt_bap *bap = stream->bap;
+
+	if (!stream || !link || stream == link)
+		return -EINVAL;
+
+	if (queue_find(stream->links, NULL, link))
+		return -EALREADY;
+
+	if (stream->client != link->client ||
+			stream->qos.cig_id != link->qos.cig_id ||
+			stream->qos.cis_id != link->qos.cis_id)
+		return -EINVAL;
+
+	if (!stream->links)
+		stream->links = queue_new();
+
+	if (!link->links)
+		link->links = queue_new();
+
+	queue_push_tail(stream->links, link);
+	queue_push_tail(link->links, stream);
+
+	/* Link IOs if already set on stream/link */
+	if (stream->io && !link->io)
+		link->io = stream_io_ref(stream->io);
+	else if (link->io && !stream->io)
+		stream->io = stream_io_ref(link->io);
+
+	DBG(bap, "stream %p link %p", stream, link);
+
+	return 0;
+}
+
+struct queue *bt_bap_stream_io_get_links(struct bt_bap_stream *stream)
+{
+	if (!stream)
+		return NULL;
+
+	return stream->links;
+}
+
+static void bap_stream_get_in_qos(void *data, void *user_data)
+{
+	struct bt_bap_stream *stream = data;
+	struct bt_bap_qos **qos = user_data;
+
+	if (!qos || *qos || stream->ep->dir != BT_BAP_SOURCE ||
+						!stream->qos.sdu)
+		return;
+
+	*qos = &stream->qos;
+}
+
+static void bap_stream_get_out_qos(void *data, void *user_data)
+{
+	struct bt_bap_stream *stream = data;
+	struct bt_bap_qos **qos = user_data;
+
+	if (!qos || *qos || stream->ep->dir != BT_BAP_SINK || !stream->qos.sdu)
+		return;
+
+	*qos = &stream->qos;
+}
+
+bool bt_bap_stream_io_get_qos(struct bt_bap_stream *stream,
+					struct bt_bap_qos **in,
+					struct bt_bap_qos **out)
+{
+	if (!stream || (!in && !out))
+		return false;
+
+	switch (stream->ep->dir) {
+	case BT_BAP_SOURCE:
+		bap_stream_get_in_qos(stream, in);
+		queue_foreach(stream->links, bap_stream_get_out_qos, out);
+		break;
+	case BT_BAP_SINK:
+		bap_stream_get_out_qos(stream, out);
+		queue_foreach(stream->links, bap_stream_get_in_qos, in);
+		break;
+	default:
+		return false;
+	}
+
+	DBG(stream->bap, "in %p out %p", in ? *in : NULL, out ? *out : NULL);
+
+	return in && out;
+}
+
+static void bap_stream_get_dir(void *data, void *user_data)
+{
+	struct bt_bap_stream *stream = data;
+	uint8_t *dir = user_data;
+
+	*dir |= stream->ep->dir;
+}
+
+uint8_t bt_bap_stream_io_dir(struct bt_bap_stream *stream)
+{
+	uint8_t dir;
+
+	if (!stream)
+		return 0x00;
+
+	dir = stream->ep->dir;
+
+	queue_foreach(stream->links, bap_stream_get_dir, &dir);
+
+	return dir;
+}
+
+static void bap_stream_io_connecting(void *data, void *user_data)
+{
+	struct bt_bap_stream *stream = data;
+	int fd = PTR_TO_INT(user_data);
+	const struct queue_entry *entry;
+
+	if (fd >= 0)
+		bap_stream_io_attach(stream, fd, true);
+	else
+		bap_stream_io_detach(stream);
+
+	for (entry = queue_get_entries(stream->bap->state_cbs); entry;
+							entry = entry->next) {
+		struct bt_bap_state *state = entry->data;
+
+		if (state->connecting)
+			state->connecting(stream, stream->io ? true : false,
+							fd, state->data);
+	}
+}
+
+int bt_bap_stream_io_connecting(struct bt_bap_stream *stream, int fd)
+{
+	if (!stream)
+		return -EINVAL;
+
+	bap_stream_io_connecting(stream, INT_TO_PTR(fd));
+
+	queue_foreach(stream->links, bap_stream_io_connecting, INT_TO_PTR(fd));
+
+	return 0;
+}
+
+bool bt_bap_stream_io_is_connecting(struct bt_bap_stream *stream, int *fd)
+{
+	struct bt_bap_stream_io *io;
+
+	if (!stream)
+		return false;
+
+	io = stream_get_io(stream);
+	if (!io)
+		return false;
+
+	if (fd)
+		*fd = stream_io_get_fd(io);
+
+	return io->connecting;
+}
diff --git a/src/shared/bap.h b/src/shared/bap.h
new file mode 100644
index 000000000000..f9b368deabd5
--- /dev/null
+++ b/src/shared/bap.h
@@ -0,0 +1,269 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2020  Intel Corporation. All rights reserved.
+ *
+ */
+
+#include <stdbool.h>
+#include <inttypes.h>
+
+#include "src/shared/io.h"
+
+#ifndef __packed
+#define __packed __attribute__((packed))
+#endif
+
+#define BT_BAP_SINK			0x01
+#define	BT_BAP_SOURCE			0x02
+
+#define BT_BAP_STREAM_STATE_IDLE	0x00
+#define BT_BAP_STREAM_STATE_CONFIG	0x01
+#define BT_BAP_STREAM_STATE_QOS		0x02
+#define BT_BAP_STREAM_STATE_ENABLING	0x03
+#define BT_BAP_STREAM_STATE_STREAMING	0x04
+#define BT_BAP_STREAM_STATE_DISABLING	0x05
+#define BT_BAP_STREAM_STATE_RELEASING	0x06
+
+#define BT_BAP_CONFIG_LATENCY_LOW	0x01
+#define BT_BAP_CONFIG_LATENCY_BALACED	0x02
+#define BT_BAP_CONFIG_LATENCY_HIGH	0x03
+
+#define BT_BAP_CONFIG_PHY_1M		0x01
+#define BT_BAP_CONFIG_PHY_2M		0x02
+#define BT_BAP_CONFIG_PHY_CODEC		0x03
+
+struct bt_bap;
+struct bt_bap_pac;
+struct bt_bap_stream;
+
+struct bt_bap_codec {
+	uint8_t  id;
+	uint16_t vid;
+	uint16_t cid;
+} __packed;
+
+struct bt_ltv {
+	uint8_t  len;
+	uint8_t  type;
+	uint8_t  value[0];
+} __packed;
+
+struct bt_bap_qos {
+	uint8_t  cig_id;
+	uint8_t  cis_id;
+	uint32_t interval;		/* Frame interval */
+	uint8_t  framing;		/* Frame framing */
+	uint8_t  phy;			/* PHY */
+	uint16_t sdu;			/* Maximum SDU Size */
+	uint8_t  rtn;			/* Retransmission Effort */
+	uint16_t latency;		/* Transport Latency */
+	uint32_t delay;			/* Presentation Delay */
+	uint8_t  target_latency;	/* Target Latency */
+};
+
+typedef void (*bt_bap_ready_func_t)(struct bt_bap *bap, void *user_data);
+typedef void (*bt_bap_destroy_func_t)(void *user_data);
+typedef void (*bt_bap_debug_func_t)(const char *str, void *user_data);
+typedef void (*bt_bap_pac_func_t)(struct bt_bap_pac *pac, void *user_data);
+typedef bool (*bt_bap_pac_foreach_t)(struct bt_bap_pac *lpac,
+					struct bt_bap_pac *rpac,
+					void *user_data);
+typedef void (*bt_bap_pac_select_t)(struct bt_bap_pac *pac, int err,
+					struct iovec *caps,
+					struct iovec *metadata,
+					struct bt_bap_qos *qos,
+					void *user_data);
+typedef void (*bt_bap_pac_config_t)(struct bt_bap_stream *stream, int err);
+typedef void (*bt_bap_state_func_t)(struct bt_bap_stream *stream,
+					uint8_t old_state, uint8_t new_state,
+					void *user_data);
+typedef void (*bt_bap_connecting_func_t)(struct bt_bap_stream *stream,
+					bool state, int fd,
+					void *user_data);
+typedef void (*bt_bap_stream_func_t)(struct bt_bap_stream *stream,
+					uint8_t code, uint8_t reason,
+					void *user_data);
+typedef void (*bt_bap_func_t)(struct bt_bap *bap, void *user_data);
+
+/* Local PAC related functions */
+
+unsigned int bt_bap_pac_register(bt_bap_pac_func_t added,
+				bt_bap_pac_func_t removed, void *user_data,
+				bt_bap_destroy_func_t destroy);
+bool bt_bap_pac_unregister(unsigned int id);
+
+struct bt_bap_pac_qos {
+	uint8_t  framing;
+	uint8_t  phy;
+	uint8_t  rtn;
+	uint16_t latency;
+	uint32_t pd_min;
+	uint32_t pd_max;
+	uint32_t ppd_min;
+	uint32_t ppd_max;
+};
+
+struct bt_bap_pac *bt_bap_add_vendor_pac(struct gatt_db *db,
+					const char *name, uint8_t type,
+					uint8_t id, uint16_t cid, uint16_t vid,
+					struct bt_bap_pac_qos *qos,
+					struct iovec *data,
+					struct iovec *metadata);
+
+struct bt_bap_pac *bt_bap_add_pac(struct gatt_db *db, const char *name,
+					uint8_t type, uint8_t id,
+					struct bt_bap_pac_qos *qos,
+					struct iovec *data,
+					struct iovec *metadata);
+
+struct bt_bap_pac_ops {
+	int (*select) (struct bt_bap_pac *pac, struct bt_bap_pac_qos *qos,
+			struct iovec *caps, struct iovec *metadata,
+			bt_bap_pac_select_t cb, void *cb_data, void *user_data);
+	int (*config) (struct bt_bap_stream *stream, struct iovec *cfg,
+			struct bt_bap_qos *qos, bt_bap_pac_config_t cb,
+			void *user_data);
+	void (*clear) (struct bt_bap_stream *stream, void *user_data);
+};
+
+bool bt_bap_pac_set_ops(struct bt_bap_pac *pac, struct bt_bap_pac_ops *ops,
+					void *user_data);
+
+bool bt_bap_remove_pac(struct bt_bap_pac *pac);
+
+uint8_t bt_bap_pac_get_type(struct bt_bap_pac *pac);
+
+struct bt_bap_stream *bt_bap_pac_get_stream(struct bt_bap_pac *pac);
+
+/* Session related function */
+unsigned int bt_bap_register(bt_bap_func_t added, bt_bap_func_t removed,
+							void *user_data);
+bool bt_bap_unregister(unsigned int id);
+
+struct bt_bap *bt_bap_new(struct gatt_db *ldb, struct gatt_db *rdb);
+
+bool bt_bap_set_user_data(struct bt_bap *bap, void *user_data);
+
+void *bt_bap_get_user_data(struct bt_bap *bap);
+
+struct bt_att *bt_bap_get_att(struct bt_bap *bap);
+
+struct bt_bap *bt_bap_ref(struct bt_bap *bap);
+void bt_bap_unref(struct bt_bap *bap);
+
+bool bt_bap_attach(struct bt_bap *bap, struct bt_gatt_client *client);
+void bt_bap_detach(struct bt_bap *bap);
+
+bool bt_bap_set_debug(struct bt_bap *bap, bt_bap_debug_func_t cb,
+			void *user_data, bt_bap_destroy_func_t destroy);
+
+bool bap_print_cc(void *data, size_t len, util_debug_func_t func,
+						void *user_data);
+
+unsigned int bt_bap_ready_register(struct bt_bap *bap,
+				bt_bap_ready_func_t func, void *user_data,
+				bt_bap_destroy_func_t destroy);
+bool bt_bap_ready_unregister(struct bt_bap *bap, unsigned int id);
+
+unsigned int bt_bap_state_register(struct bt_bap *bap,
+				bt_bap_state_func_t func,
+				bt_bap_connecting_func_t connecting,
+				void *user_data, bt_bap_destroy_func_t destroy);
+bool bt_bap_state_unregister(struct bt_bap *bap, unsigned int id);
+
+const char *bt_bap_stream_statestr(uint8_t state);
+
+void bt_bap_foreach_pac(struct bt_bap *bap, uint8_t type,
+			bt_bap_pac_foreach_t func, void *user_data);
+
+int bt_bap_pac_get_vendor_codec(struct bt_bap_pac *pac, uint8_t *id,
+				uint16_t *cid, uint16_t *vid,
+				struct iovec **data, struct iovec **metadata);
+
+int bt_bap_pac_get_codec(struct bt_bap_pac *pac, uint8_t *id,
+				struct iovec **data, struct iovec **metadata);
+
+/* Stream related functions */
+int bt_bap_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
+			bt_bap_pac_select_t func, void *user_data);
+
+struct bt_bap_stream *bt_bap_config(struct bt_bap *bap,
+					struct bt_bap_pac *lpac,
+					struct bt_bap_pac *rpac,
+					struct bt_bap_qos *pqos,
+					struct iovec *data,
+					bt_bap_stream_func_t func,
+					void *user_data);
+
+struct bt_bap *bt_bap_stream_get_session(struct bt_bap_stream *stream);
+uint8_t bt_bap_stream_get_state(struct bt_bap_stream *stream);
+
+bool bt_bap_stream_set_user_data(struct bt_bap_stream *stream, void *user_data);
+
+void *bt_bap_stream_get_user_data(struct bt_bap_stream *stream);
+
+unsigned int bt_bap_stream_config(struct bt_bap_stream *stream,
+					struct bt_bap_qos *pqos,
+					struct iovec *data,
+					bt_bap_stream_func_t func,
+					void *user_data);
+
+unsigned int bt_bap_stream_qos(struct bt_bap_stream *stream,
+					struct bt_bap_qos *qos,
+					bt_bap_stream_func_t func,
+					void *user_data);
+
+unsigned int bt_bap_stream_enable(struct bt_bap_stream *stream,
+					bool enable_links,
+					struct iovec *metadata,
+					bt_bap_stream_func_t func,
+					void *user_data);
+
+unsigned int bt_bap_stream_start(struct bt_bap_stream *stream,
+					bt_bap_stream_func_t func,
+					void *user_data);
+
+unsigned int bt_bap_stream_disable(struct bt_bap_stream *stream,
+					bool disable_links,
+					bt_bap_stream_func_t func,
+					void *user_data);
+
+unsigned int bt_bap_stream_stop(struct bt_bap_stream *stream,
+					bt_bap_stream_func_t func,
+					void *user_data);
+
+unsigned int bt_bap_stream_metadata(struct bt_bap_stream *stream,
+					struct iovec *metadata,
+					bt_bap_stream_func_t func,
+					void *user_data);
+
+unsigned int bt_bap_stream_release(struct bt_bap_stream *stream,
+					bt_bap_stream_func_t func,
+					void *user_data);
+
+uint8_t bt_bap_stream_get_dir(struct bt_bap_stream *stream);
+uint32_t bt_bap_stream_get_location(struct bt_bap_stream *stream);
+struct iovec *bt_bap_stream_get_config(struct bt_bap_stream *stream);
+struct bt_bap_qos *bt_bap_stream_get_qos(struct bt_bap_stream *stream);
+struct iovec *bt_bap_stream_get_metadata(struct bt_bap_stream *stream);
+
+struct io *bt_bap_stream_get_io(struct bt_bap_stream *stream);
+
+bool bt_bap_stream_set_io(struct bt_bap_stream *stream, int fd);
+
+int bt_bap_stream_cancel(struct bt_bap_stream *stream, unsigned int id);
+
+int bt_bap_stream_io_link(struct bt_bap_stream *stream,
+					struct bt_bap_stream *link);
+struct queue *bt_bap_stream_io_get_links(struct bt_bap_stream *stream);
+bool bt_bap_stream_io_get_qos(struct bt_bap_stream *stream,
+					struct bt_bap_qos **in,
+					struct bt_bap_qos **out);
+
+uint8_t bt_bap_stream_io_dir(struct bt_bap_stream *stream);
+
+int bt_bap_stream_io_connecting(struct bt_bap_stream *stream, int fd);
+bool bt_bap_stream_io_is_connecting(struct bt_bap_stream *stream, int *fd);