// SPDX-License-Identifier: LGPL-2.1-or-later
/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2022  Intel Corporation. All rights reserved.
 *  Copyright 2023-2025 NXP
 *
 */

#define _GNU_SOURCE
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>

#include "bluetooth/bluetooth.h"
#include "bluetooth/uuid.h"

#include "src/shared/io.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"
#include "src/shared/bap-debug.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

#define BAP_FREQ_LTV_TYPE 1
#define BAP_DURATION_LTV_TYPE 2
#define BAP_CHANNEL_ALLOCATION_LTV_TYPE 3
#define BAP_FRAME_LEN_LTV_TYPE 4
#define CODEC_SPECIFIC_CONFIGURATION_MASK (\
		(1<<BAP_FREQ_LTV_TYPE)|\
		(1<<BAP_DURATION_LTV_TYPE)|\
		(1<<BAP_FRAME_LEN_LTV_TYPE))

#define BAP_METADATA_PREF_CONTEXT_LTV_TYPE	0x01
#define BAP_METADATA_CONTEXT_LTV_TYPE		0x02
#define BAP_METADATA_PROGRAM_INFO_LTV_TYPE	0x03
#define BAP_METADATA_LANGUAGE_LTV_TYPE		0x04

struct bt_bap_pac_changed {
	unsigned int id;
	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_bis_cb {
	unsigned int id;
	bt_bap_bis_func_t probe;
	bt_bap_func_t remove;
	bt_bap_destroy_func_t destroy;
	void *data;
};

struct bt_bap_bcode_cb {
	unsigned int id;
	bt_bap_bcode_func_t func;
	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;
	uint32_t source_loc_value;
	uint32_t sink_loc_value;
	uint16_t source_context_value;
	uint16_t sink_context_value;
	uint16_t supported_source_context_value;
	uint16_t supported_sink_context_value;
};

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 *broadcast_sources;
	struct queue *broadcast_sinks;
};

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_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;
	unsigned int disconn_id;
	unsigned int idle_id;
	bool in_cp_write;

	struct queue *reqs;
	struct queue *notify;
	struct queue *streams;
	struct queue *local_eps;
	struct queue *remote_eps;

	struct queue *pac_cbs;
	struct queue *ready_cbs;
	struct queue *state_cbs;
	struct queue *bis_cbs;
	struct queue *bcode_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;
	struct bt_bap_codec codec;
	struct bt_bap_pac_qos qos;
	struct iovec *data;
	struct iovec *metadata;
	struct queue *channels;
	struct bt_bap_pac_ops *ops;
	void *user_data;
};

struct bt_bap_endpoint {
	struct bt_bap *bap;
	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_ops {
	uint8_t type;
	void (*set_state)(struct bt_bap_stream *stream, uint8_t state);
	unsigned int (*get_state)(struct bt_bap_stream *stream);
	unsigned int (*config)(struct bt_bap_stream *stream,
				struct bt_bap_qos *qos, struct iovec *data,
				bt_bap_stream_func_t func, void *user_data);
	unsigned int (*qos)(struct bt_bap_stream *stream,
				struct bt_bap_qos *qos,
				bt_bap_stream_func_t func, void *user_data);
	unsigned int (*enable)(struct bt_bap_stream *stream, bool enable_links,
				struct iovec *metadata,
				bt_bap_stream_func_t func, void *user_data);
	unsigned int (*start)(struct bt_bap_stream *stream,
				bt_bap_stream_func_t func, void *user_data);
	unsigned int (*disable)(struct bt_bap_stream *stream,
				bool disable_links, bt_bap_stream_func_t func,
				void *user_data);
	unsigned int (*stop)(struct bt_bap_stream *stream,
				bt_bap_stream_func_t func, void *user_data);
	unsigned int (*metadata)(struct bt_bap_stream *stream,
				struct iovec *data, bt_bap_stream_func_t func,
				void *user_data);
	unsigned int (*get_dir)(struct bt_bap_stream *stream);
	unsigned int (*get_loc)(struct bt_bap_stream *stream);
	unsigned int (*release)(struct bt_bap_stream *stream,
				bt_bap_stream_func_t func, void *user_data);
	void (*detach)(struct bt_bap_stream *stream);
	bool (*set_io)(struct bt_bap_stream *stream, int fd);
	struct bt_bap_stream_io* (*get_io)(struct bt_bap_stream *stream);
	uint8_t (*io_dir)(struct bt_bap_stream *stream);
	int (*io_link)(struct bt_bap_stream *stream,
					struct bt_bap_stream *link);
	int (*io_unlink)(struct bt_bap_stream *stream,
					struct bt_bap_stream *link);
};

struct bt_bap_stream {
	int ref_count;
	struct bt_bap *bap;
	struct bt_bap_endpoint *ep;
	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;
	const struct bt_bap_stream_ops *ops;
	uint8_t old_state;
	uint8_t state;
	unsigned int state_id;
	struct queue *pending_states;
	bool no_cache_config;
	bool client;
	bool locked;
	bool need_reconfig;
	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;

struct bt_base {
	uint8_t big_id;
	uint32_t pres_delay;
	uint8_t next_bis_index;
	struct queue *subgroups;
};

struct bt_subgroup {
	uint8_t index;
	struct bt_bap_codec codec;
	struct iovec *caps;
	struct iovec *meta;
	struct queue *bises;
};

struct bt_bis {
	uint8_t index;
	struct iovec *caps;
};

/* Contains local bt_bap_db */
static struct queue *bap_db;
static struct queue *bap_cbs;
static struct queue *sessions;

/* Structure holding the parameters for Periodic Advertisement create sync.
 * The full QOS is populated at the time the user selects and endpoint and
 * configures it using SetConfiguration.
 */
struct bt_iso_qos bap_sink_pa_qos = {
	.bcast = {
		.options		= 0x00,
		.skip			= 0x0000,
		.sync_timeout		= BT_ISO_SYNC_TIMEOUT,
		.sync_cte_type	= 0x00,
		/* TODO: The following parameters are not needed for PA Sync.
		 * They will be removed when the kernel checks will be removed.
		 */
		.big			= BT_ISO_QOS_BIG_UNSET,
		.bis			= BT_ISO_QOS_BIS_UNSET,
		.encryption		= 0x00,
		.bcode			= {0x00},
		.mse			= 0x00,
		.timeout		= BT_ISO_SYNC_TIMEOUT,
		.sync_factor		= 0x07,
		.packing		= 0x00,
		.framing		= 0x00,
		.in = {
			.interval	= 10000,
			.latency	= 10,
			.sdu		= 40,
			.phys		= BIT(1),
			.rtn		= 2,
		},
		.out = {
			.interval	= 10000,
			.latency	= 10,
			.sdu		= 40,
			.phys		= BIT(1),
			.rtn		= 2,
		}
	}
};

static void bap_stream_set_io(void *data, void *user_data);
static void stream_find_io(void *data, void *user_data);
static void bap_stream_get_dir(void *data, void *user_data);
static struct bt_bap_stream_io *stream_io_ref(struct bt_bap_stream_io *io);
static int bap_bcast_io_unlink(struct bt_bap_stream *stream,
				struct bt_bap_stream *link);

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);
}

unsigned int bt_bap_pac_register(struct bt_bap *bap, 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;
	static unsigned int id;

	if (!bap)
		return 0;

	changed = new0(struct bt_bap_pac_changed, 1);
	changed->id = ++id ? id : ++id;
	changed->added = added;
	changed->removed = removed;
	changed->destroy = destroy;
	changed->data = user_data;

	queue_push_tail(bap->pac_cbs, changed);

	return changed->id;
}

static void pac_changed_free(void *data)
{
	struct bt_bap_pac_changed *changed = data;

	if (changed->destroy)
		changed->destroy(changed->data);

	free(changed);
}

static bool match_pac_changed_id(const void *data, const void *match_data)
{
	const struct bt_bap_pac_changed *changed = data;
	unsigned int id = PTR_TO_UINT(match_data);

	return (changed->id == id);
}

bool bt_bap_pac_unregister(struct bt_bap *bap, unsigned int id)
{
	struct bt_bap_pac_changed *changed;

	if (!bap)
		return false;

	changed = queue_remove_if(bap->pac_cbs, match_pac_changed_id,
						UINT_TO_PTR(id));
	if (!changed)
		return false;

	pac_changed_free(changed);

	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 = util_iov_push(iov, sizeof(*rsp));
		rsp->num_pac = 0;
	} else
		rsp = iov->iov_base;

	rsp->num_pac++;

	p = util_iov_push(iov, sizeof(*p));
	p->codec.id = pac->codec.id;
	p->codec.cid = cpu_to_le16(pac->codec.cid);
	p->codec.vid = cpu_to_le16(pac->codec.vid);

	if (pac->data) {
		p->cc_len = pac->data->iov_len;
		util_iov_push_mem(iov, p->cc_len, pac->data->iov_base);
	} else
		p->cc_len = 0;

	meta = util_iov_push(iov, sizeof(*meta));

	if (pac->metadata) {
		meta->len = pac->metadata->iov_len;
		util_iov_push_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);
	queue_foreach(bdb->broadcast_sinks, pac_foreach, &iov);

	if (offset > iov.iov_len) {
		gatt_db_attribute_read_result(attrib, id,
						BT_ATT_ERROR_INVALID_OFFSET,
						NULL, 0);
		return;
	}

	gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base + offset,
							iov.iov_len - offset);
}

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)
{
	struct bt_pacs *pacs = user_data;
	uint32_t value = cpu_to_le32(pacs->sink_loc_value);

	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);

	if (offset > iov.iov_len) {
		gatt_db_attribute_read_result(attrib, id,
						BT_ATT_ERROR_INVALID_OFFSET,
						NULL, 0);
		return;
	}

	gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base + offset,
							iov.iov_len - offset);
}

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)
{
	struct bt_pacs *pacs = user_data;
	uint32_t value = cpu_to_le32(pacs->source_loc_value);

	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 *pacs = user_data;
	struct bt_pacs_context ctx = {
		.snk = cpu_to_le16(pacs->sink_context_value),
		.src = cpu_to_le16(pacs->source_context_value)
	};

	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 *pacs = user_data;
	struct bt_pacs_context ctx = {
		.snk = cpu_to_le16(pacs->supported_sink_context_value),
		.src = cpu_to_le16(pacs->supported_source_context_value)
	};

	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);
	gatt_db_attribute_set_fixed_length(pacs->sink_loc, sizeof(uint32_t));

	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->source = 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->source_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);
	gatt_db_attribute_set_fixed_length(pacs->source_loc, sizeof(uint32_t));

	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);
	gatt_db_attribute_set_fixed_length(pacs->context,
						sizeof(struct bt_pacs_context));

	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);
	gatt_db_attribute_set_fixed_length(pacs->supported_context,
						sizeof(struct bt_pacs_context));

	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;

	bap->disconn_id = 0;

	DBG(bap, "bap %p disconnected err %d", bap, err);

	bt_bap_detach(bap);
}

struct bt_bap *bt_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);
	if (!bap)
		return NULL;

	bap->att = att;

	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_endpoint_new_broadcast(struct bt_bap_db *bdb,
								uint8_t type)
{
	struct bt_bap_endpoint *ep;

	ep = new0(struct bt_bap_endpoint, 1);
	ep->bdb = bdb;
	ep->attr = NULL;
	if (type == BT_BAP_BCAST_SINK)
		ep->dir = BT_BAP_BCAST_SOURCE;
	else
		ep->dir = BT_BAP_BCAST_SINK;

	return ep;
}

static struct bt_bap_endpoint *bap_get_endpoint(struct queue *endpoints,
						struct bt_bap_db *db,
						struct gatt_db_attribute *attr)
{
	struct bt_bap_endpoint *ep;

	if (!db || !attr)
		return NULL;

	ep = queue_find(endpoints, bap_endpoint_match, attr);
	if (ep)
		return ep;

	ep = bap_endpoint_new(db, attr);
	if (!ep)
		return NULL;

	queue_push_tail(endpoints, ep);

	return ep;
}

static bool match_ep_type(const void *data, const void *match_data)
{
	const struct bt_bap_endpoint *ep = data;
	const uint8_t type = PTR_TO_INT(match_data);

	return (ep->dir == type);
}

static struct bt_bap_endpoint *bap_get_endpoint_bcast(struct queue *endpoints,
					struct bt_bap_db *db, uint8_t type)
{
	struct bt_bap_endpoint *ep;

	if (!db)
		return NULL;

	ep = queue_find(endpoints, match_ep_type, INT_TO_PTR(type));
	if (ep)
		return ep;

	ep = bap_endpoint_new_broadcast(db, type);
	if (!ep)
		return NULL;

	queue_push_tail(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_local_endpoint_id(struct bt_bap *bap,
								uint8_t id)
{
	struct bt_bap_endpoint *ep;
	struct gatt_db_attribute *attr = NULL;
	size_t i;

	if (!bap)
		return NULL;

	ep = queue_find(bap->local_eps, bap_endpoint_match_id, UINT_TO_PTR(id));
	if (ep)
		return ep;

	for (i = 0; i < ARRAY_SIZE(bap->ldb->ascs->ase); i++) {
		struct bt_ase *ase = bap->ldb->ascs->ase[i];

		if (id) {
			if (ase->id != id)
				continue;
			attr = ase->attr;
			break;
		}

		ep = queue_find(bap->local_eps, bap_endpoint_match, ase->attr);
		if (!ep) {
			attr = ase->attr;
			break;
		}
	}

	if (!attr)
		return NULL;

	ep = bap_endpoint_new(bap->ldb, attr);
	if (!ep)
		return NULL;

	ep->id = id;
	queue_push_tail(bap->local_eps, 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 = NULL;
	struct bt_bap_endpoint *ep = NULL;
	struct bt_ascs_ase_status rsp;

	if (ase)
		bap = bt_bap_get_session(att, ase->ascs->bdb->db);

	if (bap)
		ep = bap_get_endpoint(bap->local_eps, bap->ldb, attrib);

	if (!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 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 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_success(struct iovec *iov, uint8_t id)
{
	return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_SUCCESS,
					BT_ASCS_REASON_NONE);
}

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);

	if (!lpac)
		return;

	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 preferred settings if not set */
	if (!lpac->qos.phys)
		lpac->qos.phys = BIT(1);

	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 preferred settings on bt_bap_pac */
	config = (void *)status->params;
	config->framing = lpac->qos.framing;
	config->phys = lpac->qos.phys;
	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;

	if (config->codec.id == 0x0ff) {
		config->codec.vid = cpu_to_le16(config->codec.vid);
		config->codec.cid = cpu_to_le16(config->codec.cid);
	}

	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.ucast.cis_id;
	qos->cig_id = stream->qos.ucast.cig_id;
	put_le24(stream->qos.ucast.io_qos.interval, qos->interval);
	qos->framing = stream->qos.ucast.framing;
	qos->phys = stream->qos.ucast.io_qos.phys;
	qos->sdu = cpu_to_le16(stream->qos.ucast.io_qos.sdu);
	qos->rtn = stream->qos.ucast.io_qos.rtn;
	qos->latency = cpu_to_le16(stream->qos.ucast.io_qos.latency);
	put_le24(stream->qos.ucast.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, uint8_t state)
{
	struct bt_bap_endpoint *ep = stream->ep;
	struct bt_ascs_ase_status *status;
	struct bt_ascs_ase_status_metadata *meta;
	size_t len;
	size_t meta_len = 0;

	DBG(stream->bap, "stream %p", stream);

	if (stream->meta)
		meta_len = stream->meta->iov_len;

	len = sizeof(*status) + sizeof(*meta) + meta_len;
	status = malloc(len);

	memset(status, 0, len);
	status->id = ep->id;
	status->state = state;

	meta = (void *)status->params;
	meta->cis_id = stream->qos.ucast.cis_id;
	meta->cig_id = stream->qos.ucast.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 stream_notify_release(struct bt_bap_stream *stream)
{
	struct bt_bap_endpoint *ep = stream->ep;
	struct bt_ascs_ase_status status;

	DBG(stream->bap, "stream %p", stream);

	memset(&status, 0, sizeof(status));
	status.id = ep->id;
	status.state = BT_ASCS_ASE_STATE_RELEASING;

	gatt_db_attribute_notify(ep->attr, (void *)&status, sizeof(status),
					bt_bap_get_att(stream->bap));
}

static void stream_notify_idle(struct bt_bap_stream *stream)
{
	struct bt_bap_endpoint *ep = stream->ep;
	struct bt_ascs_ase_status status;

	DBG(stream->bap, "stream %p", stream);

	memset(&status, 0, sizeof(status));
	status.id = ep->id;
	status.state = BT_ASCS_ASE_STATE_IDLE;

	gatt_db_attribute_notify(ep->attr, (void *)&status, sizeof(status),
					bt_bap_get_att(stream->bap));
}

static struct bt_bap *bt_bap_ref_safe(struct bt_bap *bap)
{
	if (!bap || !bap->ref_count || !queue_find(sessions, NULL, bap))
		return NULL;

	return bt_bap_ref(bap);
}

static void bap_stream_clear_cfm(struct bt_bap_stream *stream)
{
	if (!stream->lpac || !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 *stream = data;
	struct bt_bap_stream *link = user_data;

	queue_remove(stream->links, link);
}

static void bap_stream_free(void *data)
{
	struct bt_bap_stream *stream = data;

	timeout_remove(stream->state_id);
	queue_destroy(stream->pending_states, NULL);

	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);
	util_iov_free(stream->cc, 1);
	util_iov_free(stream->meta, 1);
	free(stream);
}

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_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 bool match_req_stream(const void *data, const void *match_data)
{
	const struct bt_bap_req *req = data;

	return req->stream == match_data;
}

static void bap_req_abort(void *data)
{
	struct bt_bap_req *req = data;
	struct bt_bap *bap = req->stream->bap;

	DBG(bap, "req %p", req);
	bap_req_complete(req, NULL);
}

static void bap_abort_stream_req(struct bt_bap *bap,
						struct bt_bap_stream *stream)
{
	queue_remove_all(bap->reqs, match_req_stream, stream, bap_req_abort);

	if (bap->req && bap->req->stream == stream) {
		struct bt_bap_req *req = bap->req;

		bap->req = NULL;
		bap_req_complete(req, NULL);
	}
}

static void bt_bap_stream_unref(void *data)
{
	struct bt_bap_stream *stream = data;

	if (!stream)
		return;

	if (__sync_sub_and_fetch(&stream->ref_count, 1))
		return;

	bap_stream_free(stream);
}

static void bap_ucast_detach(struct bt_bap_stream *stream)
{
	struct bt_bap_endpoint *ep = stream->ep;

	if (!ep)
		return;

	DBG(stream->bap, "stream %p ep %p", stream, ep);

	bap_abort_stream_req(stream->bap, stream);

	queue_remove(stream->bap->streams, stream);
	bap_stream_clear_cfm(stream);

	ep->stream = NULL;

	if (!stream->client) {
		ep->state = BT_ASCS_ASE_STATE_IDLE;
		ep->old_state = BT_ASCS_ASE_STATE_IDLE;
	}

	bt_bap_stream_unref(stream);
}

static void bap_bcast_src_detach(struct bt_bap_stream *stream)
{
	struct bt_bap_endpoint *ep = stream->ep;

	if (!ep)
		return;

	DBG(stream->bap, "stream %p ep %p", stream, ep);

	queue_remove(stream->bap->streams, stream);
	bap_stream_clear_cfm(stream);

	stream->ep = NULL;
	ep->stream = NULL;

	bt_bap_stream_unref(stream);
}

static void bap_bcast_sink_detach(struct bt_bap_stream *stream)
{
	DBG(stream->bap, "stream %p", stream);

	queue_remove(stream->bap->streams, stream);
	bap_stream_clear_cfm(stream);

	bt_bap_stream_unref(stream);
}

static bool bap_stream_io_link(const void *data, const void *user_data)
{
	struct bt_bap_stream *stream = (void *)data;
	struct bt_bap_stream *link = (void *)user_data;

	return !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_find(bap->streams, bap_stream_io_link, stream);
}

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 stream_state_changed(void *data, void *user_data)
{
	const struct bt_bap_state *state = data;
	struct bt_bap_stream *stream = user_data;

	if (state->func)
		state->func(stream, stream->old_state, stream->state,
							state->data);
}

static void bap_stream_state_changed(struct bt_bap_stream *stream)
{
	struct bt_bap *bap = stream->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:
		/* As client, we detach after Receiver Stop Ready */
		if (!stream->client)
			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;
	}

	stream->old_state = stream->ep->old_state;
	stream->state = stream->ep->state;

	/* Notify callbacks using queue_foreach since it does attempt to
	 * protect against concurrent modifications to the list.
	 */
	queue_foreach(bap->state_cbs, stream_state_changed, stream);

	/* Post notification updates */
	switch (stream->ep->state) {
	case BT_ASCS_ASE_STATE_IDLE:
		if (bap->req && bap->req->stream == stream) {
			bap_req_complete(bap->req, NULL);
			bap->req = NULL;
		}

		if (stream->ops && stream->ops->detach)
			stream->ops->detach(stream);

		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:
		/* Client may terminate CIS after Receiver Stop Ready completes
		 * successfully (BAP v1.0.2, 5.6.5.1). Do it when back to QOS.
		 * Ensure IO is detached also if CIS was not yet established.
		 */
		if (stream->client) {
			bt_bap_stream_stop(stream, NULL, NULL);
			if (stream->io)
				stream->io->connecting = false;
		}
		break;
	case BT_ASCS_ASE_STATE_RELEASING:
		if (stream->client) {
			bap_stream_clear_cfm(stream);
			bap_stream_io_detach(stream);
			bt_bap_stream_io_unlink(stream, NULL);
		}
		break;
	}
}

/* Return false if the stream is being detached */
static bool stream_set_state(struct bt_bap_stream *stream, uint8_t state)
{
	struct bt_bap *bap = stream->bap;

	/* Check if ref_count is already 0 which means detaching is in
	 * progress.
	 */
	bap = bt_bap_ref_safe(bap);
	if (!bap) {
		if (stream->ops && stream->ops->detach)
			stream->ops->detach(stream);

		return false;
	}

	if (stream->ops && stream->ops->set_state)
		stream->ops->set_state(stream, state);

	bt_bap_unref(bap);
	return true;
}

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);

	if (!pac) {
		ascs_ase_rsp_add(rsp, stream->ep->id, BT_ASCS_RSP_CONF_REJECTED,
							BT_ASCS_REASON_CODEC);
		return 0;
	}

	/* TODO: Wait for pac->ops response */
	ascs_ase_rsp_success(rsp, stream->ep->id);

	if (!util_iov_memcmp(stream->cc, cc)) {
		stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG);
		return 0;
	}

	util_iov_free(stream->cc, 1);
	stream->cc = util_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 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 ? id : ++id;
	req->stream = stream;
	req->op = op;
	req->iov = util_iov_dup(iov, len);
	req->len = len;
	req->func = func;
	req->user_data = user_data;

	return req;
}

static uint16_t bap_req_len(struct bt_bap_req *req)
{
	uint16_t len = 0;
	size_t i;
	const struct queue_entry *e;

	for (i = 0; i < req->len; i++)
		len += req->iov[i].iov_len;

	e = queue_get_entries(req->group);
	for (; e; e = e->next)
		len += bap_req_len(e->data);

	return len;
}

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 struct bt_ascs *bap_get_ascs(struct bt_bap *bap)
{
	if (!bap || !bap->rdb)
		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 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++)
		util_iov_push_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;
	struct bt_ascs_ase_hdr hdr;
	struct iovec iov;
	size_t i;

	iov.iov_len = sizeof(hdr) + bap_req_len(req);

	DBG(bap, "req %p len %u", req, iov.iov_len);

	if (req->stream && !queue_find(bap->streams, NULL, req->stream)) {
		DBG(bap, "stream %p detached, aborting op 0x%02x", req->stream,
								req->op);
		return false;
	}

	if (!gatt_db_attribute_get_char_data(ascs->ase_cp, NULL, &handle,
						NULL, NULL, NULL)) {
		DBG(bap, "Unable to find Control Point");
		return false;
	}

	iov.iov_base = alloca(iov.iov_len);
	iov.iov_len = 0;

	hdr.op = req->op;
	hdr.num = 1 + queue_length(req->group);

	util_iov_push_mem(&iov, sizeof(hdr), &hdr);

	for (i = 0; i < req->len; i++)
		util_iov_push_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) {
		DBG(bap, "Unable to Write to Control Point");
		return false;
	}

	bap->req = req;

	return true;
}

static bool bap_process_queue(void *data)
{
	struct bt_bap *bap = data;
	struct bt_bap_req *req;

	DBG(bap, "");

	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;
		bap_req_complete(req, NULL);
	}

	return false;
}

static bool bap_queue_req(struct bt_bap *bap, struct bt_bap_req *req)
{
	struct bt_bap_req *pend;
	struct queue *queue;
	struct bt_att *att = bt_bap_get_att(bap);
	uint16_t mtu = bt_att_get_mtu(att);
	uint16_t len = 3 + 2 + bap_req_len(req);

	if (len > mtu) {
		DBG(bap, "Unable to queue request: req len %u > %u mtu", len,
									mtu);
		return false;
	}

	pend = queue_find(bap->reqs, match_req, req);
	/* Check if req can be grouped together and it fits in the MTU */
	if (pend && (bap_req_len(pend) + len < mtu)) {
		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 stream_notify(struct bt_bap_stream *stream, uint8_t state)
{
	DBG(stream->bap, "stream %p state %d", stream, state);

	switch (state) {
	case BT_ASCS_ASE_STATE_IDLE:
		stream_notify_idle(stream);
		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, state);
		break;
	case BT_ASCS_ASE_STATE_RELEASING:
		stream_notify_release(stream);
		break;
	}
}

static bool stream_notify_state(void *data)
{
	struct bt_bap_stream *stream = data;
	struct bt_bap_endpoint *ep = stream->ep;
	uint8_t state;

	if (stream->state_id) {
		timeout_remove(stream->state_id);
		stream->state_id = 0;
	}

	/* Notify any pending states before notifying ep->state */
	while ((state = PTR_TO_UINT(queue_pop_head(stream->pending_states))))
		stream_notify(stream, state);

	stream_notify(stream, ep->state);

	return false;
}

static struct bt_bap_stream *bt_bap_stream_ref(struct bt_bap_stream *stream)
{
	if (!stream)
		return NULL;

	__sync_fetch_and_add(&stream->ref_count, 1);

	return stream;
}

static void bap_ucast_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;

	DBG(stream->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));

	if (stream->client)
		goto done;

	if (!stream->bap->in_cp_write)
		stream_notify_state(stream);
	else if (!stream->state_id)
		stream->state_id = timeout_add(BAP_PROCESS_TIMEOUT,
						stream_notify_state,
						bt_bap_stream_ref(stream),
						bt_bap_stream_unref);
	else /* If a state_id is already pending then queue the old one */
		queue_push_tail(stream->pending_states,
				UINT_TO_PTR(ep->old_state));


done:
	bap_stream_state_changed(stream);
}

static unsigned int bap_ucast_get_state(struct bt_bap_stream *stream)
{
	return stream->ep->state;
}

static uint8_t bits_to_phy(uint8_t bits)
{
	uint8_t phy = 0x00;

	/* Convert PHY bits to PHY values on a ascending order. */
	if (bits & BIT(0))
		phy = 0x01; /* LE 1M */

	if (bits & BIT(1))
		phy = 0x02; /* LE 2M */

	if (bits & BIT(2))
		phy = 0x03; /* LE Coded */

	return phy;
}

static unsigned int bap_ucast_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 (!stream->client) {
		stream_config(stream, data, NULL);
		return -EINVAL;
	}

	memset(&config, 0, sizeof(config));

	config.ase = stream->ep->id;
	config.latency = qos->ucast.target_latency;
	config.phy = bits_to_phy(qos->ucast.io_qos.phys);
	config.codec = stream->rpac->codec;

	if (config.codec.id == 0xff) {
		config.codec.cid = cpu_to_le16(config.codec.cid);
		config.codec.vid = cpu_to_le16(config.codec.vid);
	}

	iov[0].iov_base = &config;
		iov[0].iov_len = sizeof(config);

	if (data) {
		if (!bt_bap_debug_config(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 unsigned int bap_ucast_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;

	/* Table 3.2: ASE state machine transition
	 * Initiating device - client Only
	 */
	if (!stream->client)
		return 0;

	if (stream->need_reconfig)
		return 0;

	memset(&qos, 0, sizeof(qos));

	/* TODO: Figure out how to pass these values around */
	qos.ase = stream->ep->id;
	qos.cig = data->ucast.cig_id;
	qos.cis = data->ucast.cis_id;
	put_le24(data->ucast.io_qos.interval, qos.interval);
	qos.framing = data->ucast.framing;
	qos.phys = data->ucast.io_qos.phys;
	qos.sdu = cpu_to_le16(data->ucast.io_qos.sdu);
	qos.rtn = data->ucast.io_qos.rtn;
	qos.latency = cpu_to_le16(data->ucast.io_qos.latency);
	put_le24(data->ucast.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 void bap_stream_get_context(size_t i, uint8_t l, uint8_t t, uint8_t *v,
					void *user_data)
{
	bool *found = user_data;

	if (!v)
		return;

	*found = true;
}

static unsigned 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;
	uint16_t value = cpu_to_le16(0x0001); /* 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) {
		util_iov_free(stream->meta, 1);
		stream->meta = util_iov_dup(data, 1);
	}

	/* Check if metadata contains an Audio Context */
	if (stream->meta) {
		uint8_t type = 0x02;
		bool found = false;

		util_ltv_foreach(stream->meta->iov_base,
				stream->meta->iov_len, &type,
				bap_stream_get_context, &found);
		if (!found)
			util_ltv_push(stream->meta, sizeof(value), type,
				      &value);
	}

	/* If metadata doesn't contain an Audio Context, add one */
	if (!stream->meta) {
		stream->meta = new0(struct iovec, 1);
		util_ltv_push(stream->meta, sizeof(value), 0x02, &value);
	}

	iov[1].iov_base = stream->meta->iov_base;
	iov[1].iov_len = stream->meta->iov_len;

	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 unsigned int bap_bcast_qos(struct bt_bap_stream *stream,
					struct bt_bap_qos *data,
					bt_bap_stream_func_t func,
					void *user_data)
{
	stream->qos = *data;
	return 1;
}

static unsigned int bap_bcast_config(struct bt_bap_stream *stream,
				     struct bt_bap_qos *qos, struct iovec *data,
				     bt_bap_stream_func_t func, void *user_data)
{
	if (!stream->lpac)
		return 0;

	stream->qos = *qos;
	stream->lpac->ops->config(stream, stream->cc, &stream->qos,
			ep_config_cb, stream->lpac->user_data);

	return 1;
}

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);
}

static unsigned int bap_ucast_enable(struct bt_bap_stream *stream,
					bool enable_links, struct iovec *data,
					bt_bap_stream_func_t func,
					void *user_data)
{
	int ret;

	/* Table 3.2: ASE state machine transition
	 * Initiating device - client Only
	 */
	if (!stream->client)
		return 0;

	ret = bap_stream_metadata(stream, BT_ASCS_ENABLE, data, func,
					user_data);
	if (!ret || !enable_links)
		return ret;

	queue_foreach(stream->links, bap_stream_enable_link, data);

	return ret;
}

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 unsigned int bap_ucast_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 (!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 uint8_t stream_disable(struct bt_bap_stream *stream, struct iovec *rsp)
{
	if (!stream)
		return 0;

	switch (stream->ep->state) {
	case BT_BAP_STREAM_STATE_ENABLING:
	case BT_BAP_STREAM_STATE_STREAMING:
		break;
	default:
		return 0;
	}

	DBG(stream->bap, "stream %p", stream);

	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);

	if (stream->ep->dir == BT_BAP_SOURCE)
		stream_set_state(stream, BT_BAP_STREAM_STATE_DISABLING);

	return 0;
}

static void bap_stream_disable_link(void *data, void *user_data)
{
	struct bt_bap_stream *stream = data;

	bt_bap_stream_disable(stream, false, NULL, NULL);
}

static unsigned int bap_ucast_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 (!stream->client)
		return stream_disable(stream, NULL);

	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;
}

static uint8_t stream_stop(struct bt_bap_stream *stream, struct iovec *rsp)
{
	if (!stream)
		return 0;

	DBG(stream->bap, "stream %p", stream);

	ascs_ase_rsp_success(rsp, stream->ep->id);

	stream_set_state(stream, BT_BAP_STREAM_STATE_QOS);

	return 0;
}

static unsigned int bap_ucast_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 (!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;
}

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);

	util_iov_free(stream->meta, 1);
	stream->meta = util_iov_dup(meta, 1);

	switch (bt_bap_stream_get_state(stream)) {
	case BT_BAP_STREAM_STATE_IDLE:
		/* Initial metadata */
		break;
	default:
		/* Force state change to the same state to update metadata */
		stream_set_state(stream, bt_bap_stream_get_state(stream));
	}

	return 0;
}

static unsigned int bap_ucast_metadata(struct bt_bap_stream *stream,
					struct iovec *data,
					bt_bap_stream_func_t func,
					void *user_data)
{
	if (!stream->client) {
		stream_metadata(stream, data, NULL);
		return 0;
	}

	switch (bt_bap_stream_get_state(stream)) {
	/* Valid only if ASE_State field = 0x03 (Enabling) */
	case BT_BAP_STREAM_STATE_ENABLING:
	 /* or 0x04 (Streaming) */
	case BT_BAP_STREAM_STATE_STREAMING:
		return bap_stream_metadata(stream, BT_ASCS_METADATA, data, func,
						user_data);
	}

	stream_metadata(stream, data, NULL);
	return 0;
}

static uint8_t stream_release(struct bt_bap_stream *stream, struct iovec *rsp)
{
	DBG(stream->bap, "stream %p", stream);

	ascs_ase_rsp_success(rsp, stream->ep->id);

	/* In case the stream IO is already down the released transition needs
	 * to take action immeditely.
	 */
	if (!stream->io) {
		bool cache_config = !stream->no_cache_config;

		switch (bt_bap_stream_get_state(stream)) {
		case BT_BAP_STREAM_STATE_CONFIG:
			/* Released (no caching) */
			cache_config = false;
			break;
		default:
			/* Released (caching) */
			break;
		}

		stream_set_state(stream, BT_BAP_STREAM_STATE_RELEASING);
		if (cache_config)
			stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG);
		else
			stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE);
	} else
		stream_set_state(stream, BT_BAP_STREAM_STATE_RELEASING);

	return 0;
}

static bool bap_stream_valid(struct bt_bap_stream *stream)
{
	if (!stream || !stream->bap)
		return false;

	return queue_find(stream->bap->streams, NULL, stream);
}

static unsigned int bap_ucast_get_dir(struct bt_bap_stream *stream)
{
	return stream->ep->dir;
}

static unsigned int bap_ucast_get_location(struct bt_bap_stream *stream)
{
	struct bt_pacs *pacs;

	if (!stream)
		return 0x00000000;

	pacs = stream->client ? stream->bap->rdb->pacs : stream->bap->ldb->pacs;

	if (stream->ep->dir == BT_BAP_SOURCE)
		return pacs->source_loc_value;
	else if (stream->ep->dir == BT_BAP_SINK)
		return pacs->sink_loc_value;
	return 0x00000000;
}

static unsigned int bap_ucast_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;
	struct bt_bap *bap;

	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);

	bap = stream->bap;

	/* If stream does not belong to a client session, clean it up now */
	if (!bap_stream_valid(stream)) {
		stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE);
		return 0;
	}

	req = bap_req_new(stream, BT_ASCS_RELEASE, &iov, 1, func, user_data);
	if (!bap_queue_req(bap, req)) {
		bap_req_free(req);
		return 0;
	}

	return req->id;
}

static void bap_bcast_set_state(struct bt_bap_stream *stream, uint8_t state)
{
	struct bt_bap *bap = stream->bap;

	stream->old_state = stream->state;
	stream->state = state;

	bt_bap_stream_ref(stream);

	DBG(bap, "stream %p dir 0x%02x: %s -> %s", stream,
			bt_bap_stream_get_dir(stream),
			bt_bap_stream_statestr(stream->old_state),
			bt_bap_stream_statestr(stream->state));

	/* Notify callbacks using queue_foreach since it does attempt to
	 * protect against concurrent modifications to the list.
	 */
	queue_foreach(bap->state_cbs, stream_state_changed, stream);

	/* Post notification updates */
	switch (stream->state) {
	case BT_ASCS_ASE_STATE_IDLE:
		if (stream->ops && stream->ops->detach)
			stream->ops->detach(stream);
		break;
	case BT_ASCS_ASE_STATE_RELEASING:
		bap_stream_io_detach(stream);
		break;
	case BT_ASCS_ASE_STATE_ENABLING:
		if (bt_bap_stream_get_io(stream))
			/* Start stream if fd has already been set */
			bt_bap_stream_start(stream, NULL, NULL);

		break;
	}

	bt_bap_stream_unref(stream);
}

static unsigned int bap_bcast_get_state(struct bt_bap_stream *stream)
{
	return stream->state;
}

static bool bcast_sink_stream_enabled(const void *data, const void *match_data)
{
	struct bt_bap_stream *stream = (struct bt_bap_stream *)data;
	struct bt_bap_stream *match = (struct bt_bap_stream *)match_data;
	uint8_t state = bt_bap_stream_get_state(stream);

	if (stream == match)
		return false;

	if (queue_find(stream->links, NULL, match))
		return false;

	/* Ignore streams that are not Broadcast Sink */
	if (bt_bap_pac_get_type(stream->lpac) != BT_BAP_BCAST_SINK)
		return false;

	return ((state == BT_BAP_STREAM_STATE_ENABLING) ||
			bt_bap_stream_get_io(stream));
}

static unsigned int bap_bcast_sink_enable(struct bt_bap_stream *stream,
					bool enable_links, struct iovec *data,
					bt_bap_stream_func_t func,
					void *user_data)
{
	struct bt_bap *bap = stream->bap;

	/* The stream cannot be enabled if there is any other
	 * unlinked stream for the same source that is in the
	 * process of enabling or that has already been started.
	 */
	if (queue_find(bap->streams, bcast_sink_stream_enabled, stream))
		return 0;

	stream_set_state(stream, BT_BAP_STREAM_STATE_ENABLING);

	return 1;
}

static unsigned int bap_bcast_src_enable(struct bt_bap_stream *stream,
					bool enable_links, struct iovec *data,
					bt_bap_stream_func_t func,
					void *user_data)
{
	stream_set_state(stream, BT_BAP_STREAM_STATE_ENABLING);

	return 1;
}

static unsigned int bap_bcast_start(struct bt_bap_stream *stream,
					bt_bap_stream_func_t func,
					void *user_data)
{
	stream_set_state(stream, BT_BAP_STREAM_STATE_STREAMING);

	return 1;
}

static unsigned int bap_bcast_disable(struct bt_bap_stream *stream,
					bool disable_links,
					bt_bap_stream_func_t func,
					void *user_data)
{
	bap_stream_io_detach(stream);
	stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG);

	return 1;
}

static unsigned int bap_bcast_metadata(struct bt_bap_stream *stream,
					struct iovec *data,
					bt_bap_stream_func_t func,
					void *user_data)
{
	util_iov_free(stream->meta, 1);
	stream->meta = util_iov_dup(data, 1);

	if (func)
		func(stream, BT_ASCS_RSP_SUCCESS, 0x00, user_data);

	return 1;
}

static unsigned int bap_bcast_src_get_dir(struct bt_bap_stream *stream)
{
	return BT_BAP_BCAST_SINK;
}

static unsigned int bap_bcast_sink_get_dir(struct bt_bap_stream *stream)
{
	return BT_BAP_BCAST_SOURCE;
}

static void bap_sink_get_allocation(size_t i, uint8_t l, uint8_t t,
		uint8_t *v, void *user_data)
{
	uint32_t location32;

	if (!v)
		return;

	memcpy(&location32, v, l);
	*((uint32_t *)user_data) = le32_to_cpu(location32);
}

static unsigned int bap_bcast_get_location(struct bt_bap_stream *stream)
{
	uint8_t type = BAP_CHANNEL_ALLOCATION_LTV_TYPE;
	uint32_t allocation = 0;
	struct iovec *caps;

	caps = bt_bap_stream_get_config(stream);

	/* Get stream allocation from capabilities */
	util_ltv_foreach(caps->iov_base, caps->iov_len, &type,
			bap_sink_get_allocation, &allocation);

	return allocation;
}

static unsigned int bap_bcast_release(struct bt_bap_stream *stream,
					bt_bap_stream_func_t func,
					void *user_data)
{
	stream_set_state(stream, BT_BAP_STREAM_STATE_RELEASING);
	stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE);

	return 1;
}

static bool bap_ucast_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 bap_bcast_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));

	return true;
}

static struct bt_bap_stream_io *bap_ucast_get_io(struct bt_bap_stream *stream)
{
	struct bt_bap_stream_io *io = NULL;

	if (!stream)
		return NULL;

	if (stream->io)
		return stream->io;

	queue_foreach(stream->links, stream_find_io, &io);

	return io;
}

static struct bt_bap_stream_io *bap_bcast_get_io(struct bt_bap_stream *stream)
{
	if (!stream)
		return NULL;

	return stream->io;
}

static uint8_t bap_ucast_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 uint8_t bap_bcast_io_dir(struct bt_bap_stream *stream)
{
	uint8_t dir;
	uint8_t pac_type;

	if (!stream)
		return 0x00;

	pac_type = bt_bap_pac_get_type(stream->lpac);

	if (pac_type == BT_BAP_BCAST_SINK)
		dir = BT_BAP_BCAST_SOURCE;
	else
		dir = BT_BAP_BCAST_SINK;

	return dir;
}

static int bap_ucast_io_link(struct bt_bap_stream *stream,
				struct bt_bap_stream *link)
{
	struct bt_bap *bap;

	if (!stream || !link || stream == link)
		return -EINVAL;

	bap = stream->bap;

	if (!queue_isempty(stream->links) || !queue_isempty(link->links))
		return -EALREADY;

	if (stream->client != link->client ||
			stream->qos.ucast.cig_id != link->qos.ucast.cig_id ||
			stream->qos.ucast.cis_id != link->qos.ucast.cis_id ||
			stream->ep->dir == link->ep->dir)
		return -EINVAL;

	/* The server pairs Sink and Source by matching CIG/CIS, but those are
	 * unset until QoS Configured, so it would link unrelated ASEs (CIS 0
	 * is otherwise a valid ID). Clients gate linking on the lock instead.
	 */
	if (!stream->client &&
			(stream->qos.ucast.cis_id == BT_ISO_QOS_CIS_UNSET ||
			link->qos.ucast.cis_id == BT_ISO_QOS_CIS_UNSET))
		return -EINVAL;

	if (stream->client && !(stream->locked && link->locked))
		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;
}

static void stream_unlink_ucast(void *data)
{
	struct bt_bap_stream *link = data;

	DBG(link->bap, "stream %p unlink", link);

	queue_destroy(link->links, NULL);
	link->links = NULL;
}

static int bap_ucast_io_unlink(struct bt_bap_stream *stream,
						struct bt_bap_stream *link)
{
	if (!stream)
		return -EINVAL;

	queue_destroy(stream->links, stream_unlink_ucast);
	stream->links = NULL;

	DBG(stream->bap, "stream %p unlink", stream);
	return 0;

}

static void stream_link(void *data, void *user_data)
{
	struct bt_bap_stream *stream = (void *)data;
	struct bt_bap_stream *link = (void *)user_data;

	bt_bap_stream_io_link(stream, link);
}

static int bap_bcast_io_link(struct bt_bap_stream *stream,
				struct bt_bap_stream *link)
{
	struct bt_bap *bap;

	if (!stream || !link || stream == link)
		return -EINVAL;

	bap = stream->bap;

	if (queue_find(stream->links, NULL, link) ||
		queue_find(link->links, NULL, stream))
		return -EALREADY;

	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);

	DBG(bap, "stream %p link %p", stream, link);

	queue_foreach(stream->links, stream_link, link);

	return 0;
}

static void stream_unlink(void *data, void *user_data)
{
	struct bt_bap_stream *stream = (void *)data;
	struct bt_bap_stream *link = (void *)user_data;

	bap_bcast_io_unlink(stream, link);
}

static int bap_bcast_io_unlink(struct bt_bap_stream *stream,
				struct bt_bap_stream *link)
{
	struct bt_bap *bap;

	if (!stream || !link || stream == link)
		return -EINVAL;

	bap = stream->bap;

	if (!queue_find(stream->links, NULL, link) ||
		!queue_find(link->links, NULL, stream))
		return -EALREADY;

	queue_remove(stream->links, link);
	queue_remove(link->links, stream);

	DBG(bap, "stream %p unlink %p", stream, link);

	queue_foreach(stream->links, stream_unlink, link);

	return 0;
}

#define STREAM_OPS(_type, _set_state, _get_state, _config, _qos, _enable, \
	_start, _disable, _stop, _metadata, _get_dir, _get_loc, _release, \
	_detach, _set_io, _get_io, _io_dir, _io_link, _io_unlink) \
{ \
	.type = _type, \
	.set_state = _set_state, \
	.get_state = _get_state, \
	.config = _config, \
	.qos = _qos, \
	.enable = _enable, \
	.start = _start, \
	.disable = _disable, \
	.stop = _stop, \
	.metadata = _metadata, \
	.get_dir = _get_dir,\
	.get_loc = _get_loc, \
	.release = _release, \
	.detach = _detach, \
	.set_io = _set_io, \
	.get_io = _get_io, \
	.io_dir = _io_dir, \
	.io_link = _io_link, \
	.io_unlink = _io_unlink, \
}

static const struct bt_bap_stream_ops stream_ops[] = {
	STREAM_OPS(BT_BAP_SINK, bap_ucast_set_state,
			bap_ucast_get_state,
			bap_ucast_config, bap_ucast_qos, bap_ucast_enable,
			bap_ucast_start, bap_ucast_disable, bap_ucast_stop,
			bap_ucast_metadata, bap_ucast_get_dir,
			bap_ucast_get_location,
			bap_ucast_release, bap_ucast_detach,
			bap_ucast_set_io, bap_ucast_get_io,
			bap_ucast_io_dir, bap_ucast_io_link,
			bap_ucast_io_unlink),
	STREAM_OPS(BT_BAP_SOURCE, bap_ucast_set_state,
			bap_ucast_get_state,
			bap_ucast_config, bap_ucast_qos, bap_ucast_enable,
			bap_ucast_start, bap_ucast_disable, bap_ucast_stop,
			bap_ucast_metadata, bap_ucast_get_dir,
			bap_ucast_get_location,
			bap_ucast_release, bap_ucast_detach,
			bap_ucast_set_io, bap_ucast_get_io,
			bap_ucast_io_dir, bap_ucast_io_link,
			bap_ucast_io_unlink),
	STREAM_OPS(BT_BAP_BCAST_SINK, bap_bcast_set_state,
			bap_bcast_get_state,
			bap_bcast_config, bap_bcast_qos, bap_bcast_sink_enable,
			bap_bcast_start, bap_bcast_disable, NULL,
			bap_bcast_metadata, bap_bcast_sink_get_dir,
			bap_bcast_get_location,
			bap_bcast_release, bap_bcast_sink_detach,
			bap_bcast_set_io, bap_bcast_get_io,
			bap_bcast_io_dir, bap_bcast_io_link,
			bap_bcast_io_unlink),
	STREAM_OPS(BT_BAP_BCAST_SOURCE, bap_bcast_set_state,
			bap_bcast_get_state,
			bap_bcast_config, bap_bcast_qos, bap_bcast_src_enable,
			bap_bcast_start, bap_bcast_disable, NULL,
			bap_bcast_metadata, bap_bcast_src_get_dir,
			bap_bcast_get_location,
			bap_bcast_release, bap_bcast_src_detach,
			bap_bcast_set_io, bap_bcast_get_io,
			bap_bcast_io_dir, bap_bcast_io_link,
			bap_bcast_io_unlink),
};

static const struct bt_bap_stream_ops *
bap_stream_new_ops(struct bt_bap_stream *stream)
{
	const struct bt_bap_stream_ops *ops;
	uint8_t type = bt_bap_pac_get_type(stream->lpac);
	size_t i;

	for (i = 0; i < ARRAY_SIZE(stream_ops); i++) {
		ops = &stream_ops[i];

		if (ops->type == type)
			return ops;
	}

	return NULL;
}

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;
	if (ep != NULL)
		ep->stream = stream;
	stream->lpac = lpac;
	stream->rpac = rpac;
	stream->cc = util_iov_dup(data, 1);
	stream->client = client;
	stream->ops = bap_stream_new_ops(stream);
	stream->pending_states = queue_new();

	switch (bt_bap_pac_get_type(lpac)) {
	case BT_BAP_SINK:
	case BT_BAP_SOURCE:
		stream->qos.ucast.cig_id = BT_ISO_QOS_CIG_UNSET;
		stream->qos.ucast.cis_id = BT_ISO_QOS_CIS_UNSET;
		break;
	case BT_BAP_BCAST_SOURCE:
	case BT_BAP_BCAST_SINK:
		stream->qos.bcast.big = BT_ISO_QOS_BIG_UNSET;
		stream->qos.bcast.bis = BT_ISO_QOS_BIS_UNSET;
		break;
	}

	queue_push_tail(bap->streams, stream);

	return bt_bap_stream_ref(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);

	io_set_ignore_errqueue(io, true);

	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;
	struct bt_bap *bap;

	if (!bap_stream_valid(stream))
		return NULL;

	if (!stream->ops || !stream->ops->get_io)
		return NULL;

	if (!bt_bap_ref_safe(stream->bap))
		return NULL;

	bap = stream->bap;

	io = stream->ops->get_io(stream);

	bt_bap_unref(bap);

	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 void bap_stream_notify_connecting(struct bt_bap_stream *stream,
						bool connecting, int fd)
{
	const struct queue_entry *entry;

	for (entry = queue_get_entries(stream->bap->state_cbs); entry;) {
		struct bt_bap_state *state = entry->data;

		/* Prefetch next entry since the callback may remove the current
		 * entry.
		 */
		entry = entry->next;

		if (state->connecting)
			state->connecting(stream, connecting, fd, state->data);
	}
}

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;
	uint8_t state;

	if (fd >= 0)
		ret = bap_stream_io_attach(stream, fd, false);
	else
		ret = bap_stream_io_detach(stream);

	if (!ret)
		return;

	if (bt_bap_stream_get_type(stream) == BT_BAP_STREAM_TYPE_BCAST)
		state = stream->state;
	else
		state = stream->ep->state;

	switch (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_STREAMING:
		if (fd < 0)
			stream_set_state(stream, BT_BAP_STREAM_STATE_QOS);
		break;
	case BT_BAP_STREAM_STATE_DISABLING:
		if (fd < 0)
			bt_bap_stream_stop(stream, NULL, NULL);
		break;
	}

	/* Notify IO connected so transports can complete pending requests. */
	if (fd >= 0)
		bap_stream_notify_connecting(stream, false, fd);
}

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 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;
	struct bt_bap_codec codec;

	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 = util_iov_pull_mem(iov, req->cc_len);
	cc.iov_len = req->cc_len;

	if (!bt_bap_debug_config(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;
	}

	/* Convert to native endianness before comparing */
	memset(&codec, 0, sizeof(codec));
	codec.id = req->codec.id;
	codec.cid = le16_to_cpu(req->codec.cid);
	codec.vid = le16_to_cpu(req->codec.vid);

	for (; e; e = e->next) {
		struct bt_bap_pac *pac = e->data;

		if (!bap_codec_equal(&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 = util_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_local_endpoint_id(bap, 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 = util_iov_pull_mem(iov, sizeof(*req));

	memset(&qos, 0, sizeof(qos));

	qos.ucast.cig_id = req->cig;
	qos.ucast.cis_id = req->cis;
	qos.ucast.io_qos.interval = get_le24(req->interval);
	qos.ucast.framing = req->framing;
	qos.ucast.io_qos.phys = req->phys;
	qos.ucast.io_qos.sdu = le16_to_cpu(req->sdu);
	qos.ucast.io_qos.rtn = req->rtn;
	qos.ucast.io_qos.latency = le16_to_cpu(req->latency);
	qos.ucast.delay = get_le24(req->pd);

	DBG(bap, "CIG 0x%02x CIS 0x%02x interval %u framing 0x%02x "
			"phys 0x%02x SDU %u rtn %u latency %u pd %u",
			req->cig, req->cis, qos.ucast.io_qos.interval,
			qos.ucast.framing, qos.ucast.io_qos.phys,
			qos.ucast.io_qos.sdu, qos.ucast.io_qos.rtn,
			qos.ucast.io_qos.latency, qos.ucast.delay);

	ep = bap_get_local_endpoint_id(bap, 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_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);

	util_iov_free(stream->meta, 1);
	stream->meta = util_iov_dup(meta, 1);

	if (!stream_set_state(stream, BT_BAP_STREAM_STATE_ENABLING))
		return 1;

	/* 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 ascs_metadata_rsp(struct bt_bap_endpoint *ep, struct iovec *meta,
							struct iovec *rsp)
{
	struct bt_ltv *ltv;
	uint16_t supported_context = 0;
	uint16_t context;

	if (ep->stream && ep->stream->lpac)
		supported_context = ep->stream->lpac->qos.supported_context;

	ltv = meta->iov_base;
	if (meta->iov_len >= sizeof(*ltv) &&
			(ltv->type < BAP_METADATA_PREF_CONTEXT_LTV_TYPE ||
			ltv->type > BAP_METADATA_LANGUAGE_LTV_TYPE)) {
		ascs_ase_rsp_add(rsp, ep->id,
				BT_ASCS_RSP_METADATA_UNSUPPORTED, ltv->type);
		return true;
	}

	if (meta->iov_len >= sizeof(*ltv) + sizeof(context) &&
			ltv->type == BAP_METADATA_CONTEXT_LTV_TYPE &&
			ltv->len == sizeof(context) + 1) {
		context = get_le16(ltv->value);
		if (!context || (context & ~supported_context)) {
			ascs_ase_rsp_add(rsp, ep->id,
					BT_ASCS_RSP_METADATA_INVALID,
					ltv->type);
			return true;
		}
	}

	return false;
}

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 = util_iov_pull_mem(iov, req->meta.len);
	meta.iov_len = req->meta.len;

	if (!bt_bap_debug_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 (ascs_metadata_rsp(ep, &meta, rsp))
		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, &meta, 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 = util_iov_pull_mem(iov, sizeof(*req));

	ep = bap_get_local_endpoint_id(bap, 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 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 = util_iov_pull_mem(iov, sizeof(*req));

	ep = bap_get_local_endpoint_id(bap, 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 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 = util_iov_pull_mem(iov, sizeof(*req));

	ep = bap_get_local_endpoint_id(bap, 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 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 = util_iov_pull_mem(iov, sizeof(*req));

	ep = bap_get_local_endpoint_id(bap, 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 ep_metadata(struct bt_bap_endpoint *ep,
				struct bt_ascs_metadata *req,
				struct iovec *iov, struct iovec *rsp)
{
	struct bt_bap_stream *stream = ep->stream;
	struct iovec meta;

	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;
	}

	if (iov->iov_len < req->len)
		return BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;

	meta.iov_base = util_iov_pull_mem(iov, req->len);
	meta.iov_len = req->len;

	if (ascs_metadata_rsp(ep, &meta, rsp))
		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 = util_iov_pull_mem(iov, sizeof(*req));

	ep = bap_get_local_endpoint_id(bap, 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, req, iov, rsp);
}

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 = util_iov_pull_mem(iov, sizeof(*req));

	ep = bap_get_local_endpoint_id(bap, 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_rsp_add_truncated(struct iovec *rsp)
{
	ascs_ase_rsp_add_errno(rsp, 0x00, -ENOMSG);
}

static bool ascs_ase_cp_rsp_invalid_len(uint8_t op, size_t len, uint8_t num)
{
	switch (op) {
	case BT_ASCS_METADATA:
		return len == num;
	case BT_ASCS_RELEASE:
		return true;
	default:
		return false;
	}
}

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 = bt_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) {
		DBG(bap, "invalid len %u < %u sizeof(*hdr)", len,
							sizeof(*hdr));
		rsp = ascs_ase_cp_rsp_new(len > 0 ? value[0] : 0x00);
		ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
		goto respond;
	}

	if (len < sizeof(*hdr)) {
		DBG(bap, "invalid len %u < %u sizeof(*hdr)", len,
							sizeof(*hdr));

		rsp = ascs_ase_cp_rsp_new(value[0]);
		ascs_ase_cp_rsp_add_truncated(rsp);
		ret = 0;
		goto respond;
	}

	hdr = util_iov_pull_mem(&iov, sizeof(*hdr));
	rsp = ascs_ase_cp_rsp_new(hdr->op);

	if (!hdr->num) {
		DBG(bap, "invalid Number_of_ASEs 0");
		ascs_ase_cp_rsp_add_truncated(rsp);
		ret = 0;
		goto respond;
	}

	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);

			if (ascs_ase_cp_rsp_invalid_len(hdr->op, iov.iov_len,
								hdr->num)) {
				ascs_ase_cp_rsp_add_truncated(rsp);
				ret = 0;
			} else
				ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;

			goto respond;
		}

		break;
	}

	if (handler && handler->str) {
		int i;

		DBG(bap, "%s", handler->str);

		/* Set in_cp_write so ASE notification are not sent ahead of
		 * CP notification.
		 */
		bap->in_cp_write = true;

		for (i = 0; i < hdr->num; i++)
			ret = handler->func(ascs, bap, &iov, rsp);

		bap->in_cp_write = false;
	} 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);
		ret = 0;
	}

	gatt_db_attribute_notify(attrib, rsp->iov_base, rsp->iov_len, att);
	gatt_db_attribute_write_result(attrib, id, ret);

	util_iov_free(rsp, 1);
}

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_WRITE_WITHOUT_RESP |
					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->broadcast_sources = queue_new();
	bdb->broadcast_sinks = 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 bool match_codec(const void *data, const void *user_data)
{
	const struct bt_bap_pac *pac = data;
	const struct bt_bap_codec *codec = user_data;

	return bap_codec_equal(&pac->codec, codec);
}

static struct bt_bap_pac *bap_pac_find(struct bt_bap_db *bdb, uint8_t type,
					struct bt_bap_codec *codec)
{
	switch (type) {
	case BT_BAP_SOURCE:
		return queue_find(bdb->sources, match_codec, codec);
	case BT_BAP_SINK:
		return queue_find(bdb->sinks, match_codec, codec);
	case BT_BAP_BCAST_SOURCE:
		return queue_find(bdb->broadcast_sources, match_codec, codec);
	case BT_BAP_BCAST_SINK:
		return queue_find(bdb->broadcast_sinks, match_codec, codec);
	}

	return NULL;
}

static void *ltv_merge(struct iovec *data, struct iovec *cont)
{
	uint8_t delimiter = 0;

	if (!data)
		return NULL;

	util_iov_append(data, &delimiter, sizeof(delimiter));

	if (!cont || !cont->iov_len || !cont->iov_base)
		return data->iov_base;

	return util_iov_append(data, cont->iov_base, cont->iov_len);
}

static void bap_pac_merge(struct bt_bap_pac *pac, struct iovec *data,
					struct iovec *metadata)
{
	/* Merge data into existing record */
	if (pac->data)
		ltv_merge(pac->data, data);
	else
		pac->data = util_iov_dup(data, 1);

	/* Merge metadata into existing record */
	if (pac->metadata)
		ltv_merge(pac->metadata, metadata);
	else
		pac->metadata = util_iov_dup(metadata, 1);
}

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;

	if (codec)
		pac->codec = *codec;

	if (qos)
		pac->qos = *qos;

	bap_pac_merge(pac, data, metadata);

	return pac;
}

static void bap_pac_free(void *data)
{
	struct bt_bap_pac *pac = data;

	free(pac->name);
	util_iov_free(pac->metadata, 1);
	util_iov_free(pac->data, 1);
	queue_destroy(pac->channels, free);
	free(pac);
}

static void pacs_sink_location_changed(struct bt_pacs *pacs)
{
	uint32_t location = cpu_to_le32(pacs->sink_loc_value);

	gatt_db_attribute_notify(pacs->sink_loc, (void *)&location,
					sizeof(location), NULL);
}

static void pacs_add_sink_location(struct bt_pacs *pacs, uint32_t location)
{
	/* Check if location value needs updating */
	if (location == pacs->sink_loc_value)
		return;

	pacs->sink_loc_value |= location;

	pacs_sink_location_changed(pacs);
}

static void pacs_supported_context_changed(struct bt_pacs *pacs)
{
	struct bt_pacs_context ctx;

	memset(&ctx, 0, sizeof(ctx));

	ctx.snk = cpu_to_le16(pacs->supported_sink_context_value);
	ctx.src = cpu_to_le16(pacs->supported_source_context_value);

	gatt_db_attribute_notify(pacs->supported_context, (void *)&ctx,
					sizeof(ctx), NULL);
}

static void pacs_add_sink_supported_context(struct bt_pacs *pacs,
						uint16_t context)
{
	context |= pacs->supported_sink_context_value;

	/* Check if context value needs updating */
	if (context == pacs->supported_sink_context_value)
		return;

	pacs->supported_sink_context_value = context;

	pacs_supported_context_changed(pacs);
}

static void pacs_context_changed(struct bt_pacs *pacs)
{
	struct bt_pacs_context ctx;

	memset(&ctx, 0, sizeof(ctx));

	ctx.snk = cpu_to_le16(pacs->sink_context_value);
	ctx.src = cpu_to_le16(pacs->source_context_value);

	gatt_db_attribute_notify(pacs->context, (void *)&ctx, sizeof(ctx),
					NULL);
}

static void pacs_add_sink_context(struct bt_pacs *pacs, uint16_t context)
{
	context |= pacs->supported_sink_context_value;

	/* Check if context value needs updating */
	if (context == pacs->sink_context_value)
		return;

	pacs->sink_context_value = context;

	pacs_context_changed(pacs);
}

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);

	pacs_add_sink_location(pac->bdb->pacs, pac->qos.location);
	pacs_add_sink_supported_context(pac->bdb->pacs,
					pac->qos.supported_context);
	pacs_add_sink_context(pac->bdb->pacs, pac->qos.context);
	gatt_db_attribute_notify(pac->bdb->pacs->sink, iov.iov_base,
				iov.iov_len, NULL);
}

static void pacs_source_location_changed(struct bt_pacs *pacs)
{
	uint32_t location = cpu_to_le32(pacs->source_loc_value);

	gatt_db_attribute_notify(pacs->source_loc, (void *)&location,
					sizeof(location), NULL);
}

static void pacs_add_source_location(struct bt_pacs *pacs, uint32_t location)
{
	location |= pacs->source_loc_value;

	/* Check if location value needs updating */
	if (location == pacs->source_loc_value)
		return;

	pacs->source_loc_value = location;

	pacs_source_location_changed(pacs);
}

static void pacs_add_source_supported_context(struct bt_pacs *pacs,
						uint16_t context)
{
	context |= pacs->supported_source_context_value;

	/* Check if context value needs updating */
	if (context == pacs->supported_source_context_value)
		return;

	pacs->supported_source_context_value = context;

	pacs_supported_context_changed(pacs);
}

static void pacs_add_source_context(struct bt_pacs *pacs, uint16_t context)
{
	context |= pacs->source_context_value;

	/* Check if context value needs updating */
	if (context == pacs->source_context_value)
		return;

	pacs->source_context_value = context;

	pacs_context_changed(pacs);
}

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->sources, pac_foreach, &iov);

	pacs_add_source_location(pac->bdb->pacs, pac->qos.location);
	pacs_add_source_supported_context(pac->bdb->pacs,
					pac->qos.supported_context);
	pacs_add_source_context(pac->bdb->pacs, pac->qos.context);

	gatt_db_attribute_notify(pac->bdb->pacs->source, iov.iov_base,
				iov.iov_len, NULL);
}

static void bap_add_broadcast_source(struct bt_bap_pac *pac)
{
	queue_push_tail(pac->bdb->broadcast_sources, pac);
}

static void bap_add_broadcast_sink(struct bt_bap_pac *pac)
{
	queue_push_tail(pac->bdb->broadcast_sinks, pac);

	/* Update local PACS for broadcast sink also, when registering an
	 * endpoint
	 */
	pacs_add_sink_location(pac->bdb->pacs, pac->qos.location);
	pacs_add_sink_supported_context(pac->bdb->pacs,
			pac->qos.supported_context);
}

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);
}

static void notify_session_pac_added(void *data, void *user_data)
{
	struct bt_bap *bap = data;

	queue_foreach(bap->pac_cbs, notify_pac_added, user_data);
}

struct bt_bap_pac *bt_bap_add_vendor_pac_full(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_ops *ops,
					void *ops_user_data)
{
	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;

	if ((id != 0xff) && ((cid != 0U)  || (vid != 0U)))
		return NULL;

	codec.id = id;
	codec.cid = cid;
	codec.vid = vid;

	pac = bap_pac_new(bdb, name, type, &codec, qos, data, metadata);
	if (ops)
		bt_bap_pac_set_ops(pac, ops, ops_user_data);

	switch (type) {
	case BT_BAP_SINK:
		bap_add_sink(pac);
		break;
	case BT_BAP_SOURCE:
		bap_add_source(pac);
		break;
	case BT_BAP_BCAST_SOURCE:
		bap_add_broadcast_source(pac);
		break;
	case BT_BAP_BCAST_SINK:
		bap_add_broadcast_sink(pac);
		break;
	default:
		bap_pac_free(pac);
		return NULL;
	}

	queue_foreach(sessions, notify_session_pac_added, pac);

	return pac;
}

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)
{
	return bt_bap_add_vendor_pac_full(db, name, type, id, cid, vid, qos,
							data, metadata,
							NULL, NULL);
}

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;
}

uint32_t bt_bap_pac_get_locations(struct bt_bap_pac *pac)
{
	struct bt_pacs *pacs;

	if (!pac)
		return 0;

	if (pac->qos.location)
		return pac->qos.location;

	pacs = pac->bdb->pacs;

	switch (pac->type) {
	case BT_BAP_SOURCE:
		return pacs->source_loc_value;
	case BT_BAP_SINK:
		return pacs->sink_loc_value;
	default:
		return 0;
	}
}

uint16_t bt_bap_pac_get_supported_context(struct bt_bap_pac *pac)
{
	struct bt_pacs *pacs;

	if (!pac)
		return 0;

	pacs = pac->bdb->pacs;

	switch (pac->type) {
	case BT_BAP_SOURCE:
		return pacs->supported_source_context_value;
	case BT_BAP_SINK:
		return pacs->supported_sink_context_value;
	default:
		return 0;
	}
}

uint16_t bt_bap_pac_get_context(struct bt_bap_pac *pac)
{
	struct bt_pacs *pacs;

	if (!pac)
		return 0;

	pacs = pac->bdb->pacs;

	switch (pac->type) {
	case BT_BAP_SOURCE:
		return pacs->source_context_value;
	case BT_BAP_SINK:
		return pacs->sink_context_value;
	default:
		return 0;
	}
}

struct bt_bap_pac_qos *bt_bap_pac_get_qos(struct bt_bap_pac *pac)
{
	if (!pac || !pac->qos.phys)
		return NULL;

	return &pac->qos;
}

struct iovec *bt_bap_pac_get_data(struct bt_bap_pac *pac)
{
	return pac->data;
}

struct iovec *bt_bap_pac_get_metadata(struct bt_bap_pac *pac)
{
	return pac->metadata;
}

uint8_t bt_bap_stream_get_type(struct bt_bap_stream *stream)
{
	if (!stream)
		return 0x00;

	switch (bt_bap_pac_get_type(stream->lpac)) {
	case BT_BAP_SINK:
	case BT_BAP_SOURCE:
		return BT_BAP_STREAM_TYPE_UCAST;
	case BT_BAP_BCAST_SOURCE:
	case BT_BAP_BCAST_SINK:
		return BT_BAP_STREAM_TYPE_BCAST;
	}

	return 0x00;
}

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);
}

static void notify_session_pac_removed(void *data, void *user_data)
{
	struct bt_bap *bap = data;

	queue_foreach(bap->pac_cbs, notify_pac_removed, user_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_lpac_streams(void *data, void *user_data)
{
	struct bt_bap *bap = data;
	struct bt_bap_pac *pac = user_data;
	struct bt_bap_stream *stream;

	while (1) {
		stream = queue_remove_if(bap->streams, match_stream_lpac, pac);
		if (!stream)
			break;

		bt_bap_stream_ref(stream);
		stream->no_cache_config = true;
		bt_bap_stream_release(stream, NULL, NULL);
		stream->lpac = NULL;
		bt_bap_stream_unref(stream);
	}
}

static void bap_pac_sink_removed(void *data, void *user_data)
{
	struct bt_bap_pac *pac = data;
	struct bt_bap_pac_qos *qos = user_data;

	qos->location |= pac->qos.location;
	qos->supported_context |= pac->qos.supported_context;
	qos->context |= pac->qos.context;
}

bool bt_bap_remove_pac(struct bt_bap_pac *pac)
{
	if (!pac)
		return false;

	if (queue_remove_if(pac->bdb->sinks, NULL, pac)) {
		struct bt_pacs *pacs = pac->bdb->pacs;
		struct bt_bap_pac_qos qos;

		memset(&qos, 0, sizeof(qos));
		queue_foreach(pac->bdb->sinks, bap_pac_sink_removed, &qos);

		if (pacs->sink_loc_value != qos.location) {
			pacs->sink_loc_value = qos.location;
			pacs_sink_location_changed(pacs);
		}

		if (pacs->supported_sink_context_value !=
				qos.supported_context) {
			pacs->supported_sink_context_value =
							qos.supported_context;
			pacs_supported_context_changed(pacs);
		}

		if (pacs->sink_context_value != qos.context) {
			pacs->sink_context_value = qos.context;
			pacs_context_changed(pacs);
		}


		goto found;
	}

	if (queue_remove_if(pac->bdb->sources, NULL, pac))
		goto found;

	if (queue_remove_if(pac->bdb->broadcast_sources, NULL, pac))
		goto found;

	return false;

found:
	queue_foreach(sessions, remove_lpac_streams, pac);
	queue_foreach(sessions, notify_session_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);
	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_bis_cb_free(void *data)
{
	struct bt_bap_bis_cb *bis_cb = data;

	if (bis_cb->destroy)
		bis_cb->destroy(bis_cb->data);

	free(bis_cb);
}

static void bap_bcode_cb_free(void *data)
{
	struct bt_bap_bcode_cb *cb = data;

	if (cb->destroy)
		cb->destroy(cb->data);

	free(cb);
}

static void bap_ep_free(void *data)
{
	struct bt_bap_endpoint *ep = data;

	if (ep && ep->stream)
		ep->stream->ep = NULL;

	free(ep);
}

static void bap_detached(void *data, void *user_data)
{
	struct bt_bap_cb *cb = data;
	struct bt_bap *bap = user_data;

	if (!cb->detached)
		return;

	cb->detached(bap, cb->user_data);
}

static void bap_stream_unref(void *data)
{
	struct bt_bap_stream *stream = data;

	bt_bap_stream_unref(stream);
}

static void bap_free(void *data)
{
	struct bt_bap *bap = data;

	timeout_remove(bap->process_id);

	bt_bap_detach(bap);

	bap_db_free(bap->rdb);

	queue_destroy(bap->pac_cbs, pac_changed_free);
	queue_destroy(bap->ready_cbs, bap_ready_free);
	queue_destroy(bap->state_cbs, bap_state_free);
	queue_destroy(bap->bis_cbs, bap_bis_cb_free);
	queue_destroy(bap->bcode_cbs, bap_bcode_cb_free);
	queue_destroy(bap->local_eps, free);
	queue_destroy(bap->remote_eps, bap_ep_free);

	queue_destroy(bap->reqs, bap_req_free);
	queue_destroy(bap->notify, NULL);
	queue_destroy(bap->streams, bap_stream_unref);

	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;

	if (!cb->attached)
		return;

	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->notify = queue_new();
	bap->pac_cbs = queue_new();
	bap->ready_cbs = queue_new();
	bap->streams = queue_new();
	bap->state_cbs = queue_new();
	bap->bis_cbs = queue_new();
	bap->bcode_cbs = queue_new();
	bap->local_eps = 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();

	bap->rdb = bdb;
	bap->remote_eps = queue_new();

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 gatt_db *bt_bap_get_db(struct bt_bap *bap, bool remote)
{
	if (!bap)
		return NULL;

	if (remote)
		return bap->rdb ? bap->rdb->db : NULL;

	return bap->ldb ? bap->ldb->db : NULL;
}

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 (!bt_bap_ref_safe(bap))
		return;

	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);
}

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 = util_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, *m;
		struct bt_pac_metadata *meta;
		struct iovec data, metadata;

		p = util_iov_pull_mem(&iov, sizeof(*p));
		if (!p) {
			DBG(bap, "Unable to parse PAC");
			return;
		}

		if (p->codec.id == 0xff) {
			p->codec.cid = le16_to_cpu(p->codec.cid);
			p->codec.vid = le16_to_cpu(p->codec.vid);
		}

		pac = NULL;

		cc = util_iov_pull_mem(&iov, p->cc_len);
		if (!cc) {
			DBG(bap, "Unable to parse PAC codec capabilities");
			return;
		}

		if (!bt_bap_debug_caps(cc, p->cc_len, bap->debug_func,
					bap->debug_data)) {
			DBG(bap, "Invalid PAC codec capabilities LTV");
			return;
		}

		meta = util_iov_pull_mem(&iov, sizeof(*meta));
		if (!meta) {
			DBG(bap, "Unable to parse PAC metadata");
			return;
		}

		m = util_iov_pull_mem(&iov, meta->len);
		if (!m) {
			DBG(bap, "Unable to parse PAC metadata");
			return;
		}

		if (!bt_bap_debug_metadata(meta->data, meta->len,
					bap->debug_func, bap->debug_data)) {
			DBG(bap, "Invalid PAC metadata LTV");
			return;
		}

		data.iov_len = p->cc_len;
		data.iov_base = cc;

		metadata.iov_len = meta->len;
		metadata.iov_base = m;

		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);

		/* Check if there is already a PAC record for the codec */
		pac = bap_pac_find(bap->rdb, type, &p->codec);
		if (pac) {
			bap_pac_merge(pac, &data, &metadata);
			continue;
		}

		pac = bap_pac_new(bap->rdb, NULL, type, &p->codec, NULL, &data,
								&metadata);
		if (!pac)
			continue;

		queue_push_tail(queue, pac);
	}
}

static void read_source_pac(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_bap *bap = 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(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_bap *bap = 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(bool success, uint8_t att_ecode,
					const uint8_t *value, uint16_t length,
					void *user_data)
{
	struct bt_bap *bap = 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;
	}

	if (length != sizeof(uint32_t)) {
		DBG(bap, "Invalid Source PAC Location size: %d", length);
		return;
	}

	pacs->source_loc_value = get_le32(value);

	DBG(bap, "PACS Source Locations: 0x%08x", pacs->source_loc_value);

	/* Resume reading sinks if supported but for some reason is empty */
	if (pacs->source && queue_isempty(bap->rdb->sources)) {
		uint16_t value_handle;

		if (gatt_db_attribute_get_char_data(pacs->source,
						NULL, &value_handle,
						NULL, NULL, NULL))
			bt_gatt_client_read_value(bap->client, value_handle,
							read_source_pac, bap,
							NULL);
	}
}

static void read_sink_pac_loc(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_bap *bap = 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;
	}

	if (length != sizeof(uint32_t)) {
		DBG(bap, "Invalid Sink PAC Location size: %d", length);
		return;
	}

	pacs->sink_loc_value = get_le32(value);

	DBG(bap, "PACS Sink Locations: 0x%08x", pacs->sink_loc_value);

	/* Resume reading sinks if supported but for some reason is empty */
	if (pacs->sink && queue_isempty(bap->rdb->sinks)) {
		uint16_t value_handle;

		if (gatt_db_attribute_get_char_data(pacs->sink,
						NULL, &value_handle,
						NULL, NULL, NULL))
			bt_gatt_client_read_value(bap->client, value_handle,
							read_sink_pac, bap,
							NULL);
	}
}

static void read_pac_context(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_bap *bap = user_data;
	struct bt_pacs *pacs = bap_get_pacs(bap);
	const struct bt_pacs_context *ctx = (void *)value;

	if (!success) {
		DBG(bap, "Unable to read PAC Context: error 0x%02x", att_ecode);
		return;
	}

	if (length != sizeof(*ctx)) {
		DBG(bap, "Invalid PAC Context size: %d", length);
		return;
	}

	pacs->sink_context_value = le16_to_cpu(ctx->snk);
	pacs->source_context_value = le16_to_cpu(ctx->src);

	DBG(bap, "PACS Sink Context: 0x%04x", pacs->sink_context_value);
	DBG(bap, "PACS Source Context: 0x%04x", pacs->source_context_value);
}

static void read_pac_supported_context(bool success, uint8_t att_ecode,
					const uint8_t *value, uint16_t length,
					void *user_data)
{
	struct bt_bap *bap = user_data;
	struct bt_pacs *pacs = bap_get_pacs(bap);
	const struct bt_pacs_context *ctx = (void *)value;

	if (!success) {
		DBG(bap, "Unable to read PAC Supported Context: error 0x%02x",
								att_ecode);
		return;
	}

	if (length != sizeof(*ctx)) {
		DBG(bap, "Invalid PAC Supported Context size: %d", length);
		return;
	}

	pacs->supported_sink_context_value = le16_to_cpu(ctx->snk);
	pacs->supported_source_context_value = le16_to_cpu(ctx->src);

	DBG(bap, "PACS Supported Sink Context: 0x%04x",
					pacs->supported_sink_context_value);
	DBG(bap, "PACS Supported Source Context: 0x%04x",
					pacs->supported_source_context_value);
}

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)
			return;

		if (!pacs->sink)
			pacs->sink = attr;

		bt_gatt_client_read_value(bap->client, value_handle,
						read_sink_pac, bap, NULL);
	}

	if (!bt_uuid_cmp(&uuid, &uuid_source)) {
		DBG(bap, "Source PAC found: handle 0x%04x", value_handle);

		pacs = bap_get_pacs(bap);
		if (!pacs)
			return;

		if (!pacs->source)
			pacs->source = attr;

		bt_gatt_client_read_value(bap->client, value_handle,
						read_source_pac, bap, 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;
		bt_gatt_client_read_value(bap->client, value_handle,
						read_sink_pac_loc, bap, 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;
		bt_gatt_client_read_value(bap->client, value_handle,
						read_source_pac_loc, bap, 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;
		bt_gatt_client_read_value(bap->client, value_handle,
						read_pac_context, bap, 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;
		bt_gatt_client_read_value(bap->client, value_handle,
						read_pac_supported_context,
						bap, 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 = util_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.phys 0x%02x rtn %u "
			"latency %u pd %u - %u ppd %u - %u", cfg->codec.id,
			cfg->framing, cfg->phys, 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 = util_iov_pull_mem(iov, sizeof(*cc));
		if (!cc)
			break;

		DBG(bap, "Codec Config #%u: type 0x%02x len %u", i,
						cc->type, cc->len);

		util_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.phys = cfg->phys;
		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);

	util_iov_memcpy(ep->stream->cc, cfg->cc, cfg->cc_len);

	ep->stream->need_reconfig = false;
}

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 || !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 = util_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 "
			"phys 0x%02x rtn %u latency %u pd %u", qos->cig_id,
			qos->cis_id, interval, qos->framing, qos->phys,
			qos->rtn, latency, pd);

	if (!ep->stream)
		return;

	ep->stream->qos.ucast.io_qos.interval = interval;
	ep->stream->qos.ucast.framing = qos->framing;
	ep->stream->qos.ucast.io_qos.phys = qos->phys;
	ep->stream->qos.ucast.io_qos.sdu = sdu;
	ep->stream->qos.ucast.io_qos.rtn = qos->rtn;
	ep->stream->qos.ucast.io_qos.latency = latency;
	ep->stream->qos.ucast.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 = util_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 = util_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(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_bap_endpoint *ep = user_data;
	struct bt_bap *bap = ep->bap;

	if (!success) {
		DBG(bap, "ASE read status failed: 0x%04x", att_ecode);
		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);

	ep->bap = bap;

	bt_gatt_client_read_value(bap->client, value_handle, read_ase_status,
					ep, NULL);

	ep->state_id = bap_register_notify(bap, value_handle,
						bap_endpoint_notify, ep);
}

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->remote_eps, 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);
}

static void bap_attach_att(struct bt_bap *bap, struct bt_att *att)
{
	if (bap->disconn_id) {
		if (att == bt_bap_get_att(bap))
			return;
		bt_att_unregister_disconnect(bap->att, bap->disconn_id);
	}

	bap->disconn_id = bt_att_register_disconnect(bap->att,
							bap_disconnected,
							bap, NULL);
}

static void bap_idle(void *data)
{
	struct bt_bap *bap = data;

	bap->idle_id = 0;

	bap_notify_ready(bap);
}

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) {
		if (bap->att)
			bap_attach_att(bap, bap->att);
		return true;
	}

	if (bap->client)
		return false;

clone:
	bap->client = bt_gatt_client_clone(client);
	if (!bap->client)
		return false;

	bap_attach_att(bap, bt_gatt_client_get_att(client));

	bap->idle_id = bt_gatt_client_idle_register(bap->client, bap_idle,
								bap, NULL);

	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)) {
				bt_gatt_client_read_value(bap->client,
							value_handle,
							read_sink_pac,
							bap, NULL);
			}
		}

		/* Resume reading sink locations if supported */
		if (pacs->sink && pacs->sink_loc && !pacs->sink_loc_value) {
			if (gatt_db_attribute_get_char_data(pacs->sink_loc,
							NULL, &value_handle,
							NULL, NULL, NULL)) {
				bt_gatt_client_read_value(bap->client,
							value_handle,
							read_sink_pac_loc,
							bap, NULL);
			}
		}

		/* 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)) {
				bt_gatt_client_read_value(bap->client,
							value_handle,
							read_source_pac,
							bap, NULL);
			}
		}

		/* Resume reading source locations if supported */
		if (pacs->source && pacs->source_loc &&
				!pacs->source_loc_value) {
			if (gatt_db_attribute_get_char_data(pacs->source_loc,
							NULL, &value_handle,
							NULL, NULL, NULL)) {
				bt_gatt_client_read_value(bap->client,
							value_handle,
							read_source_pac_loc,
							bap, NULL);
			}
		}

		/* Resume reading supported contexts if supported */
		if (pacs->sink && pacs->supported_context &&
				!pacs->supported_sink_context_value &&
				!pacs->supported_source_context_value) {
			if (gatt_db_attribute_get_char_data(
							pacs->supported_context,
							NULL, &value_handle,
							NULL, NULL, NULL)) {
				bt_gatt_client_read_value(bap->client,
						value_handle,
						read_pac_supported_context,
						bap, NULL);
			}
		}

		/* Resume reading contexts if supported */
		if (pacs->sink && pacs->context &&
				!pacs->sink_context_value &&
				!pacs->source_context_value) {
			if (gatt_db_attribute_get_char_data(pacs->context,
							NULL, &value_handle,
							NULL, NULL, NULL)) {
				bt_gatt_client_read_value(bap->client,
							value_handle,
							read_pac_context,
							bap, NULL);
			}
		}

		queue_foreach(bap->remote_eps, bap_endpoint_foreach, bap);

		bap_cp_attach(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;
}

bool bt_bap_attach_broadcast(struct bt_bap *bap)
{
	struct bt_bap_endpoint *ep;

	if (queue_find(sessions, NULL, bap))
		return true;

	if (!sessions)
		sessions = queue_new();

	queue_push_tail(sessions, bap);

	queue_foreach(bap_cbs, bap_attached, bap);

	ep = bap_get_endpoint_bcast(bap->remote_eps, bap->ldb,
				BT_BAP_BCAST_SOURCE);
	if (ep)
		ep->bap = 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);
}

static void bap_req_detach(void *data)
{
	struct bt_bap_req *req = data;

	bap_req_complete(req, NULL);
}

void bt_bap_detach(struct bt_bap *bap)
{
	DBG(bap, "%p", bap);

	if (!queue_remove(sessions, bap))
		return;

	/* Cancel ongoing request */
	if (bap->req) {
		bap_req_detach(bap->req);
		bap->req = NULL;
	}

	bt_gatt_client_idle_unregister(bap->client, bap->idle_id);

	/* Cancel queued requests */
	queue_remove_all(bap->reqs, NULL, NULL, bap_req_detach);

	bt_gatt_client_unref(bap->client);
	bap->client = NULL;

	bt_att_unregister_disconnect(bap->att, bap->disconn_id);
	bap->att = 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;
}

unsigned int bt_bap_bis_cb_register(struct bt_bap *bap,
				bt_bap_bis_func_t probe,
				bt_bap_func_t remove,
				void *user_data,
				bt_bap_destroy_func_t destroy)
{
	struct bt_bap_bis_cb *bis_cb;
	static unsigned int id;

	if (!bap)
		return 0;

	bis_cb = new0(struct bt_bap_bis_cb, 1);
	bis_cb->id = ++id ? id : ++id;
	bis_cb->probe = probe;
	bis_cb->remove = remove;
	bis_cb->destroy = destroy;
	bis_cb->data = user_data;

	queue_push_tail(bap->bis_cbs, bis_cb);

	return bis_cb->id;
}

static bool match_bis_cb_id(const void *data, const void *match_data)
{
	const struct bt_bap_bis_cb *bis_cb = data;
	unsigned int id = PTR_TO_UINT(match_data);

	return (bis_cb->id == id);
}

bool bt_bap_bis_cb_unregister(struct bt_bap *bap, unsigned int id)
{
	struct bt_bap_bis_cb *bis_cb;

	if (!bap)
		return false;

	bis_cb = queue_remove_if(bap->bis_cbs, match_bis_cb_id,
						UINT_TO_PTR(id));
	if (!bis_cb)
		return false;

	bap_bis_cb_free(bis_cb);

	return false;
}

void bt_bap_bis_probe(struct bt_bap *bap, uint8_t sid, uint8_t bis,
		      uint8_t sgrp, struct iovec *caps, struct iovec *meta,
		      struct bt_bap_qos *qos)
{
	const struct queue_entry *entry;

	if (!bt_bap_ref_safe(bap))
		return;

	entry = queue_get_entries(bap->bis_cbs);

	while (entry) {
		struct bt_bap_bis_cb *cb = entry->data;

		entry = entry->next;

		if (cb->probe)
			cb->probe(sid, bis, sgrp, caps, meta, qos, cb->data);
	}

	bt_bap_unref(bap);
}

void bt_bap_bis_remove(struct bt_bap *bap)
{
	const struct queue_entry *entry;

	if (!bt_bap_ref_safe(bap))
		return;

	entry = queue_get_entries(bap->bis_cbs);

	while (entry) {
		struct bt_bap_bis_cb *cb = entry->data;

		entry = entry->next;

		if (cb->remove)
			cb->remove(bap, cb->data);
	}

	bt_bap_unref(bap);
}

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;

			/* Skip checking codec for bcast source,
			 * it will be checked when BASE info are received
			 */
			if ((rpac->type != BT_BAP_BCAST_SOURCE) &&
				(!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);
	case BT_BAP_BCAST_SOURCE:
	case BT_BAP_BCAST_SINK:
		return bap_foreach_pac(bap->ldb->broadcast_sinks,
						bap->rdb->broadcast_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 && pac->data)
		*data = pac->data;

	if (metadata && pac->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);
}

void bt_bap_pac_set_user_data(struct bt_bap_pac *pac, void *user_data)
{
	pac->user_data = user_data;
}

void *bt_bap_pac_get_user_data(struct bt_bap_pac *pac)
{
	return pac->user_data;
}

bool bt_bap_pac_bcast_is_local(struct bt_bap *bap, struct bt_bap_pac *pac)
{
	if (!bap->ldb)
		return false;

	if (queue_find(bap->ldb->broadcast_sinks, NULL, pac))
		return true;

	if (queue_find(bap->ldb->broadcast_sources, NULL, pac))
		return true;

	return false;
}

static bool find_ep_source(const void *data, const void *user_data)
{
	const struct bt_bap_endpoint *ep = data;

	if (ep->dir == BT_BAP_BCAST_SINK)
		return true;
	else
		return false;
}

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)
{
	unsigned int id;
	struct bt_bap *bap;

	if (!bap_stream_valid(stream))
		return 0;

	if (!stream->ops || !stream->ops->config)
		return 0;

	if (!bt_bap_ref_safe(stream->bap))
		return 0;

	bap = stream->bap;

	id = stream->ops->config(stream, qos, data, func, user_data);

	bt_bap_unref(bap);

	return 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;
}

static void bap_pac_ltv_ch_counts(size_t i, uint8_t l, uint8_t t, uint8_t *v,
								void *user_data)
{
	uint8_t *mask = user_data;

	if (v)
		*mask |= *v;
}

static uint8_t bap_pac_ch_counts(struct bt_bap_pac *pac)
{
	uint8_t type = 0x03;
	uint8_t mask = 0;

	if (!pac->data)
		return 0;

	util_ltv_foreach(pac->data->iov_base, pac->data->iov_len, &type,
						bap_pac_ltv_ch_counts, &mask);

	if (!mask)
		mask = 0x01;  /* default (BAP v1.0.1 Sec 4.3.1) */

	return mask;
}

static unsigned int bap_count_eps(struct queue *eps, uint8_t dir)
{
	const struct queue_entry *entry;
	unsigned int count = 0;

	for (entry = queue_get_entries(eps); entry; entry = entry->next) {
		struct bt_bap_endpoint *ep = entry->data;

		if (ep->dir == dir)
			count++;
	}

	return count;
}

int bt_bap_select(struct bt_bap *bap,
			struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
			unsigned int max_channels, int *count,
			bt_bap_pac_select_t func, void *user_data)
{
	uint32_t locations;
	uint8_t ch_counts;
	unsigned int num_ase;

	if (!lpac || !rpac || !func)
		return -EINVAL;

	if (!lpac->ops || !lpac->ops->select)
		return -EOPNOTSUPP;

	if (!max_channels)
		max_channels = 2;  /* By default: don't go beyond BAP AC */

	ch_counts = bap_pac_ch_counts(lpac) & bap_pac_ch_counts(rpac);
	locations = bt_bap_pac_get_locations(rpac);
	num_ase = bap_count_eps(bap->remote_eps, rpac->type);

	/* Fallback to unspecified/mono allocation if nothing is matching */
	if (!locations || !ch_counts) {
		lpac->ops->select(lpac, rpac, 0, &rpac->qos, func, user_data,
							lpac->user_data);
		if (count)
			(*count)++;
		return 0;
	}

	/* Allocate all locations to streams */
	while (num_ase) {
		uint32_t allocation = 0, alloc = 0;
		unsigned int i, n;

		/* Put max number of channels per stream */
		for (i = 0, n = 0; i < 32 && n < 8; ++i) {
			uint32_t loc = (1LL << i);

			if (!(locations & loc))
				continue;

			alloc |= loc;
			if ((BIT(n) & ch_counts) && n < max_channels)
				allocation = alloc;

			++n;
		}

		if (!allocation)
			break;

		/* Configure stream */
		lpac->ops->select(lpac, rpac, allocation, &rpac->qos,
					func, user_data, lpac->user_data);
		if (count)
			(*count)++;

		locations &= ~allocation;
		max_channels -= __builtin_popcount(allocation);
		num_ase--;
	}

	return 0;
}

void bt_bap_cancel_select(struct bt_bap_pac *lpac, bt_bap_pac_select_t func,
								void *user_data)
{
	if (!lpac || !func)
		return;

	if (!lpac->ops || !lpac->ops->cancel_select)
		return;

	lpac->ops->cancel_select(lpac, func, user_data, lpac->user_data);
}

static struct bt_bap_stream *bap_bcast_stream_new(struct bt_bap *bap,
					struct bt_bap_pac *lpac,
					struct bt_bap_qos *pqos,
					struct iovec *data)
{
	struct bt_bap_stream *stream = NULL;
	struct bt_bap_endpoint *ep = NULL;
	struct match_pac match;

	if (!bap || !lpac)
		return NULL;

	if (lpac->type == BT_BAP_BCAST_SOURCE) {
		match.lpac = lpac;
		match.rpac = NULL;
		memset(&match.codec, 0, sizeof(match.codec));

		bt_bap_foreach_pac(bap, BT_BAP_BCAST_SINK, match_pac, &match);
		if (!match.lpac)
			return NULL;

		lpac = match.lpac;

		ep = queue_find(bap->remote_eps, find_ep_source, NULL);
		if (!ep)
			return NULL;
	} else if (lpac->type != BT_BAP_BCAST_SINK) {
		return NULL;
	}

	if (!stream)
		stream = bap_stream_new(bap, ep, lpac, NULL, data, true);

	return stream;
}

static bool find_ep_ucast(const void *data, const void *user_data)
{
	const struct bt_bap_endpoint *ep = data;
	const struct match_pac *match = user_data;

	if (ep->stream) {
		if (!ep->stream->client)
			return false;
		if (ep->stream->locked)
			return false;
		if (!queue_isempty(ep->stream->pending_states))
			return false;

		switch (ep->stream->state) {
		case BT_BAP_STREAM_STATE_IDLE:
		case BT_BAP_STREAM_STATE_CONFIG:
		case BT_BAP_STREAM_STATE_QOS:
			break;
		default:
			return false;
		}
	}

	switch (ep->state) {
	case BT_ASCS_ASE_STATE_IDLE:
	case BT_ASCS_ASE_STATE_CONFIG:
	case BT_ASCS_ASE_STATE_QOS:
		break;
	default:
		return false;
	}

	if (ep->dir != match->rpac->type)
		return false;

	switch (match->lpac->type) {
	case BT_BAP_SOURCE:
		if (ep->dir != BT_BAP_SINK)
			return false;
		break;
	case BT_BAP_SINK:
		if (ep->dir != BT_BAP_SOURCE)
			return false;
		break;
	default:
		return false;
	}

	return true;
}

static struct bt_bap_stream *bap_ucast_stream_new(struct bt_bap *bap,
					struct bt_bap_pac *lpac,
					struct bt_bap_pac *rpac,
					struct bt_bap_qos *pqos,
					struct iovec *data)
{
	struct bt_bap_stream *stream = NULL;
	struct bt_bap_endpoint *ep = NULL;
	struct match_pac match;

	if (!lpac || !rpac || !bap_codec_equal(&lpac->codec, &rpac->codec))
		return NULL;

	memset(&match, 0, sizeof(match));
	match.lpac = lpac;
	match.rpac = rpac;

	/* Get free ASE */
	ep = queue_find(bap->remote_eps, find_ep_ucast, &match);
	if (!ep) {
		DBG(bap, "Unable to find usable ASE");
		return NULL;
	}

	stream = ep->stream;
	if (stream) {
		/* Replace lpac: the stream generally needs to be reconfigured
		 * after this, otherwise things like codec config not match.
		 */
		bap_stream_clear_cfm(stream);
		stream->lpac = lpac;
		util_iov_free(stream->cc, 1);
		stream->cc = util_iov_dup(data, 1);
		stream->need_reconfig = true;
	} else {
		stream = bap_stream_new(bap, ep, lpac, rpac, data, true);
	}

	return stream;
}

struct bt_bap_stream *bt_bap_stream_new(struct bt_bap *bap,
					struct bt_bap_pac *lpac,
					struct bt_bap_pac *rpac,
					struct bt_bap_qos *pqos,
					struct iovec *data)
{
	if (!bap)
		return NULL;

	/* Check if ATT is attached then it must be a unicast stream */
	if (bt_bap_get_att(bap))
		return bap_ucast_stream_new(bap, lpac, rpac, pqos, data);

	return bap_bcast_stream_new(bap, lpac, pqos, data);
}

void bt_bap_stream_lock(struct bt_bap_stream *stream)
{
	if (!stream || !stream->client)
		return;

	/* Reserve stream ASE for use by upper level, so it won't get
	 * reallocated
	 */
	stream->locked = true;
}

void bt_bap_stream_unlock(struct bt_bap_stream *stream)
{
	if (!stream || !stream->client)
		return;

	stream->locked = false;
}

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->ops->get_state(stream);
}

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)
{
	unsigned int id;

	if (!bap_stream_valid(stream))
		return 0;

	if (!stream->ops || !stream->ops->qos)
		return 0;

	if (!bt_bap_ref_safe(stream->bap))
		return 0;

	id = stream->ops->qos(stream, data, func, user_data);

	bt_bap_unref(stream->bap);

	return id;
}

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 id;
	struct bt_bap *bap;

	if (!bap_stream_valid(stream))
		return 0;

	if (!stream->ops || !stream->ops->enable)
		return 0;

	if (!bt_bap_ref_safe(stream->bap))
		return 0;

	bap = stream->bap;

	id = stream->ops->enable(stream, enable_links, metadata, func,
					user_data);

	bt_bap_unref(bap);

	return id;
}

unsigned int bt_bap_stream_start(struct bt_bap_stream *stream,
					bt_bap_stream_func_t func,
					void *user_data)
{
	unsigned int id;
	struct bt_bap *bap;

	if (!bap_stream_valid(stream))
		return 0;

	if (!stream->ops || !stream->ops->start)
		return 0;

	if (!bt_bap_ref_safe(stream->bap))
		return 0;

	bap = stream->bap;

	id = stream->ops->start(stream, func, user_data);

	bt_bap_unref(bap);

	return id;
}

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 id;
	struct bt_bap *bap;

	if (!bap_stream_valid(stream))
		return 0;

	if (!stream->ops || !stream->ops->disable)
		return 0;

	if (!bt_bap_ref_safe(stream->bap))
		return 0;

	bap = stream->bap;

	id = stream->ops->disable(stream, disable_links, func, user_data);

	bt_bap_unref(bap);

	return id;
}

unsigned int bt_bap_stream_stop(struct bt_bap_stream *stream,
					bt_bap_stream_func_t func,
					void *user_data)
{
	unsigned int id;

	if (!bap_stream_valid(stream))
		return 0;

	if (!stream->ops || !stream->ops->stop)
		return 0;

	if (!bt_bap_ref_safe(stream->bap))
		return 0;

	id = stream->ops->stop(stream, func, user_data);

	bt_bap_unref(stream->bap);

	return id;
}

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 id;

	if (!bap_stream_valid(stream))
		return 0;

	if (!stream->ops || !stream->ops->metadata)
		return 0;

	if (!bt_bap_ref_safe(stream->bap))
		return 0;

	id = stream->ops->metadata(stream, metadata, func, user_data);

	bt_bap_unref(stream->bap);

	return id;
}

unsigned int bt_bap_stream_release(struct bt_bap_stream *stream,
					bt_bap_stream_func_t func,
					void *user_data)
{
	unsigned int id;
	struct bt_bap *bap;

	if (!bap_stream_valid(stream))
		return 0;

	if (!stream || !stream->ops || !stream->ops->release)
		return 0;

	bap = stream->bap;

	if (!bt_bap_ref_safe(bap))
		return 0;

	id = stream->ops->release(stream, func, user_data);

	bt_bap_unref(bap);

	return id;
}

uint8_t bt_bap_stream_get_dir(struct bt_bap_stream *stream)
{
	if (!stream)
		return 0x00;

	return stream->ops->get_dir(stream);
}

uint32_t bt_bap_stream_get_location(struct bt_bap_stream *stream)
{
	if (!stream)
		return 0x00000000;

	return stream->ops->get_loc(stream);
}

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;
}

bool bt_bap_match_bcast_sink_stream(const void *data, const void *user_data)
{
	const struct bt_bap_stream *stream = data;

	if (!stream->lpac)
		return false;

	return stream->lpac->type == BT_BAP_BCAST_SINK;
}

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);

	/* If the IO is for a broadcast sink has been disconnected both BIG Sync
	 * and PA Sync have been lost so switch to idle state to cleanup the
	 * stream.
	 */
	if (stream->lpac->type == BT_BAP_BCAST_SINK) {
		stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE);
		return false;
	}

	switch (stream->ep->state) {
	case BT_ASCS_ASE_STATE_RELEASING:
		stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG);
		break;
	case BT_ASCS_ASE_STATE_ENABLING:
	case BT_ASCS_ASE_STATE_STREAMING:
	case BT_ASCS_ASE_STATE_DISABLING:
		stream_set_state(stream, BT_BAP_STREAM_STATE_QOS);
		break;
	}

	bt_bap_stream_set_io(stream, -1);
	return false;
}

bool bt_bap_stream_set_io(struct bt_bap_stream *stream, int fd)
{
	bool ret;
	struct bt_bap *bap;

	if (!bap_stream_valid(stream))
		return false;

	if (!stream->ops || !stream->ops->set_io)
		return false;

	if (!bt_bap_ref_safe(stream->bap))
		return false;

	bap = stream->bap;

	ret = stream->ops->set_io(stream, fd);

	bt_bap_unref(bap);

	return ret;
}

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);
}

static bool match_name(const void *data, const void *match_data)
{
	const struct bt_bap_pac *pac = data;
	const char *name = match_data;

	return (!strcmp(pac->name, name));
}

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)
{
	int ret;
	struct bt_bap *bap;

	if (!bap_stream_valid(stream))
		return -EINVAL;

	if (!stream->ops || !stream->ops->io_link)
		return -EINVAL;

	if (!bt_bap_ref_safe(stream->bap))
		return -EINVAL;

	bap = stream->bap;

	ret = stream->ops->io_link(stream, link);

	bt_bap_unref(bap);

	return ret;
}

int bt_bap_stream_io_unlink(struct bt_bap_stream *stream,
				struct bt_bap_stream *link)
{
	int ret;
	struct bt_bap *bap;

	if (!bap_stream_valid(stream))
		return -EINVAL;

	if (!stream->ops || !stream->ops->io_unlink)
		return -EINVAL;

	if (!bt_bap_ref_safe(stream->bap))
		return -EINVAL;

	bap = stream->bap;

	ret = stream->ops->io_unlink(stream, link);

	bt_bap_unref(bap);

	return ret;
}

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 (!stream)
		return;

	if (!qos || *qos || stream->ep->dir != BT_BAP_SOURCE ||
				!stream->qos.ucast.io_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 (!stream)
		return;

	if (!qos || *qos || stream->ep->dir != BT_BAP_SINK ||
				!stream->qos.ucast.io_qos.sdu)
		return;

	*qos = &stream->qos;
}

static void bap_stream_bcast_get_out_qos(void *data, void *user_data)
{
	struct bt_bap_stream *stream = data;
	struct bt_bap_qos **qos = user_data;

	if (!stream)
		return;

	if (!qos || *qos || stream->ep->dir != BT_BAP_BCAST_SINK ||
				!stream->qos.bcast.io_qos.sdu)
		return;

	*qos = &stream->qos;
}

static void bap_stream_bcast_get_in_qos(void *data, void *user_data)
{
	struct bt_bap_stream *stream = data;
	struct bt_bap_qos **qos = user_data;

	if (!stream)
		return;

	if (!qos || *qos || stream->ep->dir != BT_BAP_BCAST_SOURCE ||
				!stream->qos.bcast.io_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;
	case BT_BAP_BCAST_SOURCE:
		bap_stream_bcast_get_in_qos(stream, in);
		break;
	case BT_BAP_BCAST_SINK:
		bap_stream_bcast_get_out_qos(stream, out);
		break;
	default:
		return false;
	}

	DBG(stream->bap, "in %p out %p", in ? *in : NULL, out ? *out : NULL);

	return (in && *in) || (out && *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;
	struct bt_bap *bap;

	if (!bap_stream_valid(stream))
		return 0;

	if (!stream->ops || !stream->ops->set_io)
		return 0;

	if (!bt_bap_ref_safe(stream->bap))
		return 00;

	bap = stream->bap;

	dir = stream->ops->io_dir(stream);

	bt_bap_unref(bap);

	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);

	if (!stream)
		return;

	if (fd >= 0)
		bap_stream_io_attach(stream, fd, true);
	else
		bap_stream_io_detach(stream);

	bap_stream_notify_connecting(stream, stream->io ? true : false, fd);
}

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;
}

bool bt_bap_new_bcast_source(struct bt_bap *bap, const char *name)
{
	struct bt_bap_endpoint *ep;
	struct bt_bap_pac *pac_broadcast_source;

	/* Add the remote source only if a local sink endpoint was registered */
	if (queue_isempty(bap->ldb->broadcast_sinks))
		return false;

	/* Add remote source endpoint */
	if (!bap->rdb->broadcast_sources)
		bap->rdb->broadcast_sources = queue_new();

	if (queue_find(bap->rdb->broadcast_sources, match_name, name))
		return true;

	pac_broadcast_source = bap_pac_new(bap->rdb, name, BT_BAP_BCAST_SOURCE,
			NULL, NULL, NULL, NULL);
	queue_push_tail(bap->rdb->broadcast_sources, pac_broadcast_source);

	if (!pac_broadcast_source)
		return false;

	queue_foreach(bap->pac_cbs, notify_pac_added, pac_broadcast_source);

	/* Push remote endpoint with direction sink */
	ep = bap_endpoint_new_broadcast(bap->rdb, BT_BAP_BCAST_SINK);

	if (ep)
		queue_push_tail(bap->remote_eps, ep);

	return true;
}

void bt_bap_update_bcast_source(struct bt_bap_pac *pac,
					struct bt_bap_codec *codec,
					struct iovec *data,
					struct iovec *metadata)
{
	bap_pac_merge(pac, data, metadata);
	pac->codec = *codec;
}

static void destroy_base_bis(void *data)
{
	struct bt_bis *bis = data;

	if (!bis)
		return;

	if (bis->caps)
		util_iov_free(bis->caps, 1);

	free(bis);
}

static void generate_bis_base(void *data, void *user_data)
{
	struct bt_bis *bis = data;
	struct iovec *base_iov = user_data;
	uint8_t cc_length = bis->caps->iov_len;

	if (!util_iov_push_u8(base_iov, bis->index))
		return;

	if (!util_iov_push_u8(base_iov, cc_length))
		return;

	if (cc_length)
		util_iov_push_mem(base_iov, bis->caps->iov_len,
			bis->caps->iov_base);
}

static void generate_subgroup_base(void *data, void *user_data)
{
	struct bt_subgroup *sgrp = data;
	struct iovec *base_iov = user_data;

	if (!util_iov_push_u8(base_iov, queue_length(sgrp->bises)))
		return;

	if (!util_iov_push_u8(base_iov, sgrp->codec.id))
		return;

	if (!util_iov_push_le16(base_iov, sgrp->codec.cid))
		return;

	if (!util_iov_push_le16(base_iov, sgrp->codec.vid))
		return;

	if (sgrp->caps) {
		if (!util_iov_push_u8(base_iov, sgrp->caps->iov_len))
			return;

		if (sgrp->caps->iov_len)
			util_iov_push_mem(base_iov, sgrp->caps->iov_len,
				sgrp->caps->iov_base);
	} else if (!util_iov_push_u8(base_iov, 0))
		return;

	if (sgrp->meta) {
		if (!util_iov_push_u8(base_iov, sgrp->meta->iov_len))
			return;

		if (sgrp->meta->iov_len)
			util_iov_push_mem(base_iov, sgrp->meta->iov_len,
				sgrp->meta->iov_base);
	} else if (!util_iov_push_u8(base_iov, 0))
		return;

	queue_foreach(sgrp->bises, generate_bis_base, base_iov);
}

static struct iovec *generate_base(struct bt_base *base)
{
	struct iovec *base_iov = new0(struct iovec, 0x1);

	base_iov->iov_base = util_malloc(BASE_MAX_LENGTH);

	if (!util_iov_push_le24(base_iov, base->pres_delay)) {
		free(base_iov->iov_base);
		free(base_iov);
		return NULL;
	}

	if (!util_iov_push_u8(base_iov,
			queue_length(base->subgroups))) {
		free(base_iov->iov_base);
		free(base_iov);
		return NULL;
	}

	queue_foreach(base->subgroups, generate_subgroup_base,
				base_iov);

	return base_iov;
}

static void add_new_bis(struct bt_subgroup *subgroup,
			uint8_t bis_index, struct iovec *caps)
{
	struct bt_bis *bis = new0(struct bt_bis, 1);

	bis->index = bis_index;

	if (caps)
		bis->caps = caps;
	else
		bis->caps = new0(struct iovec, 1);

	queue_push_tail(subgroup->bises, bis);
}

static void add_new_subgroup(struct bt_base *base,
			struct bt_bap_stream *stream)
{
	struct bt_bap_pac *lpac = stream->lpac;
	struct bt_subgroup *sgrp;
	uint16_t cid = 0;
	uint16_t vid = 0;

	if (!lpac)
		return;

	sgrp = new0(struct bt_subgroup, 1);

	bt_bap_pac_get_vendor_codec(lpac, &sgrp->codec.id, &cid,
			&vid, NULL, NULL);
	sgrp->codec.cid = cid;
	sgrp->codec.vid = vid;
	sgrp->caps = util_iov_dup(stream->cc, 1);
	sgrp->meta = util_iov_dup(stream->meta, 1);
	sgrp->bises = queue_new();

	stream->qos.bcast.bis = base->next_bis_index++;
	add_new_bis(sgrp, stream->qos.bcast.bis,
					NULL);
	queue_push_tail(base->subgroups, sgrp);
}

struct bt_ltv_match {
	uint8_t l;
	void *data;
	bool found;
	uint32_t data32;
};

struct bt_ltv_search {
	struct iovec *iov;
	bool found;
};

static void match_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v,
					void *user_data)
{
	struct bt_ltv_match *ltv_match = user_data;

	if (ltv_match->found == true)
		return;

	if (ltv_match->l != l)
		return;

	if (!memcmp(v, ltv_match->data, l))
		ltv_match->found = true;
}

static void search_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v,
					void *user_data)
{
	struct bt_ltv_search *ltv_search = user_data;
	struct bt_ltv_match ltv_match;

	ltv_match.found = false;
	ltv_match.l = l;
	ltv_match.data = v;

	util_ltv_foreach(ltv_search->iov->iov_base,
			ltv_search->iov->iov_len, &t,
			match_ltv, &ltv_match);

	/* Once "found" has been updated to "false",
	 * do not overwrite it anymore.
	 * It means that an ltv was not found in the search list,
	 * and this should be detected back in the parent function.
	 */
	if (ltv_search->found)
		ltv_search->found = ltv_match.found;
}

static bool compare_ltv(struct iovec *iov1,
		struct iovec *iov2)
{
	struct bt_ltv_search ltv_search;

	if ((!iov1) && (!iov2))
		return true;

	if ((!iov1) || (!iov2))
		return false;

	/* Compare metadata length */
	if (iov1->iov_len != iov2->iov_len)
		return false;

	ltv_search.found = true;
	ltv_search.iov = iov2;

	util_ltv_foreach(iov1->iov_base,
			iov1->iov_len, NULL,
			search_ltv, &ltv_search);

	return ltv_search.found;
}

struct bt_ltv_extract {
	struct iovec *src;
	void *value;
	uint8_t len;
	struct iovec *result;
};

static void extract_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v,
					void *user_data)
{
	struct bt_ltv_extract *ext_data = user_data;
	struct bt_ltv_match ltv_match;
	uint8_t ltv_len = 0;

	ltv_match.found = false;
	ltv_match.l = l;
	ltv_match.data = v;

	/* Search each BIS caps ltv in subgroup caps
	 * to extract the one that are BIS specific
	 */
	util_ltv_foreach(ext_data->src->iov_base,
			ext_data->src->iov_len, &t,
			match_ltv, &ltv_match);

	if (!ltv_match.found) {
		ltv_len = l + 1;
		util_iov_append(ext_data->result, &ltv_len, 1);
		util_iov_append(ext_data->result, &t, 1);
		util_iov_append(ext_data->result, v, l);
	}
}

static struct iovec *extract_diff_caps(
		struct iovec *subgroup_caps, struct iovec *bis_caps)
{
	struct bt_ltv_extract ext_data;

	ext_data.src = subgroup_caps;
	ext_data.result = new0(struct iovec, 1);

	util_ltv_foreach(bis_caps->iov_base,
			bis_caps->iov_len, NULL,
			extract_ltv, &ext_data);

	return ext_data.result;
}

static void set_base_subgroup(void *data, void *user_data)
{
	struct bt_bap_stream *stream = data;
	struct bt_base *base = user_data;
	/* BIS specific codec capabilities */
	struct iovec *bis_caps;

	if (bt_bap_pac_get_type(stream->lpac) != BT_BAP_BCAST_SOURCE)
		return;

	if (stream->qos.bcast.big != base->big_id)
		return;

	if (base->pres_delay < stream->qos.bcast.delay)
		base->pres_delay = stream->qos.bcast.delay;

	if (queue_isempty(base->subgroups)) {
		add_new_subgroup(base, stream);
	} else {
		/* Verify if a subgroup has the same metadata */
		const struct queue_entry *entry;
		struct bt_subgroup *subgroup = NULL;
		bool same_meta = false;

		for (entry = queue_get_entries(base->subgroups);
						entry; entry = entry->next) {
			subgroup = entry->data;
			same_meta = compare_ltv(subgroup->meta,	stream->meta);
			if (same_meta)
				break;
		}

		if (!same_meta) {
			/* No subgroup with the same metadata found.
			 * Create a new one.
			 */
			add_new_subgroup(base, stream);
		} else {
			/* Subgroup found with the same metadata.
			 * Extract different codec capabilities.
			 */
			bis_caps = extract_diff_caps(
					subgroup->caps,
					stream->cc);

			stream->qos.bcast.bis = base->next_bis_index++;
			add_new_bis(subgroup,
					stream->qos.bcast.bis,
					bis_caps);
		}
	}
}

static void destroy_base_subgroup(void *data)
{
	struct bt_subgroup *subgroup = data;

	if (!subgroup)
		return;

	if (subgroup->caps)
		util_iov_free(subgroup->caps, 1);

	if (subgroup->meta)
		util_iov_free(subgroup->meta, 1);

	queue_destroy(subgroup->bises, destroy_base_bis);

	free(subgroup);
}

/*
 * Function to update the BASE using configuration data
 * from each BIS belonging to the same BIG
 */
struct iovec *bt_bap_stream_get_base(struct bt_bap_stream *stream)
{
	struct bt_base base;
	struct iovec *base_iov;

	base.subgroups = queue_new();
	base.next_bis_index = 1;
	base.big_id = stream->qos.bcast.big;
	base.pres_delay = stream->qos.bcast.delay;

	/* If the BIG ID was explicitly set, create a BASE with information
	 * from all streams belonging to this BIG. Otherwise, create a BASE
	 * with only this BIS.
	 */
	if (stream->qos.bcast.big != 0xFF)
		queue_foreach(stream->bap->streams, set_base_subgroup, &base);
	else {
		base.pres_delay = stream->qos.bcast.delay;
		set_base_subgroup(stream, &base);
	}

	base_iov = generate_base(&base);

	queue_destroy(base.subgroups, destroy_base_subgroup);

	return base_iov;
}

/*
 * This function compares PAC Codec Specific Capabilities, with the Codec
 * Specific Configuration LTVs received in the BASE of the BAP Source. The
 * result is accumulated in data32 which is a bitmask of types.
 */
static void check_pac_caps_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v,
					void *user_data)
{
	struct bt_ltv_match *compare_data = user_data;
	uint8_t *bis_v = compare_data->data;
	uint16_t mask;
	uint16_t min;
	uint16_t max;
	uint16_t frame_len;

	switch (t) {
	case BAP_FREQ_LTV_TYPE:
		mask = get_le16(v);

		if (mask & (1 << (bis_v[0] - 1)))
			compare_data->data32 |= 1<<t;
		break;
	case BAP_DURATION_LTV_TYPE:
		if ((v[0]) & (1 << bis_v[0]))
			compare_data->data32 |= 1<<t;
		break;
	case BAP_FRAME_LEN_LTV_TYPE:
		min = get_le16(v);
		max = get_le16(v + 2);
		frame_len = get_le16(bis_v);

		if ((frame_len >= min) &&
				(frame_len <= max))
			compare_data->data32 |= 1<<t;
		break;
	}
}

static void check_source_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v,
					void *user_data)
{
	struct bt_ltv_match *local_data = user_data;
	struct iovec *pac_caps = local_data->data;
	struct bt_ltv_match compare_data;

	compare_data.data = v;

	/* Search inside local PAC's caps for LTV of type t */
	util_ltv_foreach(pac_caps->iov_base, pac_caps->iov_len, &t,
					check_pac_caps_ltv, &compare_data);

	local_data->data32 |= compare_data.data32;
}

static void bap_sink_check_level3_ltv(size_t i, uint8_t l, uint8_t t,
		uint8_t *v, void *user_data)
{
	struct bt_ltv_extract *merge_data = user_data;

	merge_data->value = v;
	merge_data->len = l;
}

static void bap_sink_check_level2_ltv(size_t i, uint8_t l, uint8_t t,
		uint8_t *v, void *user_data)
{
	struct bt_ltv_extract *merge_data = user_data;

	merge_data->value = NULL;
	util_ltv_foreach(merge_data->src->iov_base,
			merge_data->src->iov_len,
			&t,
			bap_sink_check_level3_ltv, merge_data);

	/* If the LTV at level 2 was found at level 3 add the one from level 3,
	 * otherwise add the one at level 2
	 */
	if (merge_data->value)
		util_ltv_push(merge_data->result, merge_data->len,
				t, merge_data->value);
	else
		util_ltv_push(merge_data->result, l, t, v);
}

static void bap_sink_append_level3_ltv(size_t i, uint8_t l, uint8_t t,
		uint8_t *v, void *user_data)
{
	struct bt_ltv_extract *merge_data = user_data;

	merge_data->value = NULL;
	util_ltv_foreach(merge_data->result->iov_base,
			merge_data->result->iov_len,
			&t,
			bap_sink_check_level3_ltv, merge_data);

	/* If the LTV at level 3 was not found in merged configuration,
	 * append value
	 */
	if (!merge_data->value)
		util_ltv_push(merge_data->result, l, t, v);
}

static void check_local_pac(void *data, void *user_data)
{
	struct bt_ltv_match *compare_data = user_data;
	struct iovec *bis_data = (struct iovec *)compare_data->data;
	const struct bt_bap_pac *pac = data;

	/* Keep searching for a matching PAC if one wasn't found
	 * in previous PAC element
	 */
	if (compare_data->found == false) {
		struct bt_ltv_match bis_compare_data = {
				.data = pac->data,
				.data32 = 0, /* LTVs bitmask result */
				.found = false
		};

		/* loop each BIS LTV */
		util_ltv_foreach(bis_data->iov_base, bis_data->iov_len, NULL,
				check_source_ltv, &bis_compare_data);

		/* We have a match if all selected LTVs have a match */
		if ((bis_compare_data.data32 &
				CODEC_SPECIFIC_CONFIGURATION_MASK) ==
				CODEC_SPECIFIC_CONFIGURATION_MASK) {
			compare_data->found = true;
			compare_data->data = data;
		}
	}
}

static void bap_sink_match_allocation(size_t i, uint8_t l, uint8_t t,
		uint8_t *v, void *user_data)
{
	struct bt_ltv_match *data = user_data;
	uint32_t location32;

	if (!v)
		return;

	memcpy(&location32, v, l);
	location32 = le32_to_cpu(location32);

	/* If all the bits in the received bitmask are found in
	 * the local bitmask then we have a match
	 */
	if ((location32 & data->data32) == location32)
		data->found = true;
	else
		data->found = false;
}

static struct bt_ltv_match bap_check_bis(uint32_t sink_loc, struct queue *pacs,
	struct iovec *bis_data)
{
	struct bt_ltv_match compare_data = {};

	/* Check channel allocation against the PACS location.
	 * If we don't have a location set we can accept any BIS location.
	 * If the BIS doesn't have a location set we also accept it
	 */
	compare_data.found = true;

	if (sink_loc) {
		uint8_t type = BAP_CHANNEL_ALLOCATION_LTV_TYPE;

		compare_data.data32 = sink_loc;
		util_ltv_foreach(bis_data->iov_base, bis_data->iov_len, &type,
				bap_sink_match_allocation, &compare_data);
	}

	/* Check remaining LTVs against the PACs list */
	if (compare_data.found) {
		compare_data.data = bis_data;
		compare_data.found = false;
		queue_foreach(pacs, check_local_pac, &compare_data);
	}

	return compare_data;
}

struct iovec *bt_bap_merge_caps(struct iovec *l2_caps, struct iovec *l3_caps)
{
	struct bt_ltv_extract merge_data = {0};

	if (!l2_caps)
		/* Codec_Specific_Configuration parameters shall
		 * be present at Level 2.
		 */
		return NULL;

	if (!l3_caps)
		/* Codec_Specific_Configuration parameters may
		 * be present at Level 3.
		 */
		return util_iov_dup(l2_caps, 1);

	merge_data.src = l3_caps;
	merge_data.result = new0(struct iovec, 1);

	/* Create a Codec Specific Configuration with LTVs at level 2 (subgroup)
	 * overwritten by LTVs at level 3 (BIS)
	 */
	util_ltv_foreach(l2_caps->iov_base,
			l2_caps->iov_len,
			NULL,
			bap_sink_check_level2_ltv, &merge_data);

	/* Append LTVs at level 3 (BIS) that were not found at
	 * level 2 (subgroup)
	 */
	util_ltv_foreach(l3_caps->iov_base,
			l3_caps->iov_len,
			NULL,
			bap_sink_append_level3_ltv, &merge_data);

	return merge_data.result;
}

void bt_bap_verify_bis(struct bt_bap *bap, uint8_t bis_index,
		struct iovec *caps,
		struct bt_bap_pac **lpac)
{
	struct bt_ltv_match match_data;
	uint32_t sink_loc;
	struct queue *pacs;

	if (!caps)
		return;

	/* If the bap session corresponds to a client connection with
	 * a BAP Server, bis caps should be checked against peer caps.
	 * If the bap session corresponds to a scanned broadcast source,
	 * bis caps should be checked against local broadcast sink caps.
	 */
	if (bap->client) {
		sink_loc = bap->rdb->pacs->sink_loc_value;
		pacs = bap->rdb->sinks;
	} else {
		sink_loc = bap->ldb->pacs->sink_loc_value;
		pacs = bap->ldb->broadcast_sinks;
	}

	/* Check each BIS Codec Specific Configuration LTVs against our Codec
	 * Specific Capabilities and if the BIS matches create a PAC with it
	 */
	match_data = bap_check_bis(sink_loc, pacs, caps);
	if (match_data.found == true) {
		*lpac = match_data.data;
		DBG(bap, "Matching BIS %i", bis_index);
	} else {
		*lpac = NULL;
	}

}

bool bt_bap_parse_base(uint8_t sid, struct iovec *iov,
			struct bt_bap_qos *qos,
			util_debug_func_t func,
			bt_bap_bis_func_t handler,
			void *user_data)
{
	uint8_t sgrps;
	bool ret = true;

	util_debug(func, NULL, "BASE len: %zd", iov->iov_len);

	if (!util_iov_pull_le24(iov, &qos->bcast.delay))
		return false;

	util_debug(func, NULL, "PresentationDelay: %d", qos->bcast.delay);

	if (!util_iov_pull_u8(iov, &sgrps))
		return false;

	util_debug(func, NULL, "Number of Subgroups: %d", sgrps);

	/* Loop subgroups */
	for (int idx = 0; idx < sgrps; idx++) {
		uint8_t num_bis;
		struct bt_bap_codec *codec;
		struct iovec l2_cc;
		uint8_t l2_cc_len;
		struct iovec meta;
		uint8_t meta_len;

		util_debug(func, NULL, "Subgroup #%d", idx);

		if (!util_iov_pull_u8(iov, &num_bis)) {
			ret = false;
			goto done;
		}

		util_debug(func, NULL, "Number of BISes: %d", num_bis);

		codec = util_iov_pull_mem(iov, sizeof(*codec));

		if (!codec) {
			ret = false;
			goto done;
		}

		util_debug(func, NULL, "Codec: ID %d CID 0x%2.2x VID 0x%2.2x",
				codec->id, codec->cid, codec->vid);

		/* Level 2 */
		/* Read Codec Specific Configuration */
		if (!util_iov_pull_u8(iov, &l2_cc_len)) {
			ret = false;
			goto done;
		}

		l2_cc.iov_base = util_iov_pull_mem(iov, l2_cc_len);

		if (!l2_cc.iov_base) {
			ret = false;
			goto done;
		}

		l2_cc.iov_len = l2_cc_len;

		/* Print Codec Specific Configuration */
		util_debug(func, NULL, "CC len: %zd", l2_cc.iov_len);
		bt_bap_debug_config(l2_cc.iov_base, l2_cc.iov_len,
								func, NULL);

		/* Read Metadata */
		if (!util_iov_pull_u8(iov, &meta_len)) {
			ret = false;
			goto done;
		}

		meta.iov_base = util_iov_pull_mem(iov, meta_len);

		if (!meta.iov_base) {
			ret = false;
			goto done;
		}

		meta.iov_len = meta_len;

		/* Print Metadata */
		util_debug(func, NULL, "Metadata len: %i",
				(uint8_t)meta.iov_len);
		bt_bap_debug_metadata(meta.iov_base, meta.iov_len,
							func, NULL);

		/* Level 3 */
		for (; num_bis; num_bis--) {
			uint8_t bis_index;
			struct iovec l3_cc;
			uint8_t l3_cc_len;
			struct iovec *bis_cc;

			if (!util_iov_pull_u8(iov, &bis_index)) {
				ret = false;
				goto done;
			}

			util_debug(func, NULL, "BIS #%d", bis_index);

			/* Read Codec Specific Configuration */
			if (!util_iov_pull_u8(iov, &l3_cc_len)) {
				ret = false;
				goto done;
			}

			l3_cc.iov_base = util_iov_pull_mem(iov,
							l3_cc_len);

			if (!l3_cc.iov_base) {
				ret = false;
				goto done;
			}

			l3_cc.iov_len = l3_cc_len;

			/* Print Codec Specific Configuration */
			util_debug(func, NULL, "CC Len: %d",
					(uint8_t)l3_cc.iov_len);

			bt_bap_debug_config(l3_cc.iov_base,
						l3_cc.iov_len,
						func, NULL);

			bis_cc = bt_bap_merge_caps(&l2_cc, &l3_cc);
			if (!bis_cc)
				continue;

			handler(sid, bis_index, idx, bis_cc, &meta,
				qos, user_data);

			util_iov_free(bis_cc, 1);
		}
	}

done:
	if (!ret)
		util_debug(func, NULL, "Unable to parse Base");

	return ret;
}

void bt_bap_req_bcode(struct bt_bap_stream *stream,
				bt_bap_bcode_reply_t reply,
				void *reply_data)
{
	const struct queue_entry *entry;

	if (!bap_stream_valid(stream))
		return;

	bt_bap_stream_ref(stream);

	if (!bt_bap_ref_safe(stream->bap))
		goto done;

	entry = queue_get_entries(stream->bap->bcode_cbs);

	while (entry) {
		struct bt_bap_bcode_cb *cb = entry->data;

		entry = entry->next;

		if (cb->func)
			cb->func(stream, reply, reply_data, cb->data);
	}

	bt_bap_unref(stream->bap);

done:
	bt_bap_stream_unref(stream);
}

unsigned int bt_bap_bcode_cb_register(struct bt_bap *bap,
				bt_bap_bcode_func_t func,
				void *user_data,
				bt_bap_destroy_func_t destroy)
{
	struct bt_bap_bcode_cb *cb;
	static unsigned int id;

	if (!bap)
		return 0;

	cb = new0(struct bt_bap_bcode_cb, 1);
	cb->id = ++id ? id : ++id;
	cb->func = func;
	cb->destroy = destroy;
	cb->data = user_data;

	queue_push_tail(bap->bcode_cbs, cb);

	return cb->id;
}

static bool match_bcode_cb_id(const void *data, const void *match_data)
{
	const struct bt_bap_bcode_cb *cb = data;
	unsigned int id = PTR_TO_UINT(match_data);

	return (cb->id == id);
}

bool bt_bap_bcode_cb_unregister(struct bt_bap *bap, unsigned int id)
{
	struct bt_bap_bcode_cb *cb;

	if (!bap)
		return false;

	cb = queue_remove_if(bap->bcode_cbs, match_bcode_cb_id,
						UINT_TO_PTR(id));
	if (!cb)
		return false;

	bap_bcode_cb_free(cb);

	return false;
}

void bt_bap_iso_qos_to_bap_qos(struct bt_iso_qos *iso_qos,
				struct bt_bap_qos *bap_qos)
{
	bap_qos->bcast.big = iso_qos->bcast.big;
	bap_qos->bcast.bis = iso_qos->bcast.bis;
	bap_qos->bcast.sync_factor = iso_qos->bcast.sync_factor;
	bap_qos->bcast.packing = iso_qos->bcast.packing;
	bap_qos->bcast.framing = iso_qos->bcast.framing;
	bap_qos->bcast.encryption = iso_qos->bcast.encryption;
	if (bap_qos->bcast.encryption)
		bap_qos->bcast.bcode = util_iov_new(iso_qos->bcast.bcode,
						sizeof(iso_qos->bcast.bcode));
	bap_qos->bcast.options = iso_qos->bcast.options;
	bap_qos->bcast.skip = iso_qos->bcast.skip;
	bap_qos->bcast.sync_timeout = iso_qos->bcast.sync_timeout;
	bap_qos->bcast.sync_cte_type =
			iso_qos->bcast.sync_cte_type;
	bap_qos->bcast.mse = iso_qos->bcast.mse;
	bap_qos->bcast.timeout = iso_qos->bcast.timeout;
	bap_qos->bcast.io_qos.interval =
			iso_qos->bcast.in.interval;
	bap_qos->bcast.io_qos.latency = iso_qos->bcast.in.latency;
	bap_qos->bcast.io_qos.phys = iso_qos->bcast.in.phys;
	bap_qos->bcast.io_qos.rtn = iso_qos->bcast.in.rtn;
	bap_qos->bcast.io_qos.sdu = iso_qos->bcast.in.sdu;
}

void bt_bap_qos_to_iso_qos(struct bt_bap_qos *bap_qos,
				struct bt_iso_qos *iso_qos)
{
	memset(iso_qos, 0, sizeof(*iso_qos));

	iso_qos->bcast.big = bap_qos->bcast.big;
	iso_qos->bcast.bis = bap_qos->bcast.bis;
	iso_qos->bcast.sync_factor = bap_qos->bcast.sync_factor;
	iso_qos->bcast.packing = bap_qos->bcast.packing;
	iso_qos->bcast.framing = bap_qos->bcast.framing;
	iso_qos->bcast.encryption = bap_qos->bcast.encryption;
	if (bap_qos->bcast.bcode && bap_qos->bcast.bcode->iov_base)
		memcpy(iso_qos->bcast.bcode, bap_qos->bcast.bcode->iov_base,
				bap_qos->bcast.bcode->iov_len);
	iso_qos->bcast.options = bap_qos->bcast.options;
	iso_qos->bcast.skip = bap_qos->bcast.skip;
	iso_qos->bcast.sync_timeout = bap_qos->bcast.sync_timeout;
	iso_qos->bcast.sync_cte_type = bap_qos->bcast.sync_cte_type;
	iso_qos->bcast.mse = bap_qos->bcast.mse;
	iso_qos->bcast.timeout = bap_qos->bcast.timeout;
	memcpy(&iso_qos->bcast.out, &bap_qos->bcast.io_qos,
			sizeof(struct bt_iso_io_qos));
}
