diff --git a/src/shared/bap.c b/src/shared/bap.c
index 00d211c15ddf..3b4803ab6c72 100644
--- a/src/shared/bap.c
+++ b/src/shared/bap.c
@@ -214,6 +214,31 @@ struct bt_bap_stream_io {
 	bool connecting;
 };
 
+struct bt_bap_stream_ops {
+	uint8_t type;
+	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 (*release)(struct bt_bap_stream *stream,
+				bt_bap_stream_func_t func, void *user_data);
+};
+
 struct bt_bap_stream {
 	struct bt_bap *bap;
 	struct bt_bap_endpoint *ep;
@@ -224,6 +249,7 @@ struct bt_bap_stream {
 	struct bt_bap_qos qos;
 	struct bt_bap_stream *link;
 	struct bt_bap_stream_io *io;
+	const struct bt_bap_stream_ops *ops;
 	bool client;
 	void *user_data;
 };
@@ -829,27 +855,46 @@ static bool bap_codec_equal(const struct bt_bap_codec *c1,
 	return c1->id == c2->id;
 }
 
-static struct bt_bap_stream *bap_stream_new(struct bt_bap *bap,
-						struct bt_bap_endpoint *ep,
-						struct bt_bap_pac *lpac,
-						struct bt_bap_pac *rpac,
-						struct iovec *data,
-						bool client)
+static void ascs_ase_rsp_add(struct iovec *iov, uint8_t id,
+					uint8_t code, uint8_t reason)
 {
-	struct bt_bap_stream *stream;
+	struct bt_ascs_cp_rsp *cp;
+	struct bt_ascs_ase_rsp *rsp;
 
-	stream = new0(struct bt_bap_stream, 1);
-	stream->bap = bap;
-	stream->ep = ep;
-	ep->stream = stream;
-	stream->lpac = lpac;
-	stream->rpac = rpac;
-	stream->cc = util_iov_dup(data, 1);
-	stream->client = client;
+	if (!iov)
+		return;
 
-	queue_push_tail(bap->streams, stream);
+	cp = iov->iov_base;
 
-	return stream;
+	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)
@@ -985,6 +1030,14 @@ static void stream_notify_metadata(struct bt_bap_stream *stream)
 	free(status);
 }
 
+static struct bt_bap *bt_bap_ref_safe(struct bt_bap *bap)
+{
+	if (!bap || !bap->ref_count)
+		return NULL;
+
+	return bt_bap_ref(bap);
+}
+
 static void bap_stream_clear_cfm(struct bt_bap_stream *stream)
 {
 	if (!stream->lpac->ops || !stream->lpac->ops->clear)
@@ -1080,83 +1133,6 @@ static void bap_stream_update_io_links(struct bt_bap_stream *stream)
 	queue_find(bap->streams, bap_stream_io_link, stream);
 }
 
-static struct bt_bap_stream_io *stream_io_ref(struct bt_bap_stream_io *io)
-{
-	if (!io)
-		return NULL;
-
-	__sync_fetch_and_add(&io->ref_count, 1);
-
-	return io;
-}
-
-static struct bt_bap_stream_io *stream_io_new(struct bt_bap *bap, int fd)
-{
-	struct io *io;
-	struct bt_bap_stream_io *sio;
-
-	io = io_new(fd);
-	if (!io)
-		return NULL;
-
-	DBG(bap, "fd %d", fd);
-
-	sio = new0(struct bt_bap_stream_io, 1);
-	sio->bap = bap;
-	sio->io = io;
-
-	return stream_io_ref(sio);
-}
-
-static struct bt_bap_stream_io *stream_get_io(struct bt_bap_stream *stream)
-{
-	if (!stream)
-		return NULL;
-
-	if (stream->io)
-		return stream->io;
-
-	if (stream->link)
-		return stream->link->io;
-
-	return NULL;
-}
-
-static bool stream_io_disconnected(struct io *io, void *user_data);
-
-static bool bap_stream_io_attach(struct bt_bap_stream *stream, int fd,
-							bool connecting)
-{
-	struct bt_bap_stream_io *io;
-
-	io = stream_get_io(stream);
-	if (io) {
-		if (fd == stream_io_get_fd(io)) {
-			if (!stream->io)
-				stream->io = stream_io_ref(io);
-
-			io->connecting = connecting;
-			return true;
-		}
-
-		DBG(stream->bap, "stream %p io already set", stream);
-		return false;
-	}
-
-	DBG(stream->bap, "stream %p connecting %s", stream,
-				connecting ? "true" : "false");
-
-	io = stream_io_new(stream->bap, fd);
-	if (!io)
-		return false;
-
-	io->connecting = connecting;
-	stream->io = io;
-	io_set_disconnect_handler(io->io, stream_io_disconnected, stream, NULL);
-
-	return true;
-}
-
 static bool bap_stream_io_detach(struct bt_bap_stream *stream)
 {
 	struct bt_bap_stream *link;
@@ -1182,51 +1158,6 @@ static bool bap_stream_io_detach(struct bt_bap_stream *stream)
 	return true;
 }
 
-static void bap_stream_set_io(void *data, void *user_data)
-{
-	struct bt_bap_stream *stream = data;
-	int fd = PTR_TO_INT(user_data);
-	bool ret;
-
-	if (fd >= 0)
-		ret = bap_stream_io_attach(stream, fd, false);
-	else
-		ret = bap_stream_io_detach(stream);
-
-	if (!ret)
-		return;
-
-	switch (stream->ep->state) {
-	case BT_BAP_STREAM_STATE_ENABLING:
-		if (fd < 0)
-			bt_bap_stream_disable(stream, false, NULL, NULL);
-		else
-			bt_bap_stream_start(stream, NULL, NULL);
-		break;
-	case BT_BAP_STREAM_STATE_DISABLING:
-		if (fd < 0)
-			bt_bap_stream_stop(stream, NULL, NULL);
-		break;
-	}
-}
-
-static struct bt_bap *bt_bap_ref_safe(struct bt_bap *bap)
-{
-	if (!bap || !bap->ref_count)
-		return NULL;
-
-	return bt_bap_ref(bap);
-}
-
-static void stream_stop_complete(struct bt_bap_stream *stream, uint8_t code,
-					uint8_t reason,	void *user_data)
-{
-	DBG(stream->bap, "stream %p stop 0x%02x 0x%02x", stream, code, reason);
-
-	if (stream->ep->state == BT_ASCS_ASE_STATE_DISABLING)
-		bap_stream_io_detach(stream);
-}
-
 static void bap_req_free(void *data)
 {
 	struct bt_bap_req *req = data;
@@ -1268,6 +1199,15 @@ done:
 	bap_req_free(req);
 }
 
+static void stream_stop_complete(struct bt_bap_stream *stream, uint8_t code,
+					uint8_t reason,	void *user_data)
+{
+	DBG(stream->bap, "stream %p stop 0x%02x 0x%02x", stream, code, reason);
+
+	if (stream->ep->state == BT_ASCS_ASE_STATE_DISABLING)
+		bap_stream_io_detach(stream);
+}
+
 static void bap_stream_state_changed(struct bt_bap_stream *stream)
 {
 	struct bt_bap *bap = stream->bap;
@@ -1344,6 +1284,39 @@ static void bap_stream_state_changed(struct bt_bap_stream *stream)
 	bt_bap_unref(bap);
 }
 
+static void stream_set_state(struct bt_bap_stream *stream, uint8_t state)
+{
+	struct bt_bap_endpoint *ep = stream->ep;
+
+	ep->old_state = ep->state;
+	ep->state = state;
+
+	if (stream->lpac->type == BT_BAP_BCAST_SINK)
+		goto done;
+
+	if (stream->client)
+		goto done;
+
+	switch (ep->state) {
+	case BT_ASCS_ASE_STATE_IDLE:
+		break;
+	case BT_ASCS_ASE_STATE_CONFIG:
+		stream_notify_config(stream);
+		break;
+	case BT_ASCS_ASE_STATE_QOS:
+		stream_notify_qos(stream);
+		break;
+	case BT_ASCS_ASE_STATE_ENABLING:
+	case BT_ASCS_ASE_STATE_STREAMING:
+	case BT_ASCS_ASE_STATE_DISABLING:
+		stream_notify_metadata(stream);
+		break;
+	}
+
+done:
+	bap_stream_state_changed(stream);
+}
+
 static void stream_set_state_broadcast(struct bt_bap_stream *stream,
 					uint8_t state)
 {
@@ -1390,73 +1363,850 @@ static void stream_set_state_broadcast(struct bt_bap_stream *stream,
 	bt_bap_unref(bap);
 }
 
-static void stream_set_state(struct bt_bap_stream *stream, uint8_t state)
+static void ep_config_cb(struct bt_bap_stream *stream, int err)
 {
-	struct bt_bap_endpoint *ep = stream->ep;
+	if (err)
+		return;
 
-	ep->old_state = ep->state;
-	ep->state = state;
-
-	if (stream->lpac->type == BT_BAP_BCAST_SINK)
-		goto done;
-
-	if (stream->client)
-		goto done;
-
-	switch (ep->state) {
-	case BT_ASCS_ASE_STATE_IDLE:
-		break;
-	case BT_ASCS_ASE_STATE_CONFIG:
-		stream_notify_config(stream);
-		break;
-	case BT_ASCS_ASE_STATE_QOS:
-		stream_notify_qos(stream);
-		break;
-	case BT_ASCS_ASE_STATE_ENABLING:
-	case BT_ASCS_ASE_STATE_STREAMING:
-	case BT_ASCS_ASE_STATE_DISABLING:
-		stream_notify_metadata(stream);
-		break;
+	if (bt_bap_stream_get_type(stream) == BT_BAP_STREAM_TYPE_BCAST) {
+		if (!bt_bap_stream_io_dir(stream))
+			stream_set_state_broadcast(stream,
+				BT_BAP_STREAM_STATE_QOS);
+		else if (bt_bap_stream_io_dir(stream) == BT_BAP_BCAST_SOURCE)
+			stream_set_state_broadcast(stream,
+				BT_BAP_STREAM_STATE_CONFIG);
+		return;
 	}
 
-done:
-	bap_stream_state_changed(stream);
+	stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG);
 }
 
-static void ascs_ase_rsp_add(struct iovec *iov, uint8_t id,
-					uint8_t code, uint8_t reason)
+static uint8_t stream_config(struct bt_bap_stream *stream, struct iovec *cc,
+							struct iovec *rsp)
 {
-	struct bt_ascs_cp_rsp *cp;
-	struct bt_ascs_ase_rsp *rsp;
+	struct bt_bap_pac *pac = stream->lpac;
 
-	if (!iov)
-		return;
+	DBG(stream->bap, "stream %p", stream);
 
-	cp = iov->iov_base;
+	/* TODO: Wait for pac->ops response */
+	ascs_ase_rsp_success(rsp, stream->ep->id);
 
-	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;
+	if (!util_iov_memcmp(stream->cc, cc)) {
+		stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG);
+		return 0;
 	}
 
-	iov->iov_len += sizeof(*rsp);
-	iov->iov_base = realloc(iov->iov_base, iov->iov_len);
+	util_iov_free(stream->cc, 1);
+	stream->cc = util_iov_dup(cc, 1);
 
-	rsp = iov->iov_base + (iov->iov_len - sizeof(*rsp));
-	rsp->ase = id;
-	rsp->code = code;
-	rsp->reason = reason;
+	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)
+		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->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 = 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 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 = qos->ucast.io_qos.phy;
+	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;
+
+	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.phy = data->ucast.io_qos.phy;
+	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 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;
+	struct metadata {
+		uint8_t len;
+		uint8_t type;
+		uint8_t data[2];
+	} ctx = LTV(0x02, 0x01, 0x00); /* Context = Unspecified */
+
+	memset(&meta, 0, sizeof(meta));
+
+	meta.ase = stream->ep->id;
+
+	iov[0].iov_base = &meta;
+	iov[0].iov_len = sizeof(meta);
+
+	if (data)
+		iov[1] = *data;
+	else {
+		iov[1].iov_base = &ctx;
+		iov[1].iov_len = sizeof(ctx);
+	}
+
+	meta.len = iov[1].iov_len;
+
+	req = bap_req_new(stream, op, iov, 2, func, user_data);
+
+	if (!bap_queue_req(stream->bap, req)) {
+		bap_req_free(req);
+		return 0;
+	}
+
+	return req->id;
+}
+
+static 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)
+{
+	stream->qos = *qos;
+	if (stream->lpac->type == BT_BAP_BCAST_SINK) {
+		if (data)
+			stream_config(stream, data, NULL);
+		stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG);
+	}
+
+	return 1;
+}
+
+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;
+
+	if (stream->link)
+		bap_stream_metadata(stream->link, BT_ASCS_ENABLE, data,
+					NULL, NULL);
+
+	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 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;
+
+	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)
+		bt_bap_stream_disable(stream->link, false, NULL, NULL);
+
+	return req->id;
+}
+
+static uint8_t stream_stop(struct bt_bap_stream *stream, struct iovec *rsp)
+{
+	DBG(stream->bap, "stream %p", stream);
+
+	if (!stream)
+		return 0;
+
+	ascs_ase_rsp_success(rsp, stream->ep->id);
+
+	stream_set_state(stream, BT_BAP_STREAM_STATE_QOS);
+
+	return 0;
+}
+
+static 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);
+
+	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;
+	}
+
+	return bap_stream_metadata(stream, BT_ASCS_METADATA, data, func,
+					user_data);
+}
+
+static uint8_t stream_release(struct bt_bap_stream *stream, struct iovec *rsp)
+{
+	struct bt_bap_pac *pac;
+
+	DBG(stream->bap, "stream %p", stream);
+
+	ascs_ase_rsp_success(rsp, stream->ep->id);
+
+	pac = stream->lpac;
+	if (pac->ops && pac->ops->clear)
+		pac->ops->clear(stream, pac->user_data);
+
+	stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE);
+
+	return 0;
+}
+
+static 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_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);
+		stream = NULL;
+	}
+
+	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 unsigned int bap_bcast_enable(struct bt_bap_stream *stream,
+					bool enable_links, struct iovec *data,
+					bt_bap_stream_func_t func,
+					void *user_data)
+{
+	if (bt_bap_stream_io_dir(stream) == BT_BAP_BCAST_SOURCE)
+		stream_set_state_broadcast(stream,
+						BT_BAP_STREAM_STATE_STREAMING);
+	else
+		stream_set_state_broadcast(stream, BT_BAP_STREAM_STATE_CONFIG);
+
+	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_broadcast(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)
+{
+	stream_set_state_broadcast(stream, BT_BAP_STREAM_STATE_DISABLING);
+
+	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);
+
+	return 1;
+}
+
+static unsigned int bap_bcast_release(struct bt_bap_stream *stream,
+					bt_bap_stream_func_t func,
+					void *user_data)
+{
+	stream_set_state_broadcast(stream, BT_BAP_STREAM_STATE_RELEASING);
+
+	return 1;
+}
+
+#define STREAM_OPS(_type, _config, _qos, _enable, _start, _disable, _stop, \
+			_metadata, _release) \
+{ \
+	.type = _type, \
+	.config = _config, \
+	.qos = _qos, \
+	.enable = _enable, \
+	.start = _start, \
+	.disable = _disable, \
+	.stop = _stop, \
+	.metadata = _metadata, \
+	.release = _release, \
+}
+
+static const struct bt_bap_stream_ops stream_ops[] = {
+	STREAM_OPS(BT_BAP_SINK,
+			bap_ucast_config, bap_ucast_qos, bap_ucast_enable,
+			bap_ucast_start, bap_ucast_disable, bap_ucast_stop,
+			bap_ucast_metadata, bap_ucast_release),
+	STREAM_OPS(BT_BAP_SOURCE,
+			bap_ucast_config, bap_ucast_qos, bap_ucast_enable,
+			bap_ucast_start, bap_ucast_disable, bap_ucast_stop,
+			bap_ucast_metadata, bap_ucast_release),
+	STREAM_OPS(BT_BAP_BCAST_SINK,
+			bap_bcast_config, NULL, bap_bcast_enable,
+			bap_bcast_start, bap_bcast_disable, NULL,
+			bap_bcast_metadata, bap_bcast_release),
+	STREAM_OPS(BT_BAP_BCAST_SOURCE,
+			bap_bcast_config, NULL, bap_bcast_enable,
+			bap_bcast_start, bap_bcast_disable, NULL,
+			bap_bcast_metadata, bap_bcast_release),
+};
+
+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_stream_get_type(stream);
+	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;
+	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);
+
+	queue_push_tail(bap->streams, stream);
+
+	return stream;
+}
+
+static struct bt_bap_stream_io *stream_io_ref(struct bt_bap_stream_io *io)
+{
+	if (!io)
+		return NULL;
+
+	__sync_fetch_and_add(&io->ref_count, 1);
+
+	return io;
+}
+
+static struct bt_bap_stream_io *stream_io_new(struct bt_bap *bap, int fd)
+{
+	struct io *io;
+	struct bt_bap_stream_io *sio;
+
+	io = io_new(fd);
+	if (!io)
+		return NULL;
+
+	DBG(bap, "fd %d", fd);
+
+	sio = new0(struct bt_bap_stream_io, 1);
+	sio->bap = bap;
+	sio->io = io;
+
+	return stream_io_ref(sio);
+}
+
+static struct bt_bap_stream_io *stream_get_io(struct bt_bap_stream *stream)
+{
+	if (!stream)
+		return NULL;
+
+	if (stream->io)
+		return stream->io;
+
+	if (stream->link)
+		return stream->link->io;
+
+	return NULL;
+}
+
+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_set_io(void *data, void *user_data)
+{
+	struct bt_bap_stream *stream = data;
+	int fd = PTR_TO_INT(user_data);
+	bool ret;
+
+	if (fd >= 0)
+		ret = bap_stream_io_attach(stream, fd, false);
+	else
+		ret = bap_stream_io_detach(stream);
+
+	if (!ret)
+		return;
+
+	switch (stream->ep->state) {
+	case BT_BAP_STREAM_STATE_ENABLING:
+		if (fd < 0)
+			bt_bap_stream_disable(stream, false, NULL, NULL);
+		else
+			bt_bap_stream_start(stream, NULL, NULL);
+		break;
+	case BT_BAP_STREAM_STATE_DISABLING:
+		if (fd < 0)
+			bt_bap_stream_stop(stream, NULL, NULL);
+		break;
+	}
 }
 
 static void ascs_ase_rsp_add_errno(struct iovec *iov, uint8_t id, int err)
@@ -1518,55 +2268,6 @@ static void ascs_ase_rsp_add_errno(struct iovec *iov, uint8_t id, int err)
 	}
 }
 
-static void ascs_ase_rsp_success(struct iovec *iov, uint8_t id)
-{
-	return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_SUCCESS,
-					BT_ASCS_REASON_NONE);
-}
-
-static void ep_config_cb(struct bt_bap_stream *stream, int err)
-{
-	if (err)
-		return;
-
-	if (bt_bap_stream_get_type(stream) == BT_BAP_STREAM_TYPE_BCAST) {
-		if (!bt_bap_stream_io_dir(stream))
-			stream_set_state_broadcast(stream,
-				BT_BAP_STREAM_STATE_QOS);
-		else if (bt_bap_stream_io_dir(stream) == BT_BAP_BCAST_SOURCE)
-			stream_set_state_broadcast(stream,
-				BT_BAP_STREAM_STATE_CONFIG);
-		return;
-	}
-
-	stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG);
-}
-
-static uint8_t stream_config(struct bt_bap_stream *stream, struct iovec *cc,
-							struct iovec *rsp)
-{
-	struct bt_bap_pac *pac = stream->lpac;
-
-	DBG(stream->bap, "stream %p", stream);
-
-	/* TODO: Wait for pac->ops response */
-	ascs_ase_rsp_success(rsp, stream->ep->id);
-
-	if (!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 uint8_t ep_config(struct bt_bap_endpoint *ep, struct bt_bap *bap,
 				 struct bt_ascs_config *req,
 				 struct iovec *iov, struct iovec *rsp)
@@ -1826,17 +2527,6 @@ static uint8_t ascs_enable(struct bt_ascs *ascs, struct bt_bap *bap,
 	return ep_enable(ep, bap, req, iov, rsp);
 }
 
-static uint8_t stream_start(struct bt_bap_stream *stream, struct iovec *rsp)
-{
-	DBG(stream->bap, "stream %p", stream);
-
-	ascs_ase_rsp_success(rsp, stream->ep->id);
-
-	stream_set_state(stream, BT_BAP_STREAM_STATE_STREAMING);
-
-	return 0;
-}
-
 static uint8_t ep_start(struct bt_bap_endpoint *ep, struct iovec *rsp)
 {
 	struct bt_bap_stream *stream = ep->stream;
@@ -1970,20 +2660,6 @@ static uint8_t ascs_disable(struct bt_ascs *ascs, struct bt_bap *bap,
 	return ep_disable(ep, rsp);
 }
 
-static uint8_t stream_stop(struct bt_bap_stream *stream, struct iovec *rsp)
-{
-	DBG(stream->bap, "stream %p", stream);
-
-	if (!stream)
-		return 0;
-
-	ascs_ase_rsp_success(rsp, stream->ep->id);
-
-	stream_set_state(stream, BT_BAP_STREAM_STATE_QOS);
-
-	return 0;
-}
-
 static uint8_t ep_stop(struct bt_bap_endpoint *ep, struct iovec *rsp)
 {
 	struct bt_bap_stream *stream = ep->stream;
@@ -2045,19 +2721,6 @@ static uint8_t ascs_stop(struct bt_ascs *ascs, struct bt_bap *bap,
 	return ep_stop(ep, rsp);
 }
 
-static uint8_t stream_metadata(struct bt_bap_stream *stream, struct iovec *meta,
-						struct iovec *rsp)
-{
-	DBG(stream->bap, "stream %p", stream);
-
-	ascs_ase_rsp_success(rsp, stream->ep->id);
-
-	util_iov_free(stream->meta, 1);
-	stream->meta = util_iov_dup(meta, 1);
-
-	return 0;
-}
-
 static uint8_t ep_metadata(struct bt_bap_endpoint *ep, struct iovec *meta,
 						struct iovec *rsp)
 {
@@ -2110,23 +2773,6 @@ static uint8_t ascs_metadata(struct bt_ascs *ascs, struct bt_bap *bap,
 	return ep_metadata(ep, iov, rsp);
 }
 
-static uint8_t stream_release(struct bt_bap_stream *stream, struct iovec *rsp)
-{
-	struct bt_bap_pac *pac;
-
-	DBG(stream->bap, "stream %p", stream);
-
-	ascs_ase_rsp_success(rsp, stream->ep->id);
-
-	pac = stream->lpac;
-	if (pac->ops && pac->ops->clear)
-		pac->ops->clear(stream, pac->user_data);
-
-	stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE);
-
-	return 0;
-}
-
 static uint8_t ascs_release(struct bt_ascs *ascs, struct bt_bap *bap,
 				struct iovec *iov, struct iovec *rsp)
 {
@@ -2363,20 +3009,6 @@ static struct bt_pacs *bap_get_pacs(struct bt_bap *bap)
 	return bap->rdb->pacs;
 }
 
-static struct bt_ascs *bap_get_ascs(struct bt_bap *bap)
-{
-	if (!bap)
-		return NULL;
-
-	if (bap->rdb->ascs)
-		return bap->rdb->ascs;
-
-	bap->rdb->ascs = new0(struct bt_ascs, 1);
-	bap->rdb->ascs->bdb = bap->rdb;
-
-	return bap->rdb->ascs;
-}
-
 static bool match_codec(const void *data, const void *user_data)
 {
 	const struct bt_bap_pac *pac = data;
@@ -3844,156 +4476,6 @@ static void bap_endpoint_attach(struct bt_bap *bap, struct bt_bap_endpoint *ep)
 						bap_endpoint_notify, ep);
 }
 
-static void append_group(void *data, void *user_data)
-{
-	struct bt_bap_req *req = data;
-	struct iovec *iov = user_data;
-	size_t i;
-
-	for (i = 0; i < req->len; i++)
-		util_iov_push_mem(iov, req->iov[i].iov_len,
-					req->iov[i].iov_base);
-}
-
-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 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->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 match_req(const void *data, const void *match_data)
-{
-	const struct bt_bap_req *pend = data;
-	const struct bt_bap_req *req = match_data;
-
-	return pend->op == req->op;
-}
-
-static bool bap_queue_req(struct bt_bap *bap, struct bt_bap_req *req)
-{
-	struct bt_bap_req *pend;
-	struct queue *queue;
-	struct bt_att *att = bt_bap_get_att(bap);
-	uint16_t mtu = bt_att_get_mtu(att);
-	uint16_t len = 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 bap_cp_notify(struct bt_bap *bap, uint16_t value_handle,
 				const uint8_t *value, uint16_t length,
 				void *user_data)
@@ -4562,35 +5044,6 @@ static bool find_ep_pacs(const void *data, const void *user_data)
 	return false;
 }
 
-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 bool bap_stream_valid(struct bt_bap_stream *stream)
-{
-	if (!stream || !stream->bap)
-		return false;
-
-	return queue_find(stream->bap->streams, NULL, stream);
-}
-
 unsigned int bt_bap_stream_config(struct bt_bap_stream *stream,
 					struct bt_bap_qos *qos,
 					struct iovec *data,
@@ -4876,10 +5329,7 @@ unsigned int bt_bap_stream_qos(struct bt_bap_stream *stream,
 	struct bt_ascs_qos qos;
 	struct bt_bap_req *req;
 
-	/* Table 3.2: ASE state machine transition
-	 * Initiating device - client Only
-	 */
-	if (!bap_stream_valid(stream) || !stream->client)
+	if (!bap_stream_valid(stream))
 		return 0;
 
 	memset(&qos, 0, sizeof(qos));
@@ -4911,130 +5361,33 @@ unsigned int bt_bap_stream_qos(struct bt_bap_stream *stream,
 	return req->id;
 }
 
-static int bap_stream_metadata(struct bt_bap_stream *stream, uint8_t op,
-					struct iovec *data,
-					bt_bap_stream_func_t func,
-					void *user_data)
-{
-	struct iovec iov[2];
-	struct bt_ascs_metadata meta;
-	struct bt_bap_req *req;
-	struct metadata {
-		uint8_t len;
-		uint8_t type;
-		uint8_t data[2];
-	} ctx = LTV(0x02, 0x01, 0x00); /* Context = Unspecified */
-
-	memset(&meta, 0, sizeof(meta));
-
-	meta.ase = stream->ep->id;
-
-	iov[0].iov_base = &meta;
-	iov[0].iov_len = sizeof(meta);
-
-	if (data)
-		iov[1] = *data;
-	else {
-		iov[1].iov_base = &ctx;
-		iov[1].iov_len = sizeof(ctx);
-	}
-
-	meta.len = iov[1].iov_len;
-
-	req = bap_req_new(stream, op, iov, 2, func, user_data);
-
-	if (!bap_queue_req(stream->bap, req)) {
-		bap_req_free(req);
-		return 0;
-	}
-
-	return req->id;
-}
-
 unsigned int bt_bap_stream_enable(struct bt_bap_stream *stream,
 					bool enable_links,
 					struct iovec *metadata,
 					bt_bap_stream_func_t func,
 					void *user_data)
 {
-	int ret = 0;
-
-	/* Table 3.2: ASE state machine transition
-	 * Initiating device - client Only
-	 */
-	if (!bap_stream_valid(stream) || !stream->client)
+	if (!bap_stream_valid(stream))
 		return 0;
 
-	switch (bt_bap_stream_get_type(stream)) {
-	case BT_BAP_STREAM_TYPE_UCAST:
-		ret = bap_stream_metadata(stream, BT_ASCS_ENABLE, metadata,
-							func, user_data);
-		if (!ret || !enable_links)
-			return ret;
+	if (!stream->ops || !stream->ops->enable)
+		return 0;
 
-		if (stream->link)
-			bap_stream_metadata(stream->link, BT_ASCS_ENABLE,
-						metadata, NULL, NULL);
-		break;
-	case BT_BAP_STREAM_TYPE_BCAST:
-		if (!bt_bap_stream_io_dir(stream))
-			stream_set_state_broadcast(stream,
-				BT_BAP_STREAM_STATE_CONFIG);
-		else if (bt_bap_stream_io_dir(stream) == BT_BAP_BCAST_SOURCE)
-			stream_set_state_broadcast(stream,
-				 BT_BAP_STREAM_STATE_STREAMING);
-
-		return 1;
-	}
-
-	return ret;
+	return stream->ops->enable(stream, enable_links, metadata, func,
+					user_data);
 }
 
 unsigned int bt_bap_stream_start(struct bt_bap_stream *stream,
 					bt_bap_stream_func_t func,
 					void *user_data)
 {
-	struct iovec iov;
-	struct bt_ascs_start start;
-	struct bt_bap_req *req;
-
 	if (!bap_stream_valid(stream))
 		return 0;
 
-	switch (bt_bap_stream_get_type(stream)) {
-	case BT_BAP_STREAM_TYPE_UCAST:
-		if (!stream->client) {
-			if (stream->ep->dir == BT_BAP_SINK)
-				stream_start(stream, NULL);
-			return 0;
-		}
+	if (!stream->ops || !stream->ops->start)
+		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;
-	case BT_BAP_STREAM_TYPE_BCAST:
-		stream_set_state_broadcast(stream,
-					BT_BAP_STREAM_STATE_STREAMING);
-		return 1;
-	}
-
-	return 0;
+	return stream->ops->start(stream, func, user_data);
 }
 
 unsigned int bt_bap_stream_disable(struct bt_bap_stream *stream,
@@ -5042,84 +5395,26 @@ unsigned int bt_bap_stream_disable(struct bt_bap_stream *stream,
 					bt_bap_stream_func_t func,
 					void *user_data)
 {
-	struct iovec iov;
-	struct bt_ascs_disable disable;
-	struct bt_bap_req *req;
-
 	if (!bap_stream_valid(stream))
 		return 0;
 
-	if (!stream->client) {
-		stream_disable(stream, NULL);
+	if (!stream->ops || !stream->ops->disable)
 		return 0;
-	}
 
-	switch (bt_bap_stream_get_type(stream)) {
-	case BT_BAP_STREAM_TYPE_UCAST:
-		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)
-			bt_bap_stream_disable(stream->link, false, NULL, NULL);
-
-		return req->id;
-
-	case BT_BAP_STREAM_TYPE_BCAST:
-		stream_set_state_broadcast(stream,
-					BT_BAP_STREAM_STATE_DISABLING);
-		return 1;
-	}
-
-	return 0;
+	return stream->ops->disable(stream, disable_links, func, user_data);
 }
 
 unsigned int bt_bap_stream_stop(struct bt_bap_stream *stream,
 					bt_bap_stream_func_t func,
 					void *user_data)
 {
-	struct iovec iov;
-	struct bt_ascs_stop stop;
-	struct bt_bap_req *req;
-
 	if (!bap_stream_valid(stream))
 		return 0;
 
-	if (!stream->client) {
-		if (stream->ep->dir == BT_BAP_SINK)
-			stream_stop(stream, NULL);
-		return 0;
-	}
-
-	if (stream->ep->dir == BT_BAP_SINK)
+	if (!stream->ops || !stream->ops->stop)
 		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;
+	return stream->ops->stop(stream, func, user_data);
 }
 
 unsigned int bt_bap_stream_metadata(struct bt_bap_stream *stream,
@@ -5127,73 +5422,23 @@ unsigned int bt_bap_stream_metadata(struct bt_bap_stream *stream,
 					bt_bap_stream_func_t func,
 					void *user_data)
 {
-	if (!stream)
+	if (!bap_stream_valid(stream))
 		return 0;
 
-	switch (bt_bap_stream_get_type(stream)) {
-	case BT_BAP_STREAM_TYPE_UCAST:
-		if (!stream->client) {
-			stream_metadata(stream, metadata, NULL);
-			return 0;
-		}
-		return bap_stream_metadata(stream, BT_ASCS_METADATA,
-				metadata, func,	user_data);
-	case BT_BAP_STREAM_TYPE_BCAST:
-		util_iov_free(stream->meta, 1);
-		stream->meta = util_iov_dup(metadata, 1);
-		return 1;
-	default:
+	if (!stream->ops || !stream->ops->metadata)
 		return 0;
-	}
+
+	return stream->ops->metadata(stream, metadata, func, user_data);
 }
 
 unsigned int bt_bap_stream_release(struct bt_bap_stream *stream,
 					bt_bap_stream_func_t func,
 					void *user_data)
 {
-	struct iovec iov;
-	struct bt_ascs_release rel;
-	struct bt_bap_req *req;
-	struct bt_bap *bap;
-
-	if (!stream)
+	if (!stream || !stream->ops || !stream->ops->release)
 		return 0;
 
-	if (!stream->client) {
-		stream_release(stream, NULL);
-		return 0;
-	}
-
-	memset(&req, 0, sizeof(req));
-
-	rel.ase = stream->ep->id;
-
-	iov.iov_base = &rel;
-	iov.iov_len = sizeof(rel);
-
-	bap = stream->bap;
-
-	/* If stream is broadcast, no BT_ASCS_RELEASE is required */
-	if (bt_bap_stream_get_type(stream) == BT_BAP_STREAM_TYPE_BCAST) {
-		stream_set_state_broadcast(stream,
-					BT_BAP_STREAM_STATE_RELEASING);
-		return 0;
-	}
-
-	/* 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);
-		stream = NULL;
-	}
-
-	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;
+	return stream->ops->release(stream, func, user_data);
 }
 
 uint8_t bt_bap_stream_get_dir(struct bt_bap_stream *stream)
