text: /* @file
   * @brief Bluetooth Unicast Client
   */
  
  /*
   * Copyright (c) 2020 Intel Corporation
   * Copyright (c) 2022 Nordic Semiconductor ASA
   *
   * SPDX-License-Identifier: Apache-2.0
   */
  
  #include <zephyr/zephyr.h>
  #include <zephyr/sys/byteorder.h>
  
  #include <zephyr/bluetooth/bluetooth.h>
  #include <zephyr/bluetooth/conn.h>
  #include <zephyr/bluetooth/gatt.h>
  #include <zephyr/bluetooth/audio/audio.h>
  
  #include "../host/hci_core.h"
  #include "../host/conn_internal.h"
  
  #include "endpoint.h"
  #include "pacs_internal.h"
  #include "unicast_client_internal.h"
  
  #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_AUDIO_DEBUG_UNICAST_CLIENT)
  #define LOG_MODULE_NAME bt_unicast_client
  #include "common/log.h"
  
  #if defined(CONFIG_BT_AUDIO_UNICAST_CLIENT)
  
  #define PAC_DIR_UNUSED(dir) ((dir) != BT_AUDIO_DIR_SINK && (dir) != BT_AUDIO_DIR_SOURCE)
  
  #define EP_ISO(_iso) CONTAINER_OF(_iso, struct bt_audio_ep, iso)
  
  static struct unicast_client_pac {
  	enum bt_audio_dir dir;
  	uint16_t context;
  	struct bt_codec codec;
  } cache[CONFIG_BT_MAX_CONN][CONFIG_BT_AUDIO_UNICAST_CLIENT_PAC_COUNT];
  
  static const struct bt_uuid *snk_uuid = BT_UUID_PACS_SNK;
  static const struct bt_uuid *src_uuid = BT_UUID_PACS_SRC;
  static const struct bt_uuid *pacs_context_uuid = BT_UUID_PACS_SUPPORTED_CONTEXT;
  static const struct bt_uuid *ase_snk_uuid = BT_UUID_ASCS_ASE_SNK;
  static const struct bt_uuid *ase_src_uuid = BT_UUID_ASCS_ASE_SRC;
  static const struct bt_uuid *cp_uuid = BT_UUID_ASCS_ASE_CP;
  
  static struct bt_audio_ep snks[CONFIG_BT_MAX_CONN][CONFIG_BT_AUDIO_UNICAST_CLIENT_ASE_SNK_COUNT];
  static struct bt_audio_ep srcs[CONFIG_BT_MAX_CONN][CONFIG_BT_AUDIO_UNICAST_CLIENT_ASE_SRC_COUNT];
  
  static struct bt_gatt_subscribe_params cp_subscribe[CONFIG_BT_MAX_CONN];
  static struct bt_gatt_discover_params cp_disc[CONFIG_BT_MAX_CONN];
  
  
  /* TODO: Move the functions to avoid these prototypes */
  static int unicast_client_ep_set_metadata(struct bt_audio_ep *ep,
  					  struct net_buf_simple *buf,
  					  uint8_t len, struct bt_codec *codec);
  
  static int unicast_client_ep_set_codec(struct bt_audio_ep *ep, uint8_t id,
  				       uint16_t cid, uint16_t vid,
  				       struct net_buf_simple *buf,
  				       uint8_t len, struct bt_codec *codec);
  
  static void unicast_client_ep_iso_recv(struct bt_iso_chan *chan,
  				       const struct bt_iso_recv_info *info,
  				       struct net_buf *buf)
  {
  	struct bt_audio_ep *ep = EP_ISO(chan);
  	struct bt_audio_stream_ops *ops = ep->stream->ops;
  
  	BT_DBG("stream %p ep %p len %zu", chan, ep, net_buf_frags_len(buf));
  
  	if (ops != NULL && ops->recv != NULL) {
  		ops->recv(ep->stream, info, buf);
  	} else {
  		BT_WARN("No callback for recv set");
  	}
  }
  
  static void unicast_client_ep_iso_sent(struct bt_iso_chan *chan)
  {
  	struct bt_audio_ep *ep = CONTAINER_OF(chan, struct bt_audio_ep, iso);
  	struct bt_audio_stream_ops *ops = ep->stream->ops;
  
  	BT_DBG("stream %p ep %p", chan, ep);
  
  	if (ops != NULL && ops->sent != NULL) {
  		ops->sent(ep->stream);
  	}
  }
  
  static void unicast_client_ep_iso_connected(struct bt_iso_chan *chan)
  {
  	struct bt_audio_ep *ep = EP_ISO(chan);
  
  	BT_DBG("stream %p ep %p dir %u", chan, ep, ep != NULL ? ep->dir : 0);
  
  	if (ep->status.state != BT_AUDIO_EP_STATE_ENABLING) {
  		BT_DBG("endpoint not in enabling state: %s",
  		       bt_audio_ep_state_str(ep->status.state));
  		return;
  	}
  
  	bt_unicast_client_ep_set_state(ep, BT_AUDIO_EP_STATE_STREAMING);
  }
  
  static void unicast_client_ep_iso_disconnected(struct bt_iso_chan *chan,
  					       uint8_t reason)
  {
  	struct bt_audio_ep *ep = EP_ISO(chan);
  	struct bt_audio_stream *stream = ep->stream;
  	struct bt_audio_stream_ops *ops = stream->ops;
  	int err;
  
  	BT_DBG("stream %p ep %p reason 0x%02x", chan, ep, reason);
  
  	if (ops != NULL && ops->stopped != NULL) {
  		ops->stopped(stream);
  	} else {
  		BT_WARN("No callback for stopped set");
  	}
  
  	bt_unicast_client_ep_set_state(ep, BT_AUDIO_EP_STATE_QOS_CONFIGURED);
  
  	err = bt_audio_stream_iso_listen(stream);
  	if (err != 0) {
  		BT_ERR("Could not make stream listen: %d", err);
  	}
  }
  
  static struct bt_iso_chan_ops unicast_client_iso_ops = {
  	.recv		= unicast_client_ep_iso_recv,
  	.sent		= unicast_client_ep_iso_sent,
  	.connected	= unicast_client_ep_iso_connected,
  	.disconnected	= unicast_client_ep_iso_disconnected,
  };
  
  static void unicast_client_ep_init(struct bt_audio_ep *ep, uint16_t handle,
  				   enum bt_audio_dir dir)
  {
  	BT_DBG("ep %p dir 0x%02x handle 0x%04x", ep, dir, handle);
  
  	(void)memset(ep, 0, sizeof(*ep));
  	ep->handle = handle;
  	ep->status.id = 0U;
  	ep->iso.ops = &unicast_client_iso_ops;
  	ep->iso.qos = &ep->iso_qos;
  	ep->iso.qos->rx = &ep->iso_rx;
  	ep->iso.qos->tx = &ep->iso_tx;
  	ep->dir = dir;
  }
  
  static struct bt_audio_ep *unicast_client_ep_find(struct bt_conn *conn,
  						  uint16_t handle)
  {
  	size_t i;
  	uint8_t index;
  
  	index = bt_conn_index(conn);
  
  	for (i = 0; i < ARRAY_SIZE(snks[index]); i++) {
  		struct bt_audio_ep *ep = &snks[index][i];
  
  		if ((handle && ep->handle == handle) ||
  		    (!handle && ep->handle)) {
  			return ep;
  		}
  	}
  
  	for (i = 0; i < ARRAY_SIZE(srcs[index]); i++) {
  		struct bt_audio_ep *ep = &srcs[index][i];
  
  		if ((handle && ep->handle == handle) ||
  		    (!handle && ep->handle)) {
  			return ep;
  		}
  	}
  
  	return NULL;
  }
  
  static struct bt_audio_ep *unicast_client_ep_new(struct bt_conn *conn,
  						 enum bt_audio_dir dir,
  						 uint16_t handle)
  {
  	size_t i, size;
  	uint8_t index;
  	struct bt_audio_ep *cache;
  
  	index = bt_conn_index(conn);
  
  	switch (dir) {
  	case BT_AUDIO_DIR_SINK:
  		cache = snks[index];
  		size = ARRAY_SIZE(snks[index]);
  		break;
  	case BT_AUDIO_DIR_SOURCE:
  		cache = srcs[index];
  		size = ARRAY_SIZE(srcs[index]);
  		break;
  	default:
  		return NULL;
  	}
  
  	for (i = 0; i < size; i++) {
  		struct bt_audio_ep *ep = &cache[i];
  
  		if (!ep->handle) {
  			unicast_client_ep_init(ep, handle, dir);
  			return ep;
  		}
  	}
  
  	return NULL;
  }
  
  static struct bt_audio_ep *unicast_client_ep_get(struct bt_conn *conn,
  						 enum bt_audio_dir dir,
  						 uint16_t handle)
  {
  	struct bt_audio_ep *ep;
  
  	ep = unicast_client_ep_find(conn, handle);
  	if (ep || !handle) {
  		return ep;
  	}
  
  	return unicast_client_ep_new(conn, dir, handle);
  }
  
  static void unicast_client_ep_idle_state(struct bt_audio_ep *ep,
  					 struct net_buf_simple *buf)
  {
  	struct bt_audio_stream *stream = ep->stream;
  	const struct bt_audio_stream_ops *ops;
  
  	if (stream == NULL) {
  		return;
  	}
  
  	/* Notify upper layer */
  	ops = stream->ops;
  	if (ops != NULL && ops->released != NULL) {
  		ops->released(stream);
  	} else {
  		BT_WARN("No callback for released set");
  	}
  
  	bt_audio_stream_reset(stream);
  }
  
  static void unicast_client_ep_qos_reset(struct bt_audio_ep *ep)
  {
  	ep->iso.qos = memset(&ep->iso_qos, 0, sizeof(ep->iso_qos));
  	ep->iso.qos->rx = memset(&ep->iso_rx, 0, sizeof(ep->iso_rx));
  	ep->iso.qos->tx = memset(&ep->iso_tx, 0, sizeof(ep->iso_tx));
  }
  
  static void unicast_client_ep_config_state(struct bt_audio_ep *ep,
  					   struct net_buf_simple *buf)
  {
  	struct bt_ascs_ase_status_config *cfg;
  	struct bt_codec_qos_pref *pref;
  	struct bt_audio_stream *stream;
  
  	if (buf->len < sizeof(*cfg)) {
  		BT_ERR("Config status too short");
  		return;
  	}
  
  	stream = ep->stream;
  	if (stream == NULL) {
  		BT_ERR("No stream active for endpoint");
  		return;
  	}
  
  	cfg = net_buf_simple_pull_mem(buf, sizeof(*cfg));
  
  	if (stream->codec == NULL || stream->codec->id != cfg->codec.id) {
  		BT_ERR("Codec configuration mismatched");
  		/* TODO: Release the stream? */
  		return;
  	}
  
  	if (buf->len < cfg->cc_len) {
  		BT_ERR("Malformed ASE Config status: buf->len %u < %u cc_len",
  		       buf->len, cfg->cc_len);
  		return;
  	}
  
  	/* Reset any existing QoS configuration */
  	unicast_client_ep_qos_reset(ep);
  
  	pref = &stream->ep->qos_pref;
  
  	/* Convert to interval representation so they can be matched by QoS */
  	pref->unframed_supported = cfg->framing == BT_ASCS_QOS_FRAMING_UNFRAMED;
  	pref->phy = cfg->phy;
  	pref->rtn = cfg->rtn;
  	pref->latency = sys_le16_to_cpu(cfg->latency);
  	pref->pd_min = sys_get_le24(cfg->pd_min);
  	pref->pd_max = sys_get_le24(cfg->pd_max);
  	pref->pref_pd_min = sys_get_le24(cfg->prefer_pd_min);
  	pref->pref_pd_max = sys_get_le24(cfg->prefer_pd_max);
  
  	BT_DBG("dir 0x%02x unframed_supported 0x%02x phy 0x%02x rtn %u "
  	       "latency %u pd_min %u pd_max %u codec 0x%02x ",
  	       ep->dir, pref->unframed_supported, pref->phy, pref->rtn,
  	       pref->latency, pref->pd_min, pref->pd_max, stream->codec->id);
  
  	unicast_client_ep_set_codec(ep, cfg->codec.id,
  				    sys_le16_to_cpu(cfg->codec.cid),
  				    sys_le16_to_cpu(cfg->codec.vid),
  				    buf, cfg->cc_len, NULL);
  
  	/* Notify upper layer */
  	if (stream->ops != NULL && stream->ops->configured != NULL) {
  		stream->ops->configured(stream, pref);
  	} else {
  		BT_WARN("No callback for configured set");
  	}
  }
  
  static void unicast_client_ep_qos_state(struct bt_audio_ep *ep,
  					struct net_buf_simple *buf)
  {
  	struct bt_ascs_ase_status_qos *qos;
  	struct bt_audio_stream *stream;
  
  	if (buf->len < sizeof(*qos)) {
  		BT_ERR("QoS status too short");
  		return;
  	}
  
  	stream = ep->stream;
  	if (stream == NULL) {
  		BT_ERR("No stream active for endpoint");
  		return;
  	}
  
  	qos = net_buf_simple_pull_mem(buf, sizeof(*qos));
  
  	ep->cig_id = qos->cig_id;
  	ep->cis_id = qos->cis_id;
  	(void)memcpy(&stream->qos->interval, sys_le24_to_cpu(qos->interval),
  		     sizeof(qos->interval));
  	stream->qos->framing = qos->framing;
  	stream->qos->phy = qos->phy;
  	stream->qos->sdu = sys_le16_to_cpu(qos->sdu);
  	stream->qos->rtn = qos->rtn;
  	stream->qos->latency = sys_le16_to_cpu(qos->latency);
  	(void)memcpy(&stream->qos->pd, sys_le24_to_cpu(qos->pd),
  		     sizeof(qos->pd));
  
  	BT_DBG("dir 0x%02x cig 0x%02x cis 0x%02x codec 0x%02x interval %u "
  	       "framing 0x%02x phy 0x%02x rtn %u latency %u pd %u",
  	       ep->dir, ep->cig_id, ep->cis_id, stream->codec->id,
  	       stream->qos->interval, stream->qos->framing,
  	       stream->qos->phy, stream->qos->rtn, stream->qos->latency,
  	       stream->qos->pd);
  
  	/* Disconnect ISO if connected */
  	if (stream->iso->state == BT_ISO_STATE_CONNECTED) {
  		bt_audio_stream_disconnect(stream);
  	}
  
  	/* Notify upper layer */
  	if (stream->ops != NULL && stream->ops->qos_set != NULL) {
  		stream->ops->qos_set(stream);
  	} else {
  		BT_WARN("No callback for qos_set set");
  	}
  }
  
  static void unicast_client_ep_enabling_state(struct bt_audio_ep *ep,
  					     struct net_buf_simple *buf)
  {
  	struct bt_ascs_ase_status_enable *enable;
  	struct bt_audio_stream *stream;
  
  	if (buf->len < sizeof(*enable)) {
  		BT_ERR("Enabling status too short");
  		return;
  	}
  
  	stream = ep->stream;
  	if (stream == NULL) {
  		BT_ERR("No stream active for endpoint");
  		return;
  	}
  
  	enable = net_buf_simple_pull_mem(buf, sizeof(*enable));
  
  	BT_DBG("dir 0x%02x cig 0x%02x cis 0x%02x",
  	       ep->dir, ep->cig_id, ep->cis_id);
  
  	unicast_client_ep_set_metadata(ep, buf, enable->metadata_len, NULL);
  
  	/* Notify upper layer */
  	if (stream->ops != NULL && stream->ops->enabled != NULL) {
  		stream->ops->enabled(stream);
  	} else {
  		BT_WARN("No callback for enabled set");
  	}
  }
  
  static void unicast_client_ep_streaming_state(struct bt_audio_ep *ep,
  					      struct net_buf_simple *buf)
  {
  	struct bt_ascs_ase_status_stream *stream_status;
  	struct bt_audio_stream *stream;
  
  	if (buf->len < sizeof(*stream_status)) {
  		BT_ERR("Streaming status too short");
  		return;
  	}
  
  	stream = ep->stream;
  	if (stream == NULL) {
  		BT_ERR("No stream active for endpoint");
  		return;
  	}
  
  	stream_status = net_buf_simple_pull_mem(buf, sizeof(*stream_status));
  
  	BT_DBG("dir 0x%02x cig 0x%02x cis 0x%02x",
  	       ep->dir, ep->cig_id, ep->cis_id);
  
  	/* Notify upper layer */
  	if (stream->ops != NULL && stream->ops->started != NULL) {
  		stream->ops->started(stream);
  	} else {
  		BT_WARN("No callback for started set");
  	}
  }
  
  static void unicast_client_ep_disabling_state(struct bt_audio_ep *ep,
  					      struct net_buf_simple *buf)
  {
  	struct bt_ascs_ase_status_disable *disable;
  	struct bt_audio_stream *stream;
  
  	if (buf->len < sizeof(*disable)) {
  		BT_ERR("Disabling status too short");
  		return;
  	}
  
  	stream = ep->stream;
  	if (stream == NULL) {
  		BT_ERR("No stream active for endpoint");
  		return;
  	}
  
  	disable = net_buf_simple_pull_mem(buf, sizeof(*disable));
  
  	BT_DBG("dir 0x%02x cig 0x%02x cis 0x%02x",
  	       ep->dir, ep->cig_id, ep->cis_id);
  
  	/* Notify upper layer */
  	if (stream->ops != NULL && stream->ops->disabled != NULL) {
  		stream->ops->disabled(stream);
  	} else {
  		BT_WARN("No callback for disabled set");
  	}
  }
  
  static void unicast_client_ep_releasing_state(struct bt_audio_ep *ep,
  					      struct net_buf_simple *buf)
  {
  	struct bt_audio_stream *stream;
  
  	stream = ep->stream;
  	if (stream == NULL) {
  		BT_ERR("No stream active for endpoint");
  		return;
  	}
  
  	BT_DBG("dir 0x%02x", ep->dir);
  
  	/* The Unicast Client shall terminate any CIS established for that ASE
  	 * by following the Connected Isochronous Stream Terminate procedure
  	 * defined in Volume 3, Part C, Section 9.3.15 in when the Unicast
  	 * Client has determined that the ASE is in the Releasing state.
  	 */
  	bt_audio_stream_disconnect(stream);
  }
  
  static void unicast_client_ep_set_status(struct bt_audio_ep *ep,
  					 struct net_buf_simple *buf)
  {
  	struct bt_ascs_ase_status *status;
  	uint8_t old_state;
  
  	if (!ep) {
  		return;
  	}
  
  	status = net_buf_simple_pull_mem(buf, sizeof(*status));
  
  	BT_DBG("ep %p handle 0x%04x id 0x%02x state %s", ep, ep->handle,
  	       status->id, bt_audio_ep_state_str(status->state));
  
  	old_state = ep->status.state;
  	ep->status = *status;
  
  	switch (status->state) {
  	case BT_AUDIO_EP_STATE_IDLE:
  		unicast_client_ep_idle_state(ep, buf);
  		break;
  	case BT_AUDIO_EP_STATE_CODEC_CONFIGURED:
  		switch (old_state) {
  		/* Valid only if ASE_State field = 0x00 (Idle) */
  		case BT_AUDIO_EP_STATE_IDLE:
  		 /* or 0x01 (Codec Configured) */
  		case BT_AUDIO_EP_STATE_CODEC_CONFIGURED:
  		 /* or 0x02 (QoS Configured) */
  		case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
  		 /* or 0x06 (Releasing) */
  		case BT_AUDIO_EP_STATE_RELEASING:
  			break;
  		default:
  			BT_WARN("Invalid state transition: %s -> %s",
  				bt_audio_ep_state_str(old_state),
  				bt_audio_ep_state_str(ep->status.state));
  		}
  		unicast_client_ep_config_state(ep, buf);
  		break;
  	case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
  		switch (old_state) {
  		/* Valid only if ASE_State field = 0x01 (Codec Configured) */
  		case BT_AUDIO_EP_STATE_CODEC_CONFIGURED:
  		 /* or 0x02 (QoS Configured) */
  		case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
  			break;
  		default:
  			BT_WARN("Invalid state transition: %s -> %s",
  				bt_audio_ep_state_str(old_state),
  				bt_audio_ep_state_str(ep->status.state));
  		}
  		unicast_client_ep_qos_state(ep, buf);
  		break;
  	case BT_AUDIO_EP_STATE_ENABLING:
  		/* Valid only if ASE_State field = 0x02 (QoS Configured) */
  		if (old_state != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
  			BT_WARN("Invalid state transition: %s -> %s",
  				bt_audio_ep_state_str(old_state),
  				bt_audio_ep_state_str(ep->status.state));
  		}
  		unicast_client_ep_enabling_state(ep, buf);
  		break;
  	case BT_AUDIO_EP_STATE_STREAMING:
  		switch (old_state) {
  		/* Valid only if ASE_State field = 0x02 (QoS Configured) */
  		case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
  		 /* or  0x03 (Enabling)*/
  		case BT_AUDIO_EP_STATE_ENABLING:
  			break;
  		default:
  			BT_WARN("Invalid state transition: %s -> %s",
  				bt_audio_ep_state_str(old_state),
  				bt_audio_ep_state_str(ep->status.state));
  		}
  		unicast_client_ep_streaming_state(ep, buf);
  		break;
  	case BT_AUDIO_EP_STATE_DISABLING:
  		switch (old_state) {
  		/* Valid only if ASE_State field = 0x03 (Enabling) */
  		case BT_AUDIO_EP_STATE_ENABLING:
  		 /* or 0x04 (Streaming) */
  		case BT_AUDIO_EP_STATE_STREAMING:
  			break;
  		default:
  			BT_WARN("Invalid state transition: %s -> %s",
  				bt_audio_ep_state_str(old_state),
  				bt_audio_ep_state_str(ep->status.state));
  		}
  		unicast_client_ep_disabling_state(ep, buf);
  		break;
  	case BT_AUDIO_EP_STATE_RELEASING:
  		switch (old_state) {
  		/* Valid only if ASE_State field = 0x01 (Codec Configured) */
  		case BT_AUDIO_EP_STATE_CODEC_CONFIGURED:
  		 /* or 0x02 (QoS Configured) */
  		case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
  		 /* or 0x03 (Enabling) */
  		case BT_AUDIO_EP_STATE_ENABLING:
  		 /* or 0x04 (Streaming) */
  		case BT_AUDIO_EP_STATE_STREAMING:
  		 /* or 0x04 (Disabling) */
  		case BT_AUDIO_EP_STATE_DISABLING:
  			break;
  		default:
  			BT_WARN("Invalid state transition: %s -> %s",
  				bt_audio_ep_state_str(old_state),
  				bt_audio_ep_state_str(ep->status.state));
  		}
  		unicast_client_ep_releasing_state(ep, buf);
  		break;
  	}
  }
  
  static void unicast_client_codec_data_add(struct net_buf_simple *buf,
  					  const char *prefix,
  					  size_t num,
  					  struct bt_codec_data *data)
  {
  	for (size_t i = 0; i < num; i++) {
  		struct bt_data *d = &data[i].data;
  		struct bt_ascs_codec_config *cc;
  
  		BT_DBG("#%u: %s type 0x%02x len %u", i, prefix, d->type,
  		       d->data_len);
  		BT_HEXDUMP_DBG(d->data, d->data_len, prefix);
  
  		cc = net_buf_simple_add(buf, sizeof(*cc));
  		cc->len = d->data_len + sizeof(cc->type);
  		cc->type = d->type;
  		net_buf_simple_add_mem(buf, d->data, d->data_len);
  	}
  }
  
  static bool unicast_client_codec_config_store(struct bt_data *data,
  					      void *user_data)
  {
  	struct bt_codec *codec = user_data;
  	struct bt_codec_data *cdata;
  
  	if (codec->data_count >= ARRAY_SIZE(codec->data)) {
  		BT_ERR("No slot available for Codec Config");
  		return false;
  	}
  
  	cdata = &codec->data[codec->data_count];
  
  	if (data->data_len > sizeof(cdata->value)) {
  		BT_ERR("Not enough space for Codec Config: %u > %zu",
  		       data->data_len, sizeof(cdata->value));
  		return false;
  	}
  
  	BT_DBG("#%u type 0x%02x len %u", codec->data_count, data->type,
  	       data->data_len);
  
  	cdata->data.type = data->type;
  	cdata->data.data_len = data->data_len;
  
  	/* Deep copy data contents */
  	cdata->data.data = cdata->value;
  	(void)memcpy(cdata->value, data->data, data->data_len);
  
  	BT_HEXDUMP_DBG(cdata->value, data->data_len, "data");
  
  	codec->data_count++;
  
  	return true;
  }
  
  static int unicast_client_ep_set_codec(struct bt_audio_ep *ep, uint8_t id,
  				       uint16_t cid, uint16_t vid,
  				       struct net_buf_simple *buf, uint8_t len,
  				       struct bt_codec *codec)
  {
  	struct net_buf_simple ad;
  
  	if (!ep && !codec) {
  		return -EINVAL;
  	}
  
  	BT_DBG("ep %p codec id 0x%02x cid 0x%04x vid 0x%04x len %u", ep, id,
  	       cid, vid, len);
  
  	if (!codec) {
  		codec = &ep->codec;
  	}
  
  	codec->id = id;
  	codec->cid = cid;
  	codec->vid = vid;
  
  	/* Reset current metadata */
  	codec->data_count = 0;
  	(void)memset(codec->data, 0, sizeof(codec->data));
  
  	if (!len) {
  		return 0;
  	}
  
  	net_buf_simple_init_with_data(&ad, net_buf_simple_pull_mem(buf, len),
  				      len);
  
  	/* Parse LTV entries */
  	bt_data_parse(&ad, unicast_client_codec_config_store, codec);
  
  	/* Check if all entries could be parsed */
  	if (ad.len) {
  		BT_ERR("Unable to parse Codec Config: len %u", ad.len);
  		goto fail;
  	}
  
  	return 0;
  
  fail:
  	(void)memset(codec, 0, sizeof(*codec));
  	return -EINVAL;
  }
  
  static bool unicast_client_codec_metadata_store(struct bt_data *data,
  						void *user_data)
  {
  	struct bt_codec *codec = user_data;
  	struct bt_codec_data *meta;
  
  	if (codec->meta_count >= ARRAY_SIZE(codec->meta)) {
  		BT_ERR("No slot available for Codec Config Metadata");
  		return false;
  	}
  
  	meta = &codec->meta[codec->meta_count];
  
  	if (data->data_len > sizeof(meta->value)) {
  		BT_ERR("Not enough space for Codec Config Metadata: %u > %zu",
  		       data->data_len, sizeof(meta->value));
  		return false;
  	}
  
  	BT_DBG("#%u type 0x%02x len %u", codec->meta_count, data->type,
  	       data->data_len);
  
  	meta->data.type = data->type;
  	meta->data.data_len = data->data_len;
  
  	/* Deep copy data contents */
  	meta->data.data = meta->value;
  	(void)memcpy(meta->value, data->data, data->data_len);
  
  	BT_HEXDUMP_DBG(meta->value, data->data_len, "data");
  
  	codec->meta_count++;
  
  	return true;
  }
  
  static int unicast_client_ep_set_metadata(struct bt_audio_ep *ep,
  					  struct net_buf_simple *buf,
  					  uint8_t len, struct bt_codec *codec)
  {
  	struct net_buf_simple meta;
  	int err;
  
  	if (!ep && !codec) {
  		return -EINVAL;
  	}
  
  	BT_DBG("ep %p len %u codec %p", ep, len, codec);
  
  	if (!codec) {
  		codec = &ep->codec;
  	}
  
  	/* Reset current metadata */
  	codec->meta_count = 0;
  	(void)memset(codec->meta, 0, sizeof(codec->meta));
  
  	if (!len) {
  		return 0;
  	}
  
  	net_buf_simple_init_with_data(&meta, net_buf_simple_pull_mem(buf, len),
  				      len);
  
  	/* Parse LTV entries */
  	bt_data_parse(&meta, unicast_client_codec_metadata_store, codec);
  
  	/* Check if all entries could be parsed */
  	if (meta.len) {
  		BT_ERR("Unable to parse Metadata: len %u", meta.len);
  		err = -EINVAL;
  
  		if (meta.len > 2) {
  			/* Value of the Metadata Type field in error */
  			err = meta.data[2];
  		}
  
  		goto fail;
  	}
  
  	return 0;
  
  fail:
  	codec->meta_count = 0;
  	(void)memset(codec->meta, 0, sizeof(codec->meta));
  	return err;
  }
  
  void bt_unicast_client_ep_set_state(struct bt_audio_ep *ep, uint8_t state)
  {
  	uint8_t old_state;
  
  	if (!ep) {
  		return;
  	}
  
  	BT_DBG("ep %p id 0x%02x %s -> %s", ep, ep->status.id,
  	       bt_audio_ep_state_str(ep->status.state),
  	       bt_audio_ep_state_str(state));
  
  	old_state = ep->status.state;
  	ep->status.state = state;
  
  	if (!ep->stream || old_state == state) {
  		return;
  	}
  
  	if (state == BT_AUDIO_EP_STATE_IDLE) {
  		bt_audio_stream_detach(ep->stream);
  	}
  }
  
  static uint8_t unicast_client_cp_notify(struct bt_conn *conn,
  					struct bt_gatt_subscribe_params *params,
  					const void *data, uint16_t length)
  {
  	struct bt_ascs_cp_rsp *rsp;
  	int i;
  
  	struct net_buf_simple buf;
  
  	BT_DBG("conn %p len %u", conn, length);
  
  	if (!data) {
  		BT_DBG("Unsubscribed");
  		params->value_handle = 0x0000;
  		return BT_GATT_ITER_STOP;
  	}
  
  	net_buf_simple_init_with_data(&buf, (void *)data, length);
  
  	if (buf.len < sizeof(*rsp)) {
  		BT_ERR("Control Point Notification too small");
  		return BT_GATT_ITER_STOP;
  	}
  
  	rsp = net_buf_simple_pull_mem(&buf, sizeof(*rsp));
  
  	for (i = 0; i < rsp->num_ase; i++) {
  		struct bt_ascs_cp_ase_rsp *ase_rsp;
  
  		if (buf.len < sizeof(*ase_rsp)) {
  			BT_ERR("Control Point Notification too small");
  			return BT_GATT_ITER_STOP;
  		}
  
  		ase_rsp = net_buf_simple_pull_mem(&buf, sizeof(*ase_rsp));
  
  		BT_DBG("op %s (0x%02x) id 0x%02x code %s (0x%02x) "
  		       "reason %s (0x%02x)", bt_ascs_op_str(rsp->op), rsp->op,
  		       ase_rsp->id, bt_ascs_rsp_str(ase_rsp->code),
  		       ase_rsp->code, bt_ascs_reason_str(ase_rsp->reason),
  		       ase_rsp->reason);
  	}
  
  	return BT_GATT_ITER_CONTINUE;
  }
  
  static uint8_t unicast_client_ep_notify(struct bt_conn *conn,
  					struct bt_gatt_subscribe_params *params,
  					const void *data, uint16_t length)
  {
  	struct net_buf_simple buf;
  	struct bt_audio_ep *ep;
  
  	ep = CONTAINER_OF(params, struct bt_audio_ep, subscribe);
  
  	BT_DBG("conn %p ep %p len %u", conn, ep, length);
  
  	if (!data) {
  		BT_DBG("Unsubscribed");
  		params->value_handle = 0x0000;
  		return BT_GATT_ITER_STOP;
  	}
  
  	net_buf_simple_init_with_data(&buf, (void *)data, length);
  
  	if (buf.len < sizeof(struct bt_ascs_ase_status)) {
  		BT_ERR("Notification too small");
  		return BT_GATT_ITER_STOP;
  	}
  
  	unicast_client_ep_set_status(ep, &buf);
  
  	return BT_GATT_ITER_CONTINUE;
  }
  
  static int unicast_client_ep_subscribe(struct bt_conn *conn,
  				       struct bt_audio_ep *ep)
  {
  	BT_DBG("ep %p handle 0x%02x", ep, ep->handle);
  
  	if (ep->subscribe.value_handle) {
  		return 0;
  	}
  
  	ep->subscribe.value_handle = ep->handle;
  	ep->subscribe.ccc_handle = 0x0000;
  	ep->subscribe.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
  	ep->subscribe.disc_params = &ep->discover;
  	ep->subscribe.notify = unicast_client_ep_notify;
  	ep->subscribe.value = BT_GATT_CCC_NOTIFY;
  	atomic_set_bit(ep->subscribe.flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE);
  
  	return bt_gatt_subscribe(conn, &ep->subscribe);
  }
  
  static void unicast_client_ep_set_cp(struct bt_conn *conn, uint16_t handle)
  {
  	size_t i;
  	uint8_t index;
  
  	BT_DBG("conn %p 0x%04x", conn, handle);
  
  	index = bt_conn_index(conn);
  
  	if (!cp_subscribe[index].value_handle) {
  		cp_subscribe[index].value_handle = handle;
  		cp_subscribe[index].ccc_handle = 0x0000;
  		cp_subscribe[index].end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
  		cp_subscribe[index].disc_params = &cp_disc[index];
  		cp_subscribe[index].notify = unicast_client_cp_notify;
  		cp_subscribe[index].value = BT_GATT_CCC_NOTIFY;
  		atomic_set_bit(cp_subscribe[index].flags,
  			       BT_GATT_SUBSCRIBE_FLAG_VOLATILE);
  
  		bt_gatt_subscribe(conn, &cp_subscribe[index]);
  	}
  
  	for (i = 0; i < ARRAY_SIZE(snks[index]); i++) {
  		struct bt_audio_ep *ep = &snks[index][i];
  
  		if (ep->handle) {
  			ep->cp_handle = handle;
  		}
  	}
  
  	for (i = 0; i < ARRAY_SIZE(srcs[index]); i++) {
  		struct bt_audio_ep *ep = &srcs[index][i];
  
  		if (ep->handle) {
  			ep->cp_handle = handle;
  		}
  	}
  }
  
  NET_BUF_SIMPLE_DEFINE_STATIC(ep_buf, CONFIG_BT_L2CAP_TX_MTU);
  
  struct net_buf_simple *bt_unicast_client_ep_create_pdu(uint8_t op)
  {
  	struct bt_ascs_ase_cp *hdr;
  
  	/* Reset buffer before using */
  	net_buf_simple_reset(&ep_buf);
  
  	hdr = net_buf_simple_add(&ep_buf, sizeof(*hdr));
  	hdr->op = op;
  
  	return &ep_buf;
  }
  
  static int unicast_client_ep_config(struct bt_audio_ep *ep,
  				    struct net_buf_simple *buf,
  				    struct bt_codec *codec)
  {
  	struct bt_ascs_config *req;
  	uint8_t cc_len;
  
  	BT_DBG("ep %p buf %p codec %p", ep, buf, codec);
  
  	if (!ep) {
  		return -EINVAL;
  	}
  
  	switch (ep->status.state) {
  	/* Valid only if ASE_State field = 0x00 (Idle) */
  	case BT_AUDIO_EP_STATE_IDLE:
  	 /* or 0x01 (Codec Configured) */
  	case BT_AUDIO_EP_STATE_CODEC_CONFIGURED:
  	 /* or 0x02 (QoS Configured) */
  	case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
  		break;
  	default:
  		BT_ERR("Invalid state: %s",
  		       bt_audio_ep_state_str(ep->status.state));
  		return -EINVAL;
  	}
  
  	BT_DBG("id 0x%02x dir 0x%02x codec 0x%02x", ep->status.id,
  	       ep->dir, codec->id);
  
  	req = net_buf_simple_add(buf, sizeof(*req));
  	req->ase = ep->status.id;
  	req->latency = 0x02; /* TODO: Select target latency based on additional input? */
  	req->phy = 0x02; /* TODO: Select target PHY based on additional input? */
  	req->codec.id = codec->id;
  	req->codec.cid = codec->cid;
  	req->codec.vid = codec->vid;
  
  	cc_len = buf->len;
  	unicast_client_codec_data_add(buf, "data", codec->data_count,
  				      codec->data);
  	req->cc_len = buf->len - cc_len;
  
  	return 0;
  }
  
  int bt_unicast_client_ep_qos(struct bt_audio_ep *ep, struct net_buf_simple *buf,
  			     struct bt_codec_qos *qos)
  {
  	struct bt_ascs_qos *req;
  
  	BT_DBG("ep %p buf %p qos %p", ep, buf, qos);
  
  	if (!ep) {
  		return -EINVAL;
  	}
  
  	switch (ep->status.state) {
  	/* Valid only if ASE_State field = 0x01 (Codec Configured) */
  	case BT_AUDIO_EP_STATE_CODEC_CONFIGURED:
  	 /* or 0x02 (QoS Configured) */
  	case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
  		break;
  	default:
  		BT_ERR("Invalid state: %s",
  		       bt_audio_ep_state_str(ep->status.state));
  		return -EINVAL;
  	}
  
  	BT_DBG("id 0x%02x cig 0x%02x cis 0x%02x interval %u framing 0x%02x "
  	       "phy 0x%02x sdu %u rtn %u latency %u pd %u", ep->status.id,
  	       ep->iso.iso->iso.cig_id, ep->iso.iso->iso.cis_id,
  	       qos->interval, qos->framing, qos->phy, qos->sdu,
  	       qos->rtn, qos->latency, qos->pd);
  
  	req = net_buf_simple_add(buf, sizeof(*req));
  	req->ase = ep->status.id;
  	/* TODO: don't hardcode CIG and CIS, they should come from ISO */
  	req->cig = ep->iso.iso->iso.cig_id;
  	req->cis = ep->iso.iso->iso.cis_id;
  	sys_put_le24(qos->interval, req->interval);
  	req->framing = qos->framing;
  	req->phy = qos->phy;
  	req->sdu = qos->sdu;
  	req->rtn = qos->rtn;
  	req->latency = sys_cpu_to_le16(qos->latency);
  	sys_put_le24(qos->pd, req->pd);
  
  	return 0;
  }
  
  static int unicast_client_ep_enable(struct bt_audio_ep *ep,
  				    struct net_buf_simple *buf,
  				    struct bt_codec_data *meta,
  				    size_t meta_count)
  {
  	struct bt_ascs_metadata *req;
  
  	BT_DBG("ep %p buf %p metadata count %zu", ep, buf, meta_count);
  
  	if (!ep) {
  		return -EINVAL;
  	}
  
  	if (ep->status.state != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
  		BT_ERR("Invalid state: %s",
  		       bt_audio_ep_state_str(ep->status.state));
  		return -EINVAL;
  	}
  
  	BT_DBG("id 0x%02x", ep->status.id);
  
  	req = net_buf_simple_add(buf, sizeof(*req));
  	req->ase = ep->status.id;
  
  	req->len = buf->len;
  	unicast_client_codec_data_add(buf, "meta", meta_count, meta);
  	req->len = buf->len - req->len;
  
  	return 0;
  }
  
  static int unicast_client_ep_metadata(struct bt_audio_ep *ep,
  				      struct net_buf_simple *buf,
  				      struct bt_codec_data *meta,
  				      size_t meta_count)
  {
  	struct bt_ascs_metadata *req;
  
  	BT_DBG("ep %p buf %p metadata count %zu", ep, buf, meta_count);
  
  	if (!ep) {
  		return -EINVAL;
  	}
  
  	switch (ep->status.state) {
  	/* Valid for an ASE only if ASE_State field = 0x03 (Enabling) */
  	case BT_AUDIO_EP_STATE_ENABLING:
  	/* or 0x04 (Streaming) */
  	case BT_AUDIO_EP_STATE_STREAMING:
  		break;
  	default:
  		BT_ERR("Invalid state: %s",
  		       bt_audio_ep_state_str(ep->status.state));
  		return -EINVAL;
  	}
  
  	BT_DBG("id 0x%02x", ep->status.id);
  
  	req = net_buf_simple_add(buf, sizeof(*req));
  	req->ase = ep->status.id;
  
  	req->len = buf->len;
  	unicast_client_codec_data_add(buf, "meta", meta_count, meta);
  	req->len = buf->len - req->len;
  
  	return 0;
  }
  
  static int unicast_client_ep_start(struct bt_audio_ep *ep,
  				   struct net_buf_simple *buf)
  {
  	BT_DBG("ep %p buf %p", ep, buf);
  
  	if (!ep) {
  		return -EINVAL;
  	}
  
  	if (ep->status.state != BT_AUDIO_EP_STATE_ENABLING &&
  	    ep->status.state != BT_AUDIO_EP_STATE_DISABLING) {
  		BT_ERR("Invalid state: %s",
  		       bt_audio_ep_state_str(ep->status.state));
  		return -EINVAL;
  	}
  
  	BT_DBG("id 0x%02x", ep->status.id);
  
  	net_buf_simple_add_u8(buf, ep->status.id);
  
  	return 0;
  }
  
  static int unicast_client_ep_disable(struct bt_audio_ep *ep,
  				     struct net_buf_simple *buf)
  {
  	BT_DBG("ep %p buf %p", ep, buf);
  
  	if (!ep) {
  		return -EINVAL;
  	}
  
  	switch (ep->status.state) {
  	/* Valid only if ASE_State field = 0x03 (Enabling) */
  	case BT_AUDIO_EP_STATE_ENABLING:
  	 /* or 0x04 (Streaming) */
  	case BT_AUDIO_EP_STATE_STREAMING:
  		break;
  	default:
  		BT_ERR("Invalid state: %s",
  		       bt_audio_ep_state_str(ep->status.state));
  		return -EINVAL;
  	}
  
  	BT_DBG("id 0x%02x", ep->status.id);
  
  	net_buf_simple_add_u8(buf, ep->status.id);
  
  	return 0;
  }
  
  static int unicast_client_ep_stop(struct bt_audio_ep *ep,
  				  struct net_buf_simple *buf)
  {
  	BT_DBG("ep %p buf %p", ep, buf);
  
  	if (!ep) {
  		return -EINVAL;
  	}
  
  	/* Valid only if ASE_State field value = 0x05 (Disabling). */
  	if (ep->status.state != BT_AUDIO_EP_STATE_DISABLING) {
  		BT_ERR("Invalid state: %s",
  		       bt_audio_ep_state_str(ep->status.state));
  		return -EINVAL;
  	}
  
  	BT_DBG("id 0x%02x", ep->status.id);
  
  	net_buf_simple_add_u8(buf, ep->status.id);
  
  	return 0;
  }
  
  static int unicast_client_ep_release(struct bt_audio_ep *ep,
  				     struct net_buf_simple *buf)
  {
  	BT_DBG("ep %p buf %p", ep, buf);
  
  	if (!ep) {
  		return -EINVAL;
  	}
  
  	switch (ep->status.state) {
  	/* Valid only if ASE_State field = 0x01 (Codec Configured) */
  	case BT_AUDIO_EP_STATE_CODEC_CONFIGURED:
  	 /* or 0x02 (QoS Configured) */
  	case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
  	 /* or 0x03 (Enabling) */
  	case BT_AUDIO_EP_STATE_ENABLING:
  	 /* or 0x04 (Streaming) */
  	case BT_AUDIO_EP_STATE_STREAMING:
  	 /* or 0x05 (Disabling) */
  	case BT_AUDIO_EP_STATE_DISABLING:
  		break;
  	default:
  		BT_ERR("Invalid state: %s",
  		       bt_audio_ep_state_str(ep->status.state));
  		return -EINVAL;
  	}
  
  	BT_DBG("id 0x%02x", ep->status.id);
  
  	net_buf_simple_add_u8(buf, ep->status.id);
  
  	return 0;
  }
  
  int bt_unicast_client_ep_send(struct bt_conn *conn, struct bt_audio_ep *ep,
  			      struct net_buf_simple *buf)
  {
  	BT_DBG("conn %p ep %p buf %p len %u", conn, ep, buf, buf->len);
  
  	return bt_gatt_write_without_response(conn, ep->cp_handle,
  					      buf->data, buf->len, false);
  }
  
  static void unicast_client_reset(struct bt_audio_ep *ep)
  {
  	BT_DBG("ep %p", ep);
  
  	bt_audio_stream_reset(ep->stream);
  	(void)memset(ep, 0, offsetof(struct bt_audio_ep, subscribe));
  }
  
  static void unicast_client_ep_reset(struct bt_conn *conn)
  {
  	size_t i;
  	uint8_t index;
  
  	BT_DBG("conn %p", conn);
  
  	index = bt_conn_index(conn);
  
  	for (i = 0; i < ARRAY_SIZE(snks[index]); i++) {
  		struct bt_audio_ep *ep = &snks[index][i];
  
  		unicast_client_reset(ep);
  	}
  
  	for (i = 0; i < ARRAY_SIZE(srcs[index]); i++) {
  		struct bt_audio_ep *ep = &srcs[index][i];
  
  		unicast_client_reset(ep);
  	}
  }
  
  void bt_unicast_client_ep_attach(struct bt_audio_ep *ep,
  				 struct bt_audio_stream *stream)
  {
  	BT_DBG("ep %p stream %p", ep, stream);
  
  	if (ep == NULL || stream == NULL || ep->stream == stream) {
  		return;
  	}
  
  	if (ep->stream) {
  		BT_ERR("ep %p attached to another stream %p", ep, ep->stream);
  		return;
  	}
  
  	ep->stream = stream;
  	stream->ep = ep;
  
  	if (stream->iso == NULL) {
  		stream->iso = &ep->iso;
  	}
  }
  
  void bt_unicast_client_ep_detach(struct bt_audio_ep *ep,
  				 struct bt_audio_stream *stream)
  {
  	BT_DBG("ep %p stream %p", ep, stream);
  
  	if (ep == NULL || stream == NULL || !ep->stream) {
  		return;
  	}
  
  	if (ep->stream != stream) {
  		BT_ERR("ep %p attached to another stream %p", ep, ep->stream);
  		return;
  	}
  
  	ep->stream = NULL;
  	stream->ep = NULL;
  }
  
  int bt_unicast_client_config(struct bt_audio_stream *stream,
  			     struct bt_codec *codec)
  {
  	struct bt_audio_ep *ep = stream->ep;
  	struct bt_ascs_config_op *op;
  	struct net_buf_simple *buf;
  	int err;
  
  	buf = bt_unicast_client_ep_create_pdu(BT_ASCS_CONFIG_OP);
  
  	op = net_buf_simple_add(buf, sizeof(*op));
  	op->num_ases = 0x01;
  
  	err = unicast_client_ep_config(ep, buf, codec);
  	if (err) {
  		return err;
  	}
  
  	err = bt_unicast_client_ep_send(stream->conn, ep, buf);
  	if (err) {
  		return err;
  	}
  
  	return 0;
  }
  
  int bt_unicast_client_enable(struct bt_audio_stream *stream,
  			     struct bt_codec_data *meta,
  			     size_t meta_count)
  {
  	struct bt_audio_ep *ep = stream->ep;
  	struct net_buf_simple *buf;
  	struct bt_ascs_enable_op *req;
  	int err;
  
  	BT_DBG("stream %p", stream);
  
  	buf = bt_unicast_client_ep_create_pdu(BT_ASCS_ENABLE_OP);
  
  	req = net_buf_simple_add(buf, sizeof(*req));
  	req->num_ases = 0x01;
  
  	err = unicast_client_ep_enable(ep, buf, meta, meta_count);
  	if (err) {
  		return err;
  	}
  
  	return bt_unicast_client_ep_send(stream->conn, ep, buf);
  }
  
  int bt_unicast_client_metadata(struct bt_audio_stream *stream,
  			       struct bt_codec_data *meta,
  			       size_t meta_count)
  {
  	struct bt_audio_ep *ep = stream->ep;
  	struct net_buf_simple *buf;
  	struct bt_ascs_enable_op *req;
  	int err;
  
  	BT_DBG("stream %p", stream);
  
  	buf = bt_unicast_client_ep_create_pdu(BT_ASCS_METADATA_OP);
  
  	req = net_buf_simple_add(buf, sizeof(*req));
  	req->num_ases = 0x01;
  
  	err = unicast_client_ep_metadata(ep, buf, meta, meta_count);
  	if (err) {
  		return err;
  	}
  
  	return bt_unicast_client_ep_send(stream->conn, ep, buf);
  }
  
  int bt_unicast_client_start(struct bt_audio_stream *stream)
  {
  	struct bt_audio_ep *ep = stream->ep;
  	struct net_buf_simple *buf;
  	struct bt_ascs_start_op *req;
  	int err;
  
  	BT_DBG("stream %p", stream);
  
  	buf = bt_unicast_client_ep_create_pdu(BT_ASCS_START_OP);
  
  	req = net_buf_simple_add(buf, sizeof(*req));
  	req->num_ases = 0x00;
  
  	/* If an ASE is in the Enabling state, and if the Unicast Client has
  	 * not yet established a CIS for that ASE, the Unicast Client shall
  	 * attempt to establish a CIS by using the Connected Isochronous Stream
  	 * Central Establishment procedure.
  	 */
  	err = bt_audio_stream_connect(stream);
  	if (!err) {
  		return 0;
  	}
  
  	if (err && err != -EALREADY) {
  		BT_ERR("bt_audio_stream_connect: %d", err);
  		return err;
  	}
  
  	/* When initiated by the client, valid only if Direction field
  	 * parameter value = 0x02 (Server is Audio Source)
  	 */
  	if (ep->dir == BT_AUDIO_DIR_SOURCE) {
  		err = unicast_client_ep_start(ep, buf);
  		if (err) {
  			return err;
  		}
  		req->num_ases++;
  
  		return bt_unicast_client_ep_send(stream->conn, ep, buf);
  	}
  
  	return 0;
  }
  
  int bt_unicast_client_disable(struct bt_audio_stream *stream)
  {
  	struct bt_audio_ep *ep = stream->ep;
  	struct net_buf_simple *buf;
  	struct bt_ascs_disable_op *req;
  	int err;
  
  	BT_DBG("stream %p", stream);
  
  	buf = bt_unicast_client_ep_create_pdu(BT_ASCS_DISABLE_OP);
  
  	req = net_buf_simple_add(buf, sizeof(*req));
  	req->num_ases = 0x01;
  
  	err = unicast_client_ep_disable(ep, buf);
  	if (err) {
  		return err;
  	}
  
  	return bt_unicast_client_ep_send(stream->conn, ep, buf);
  }
  
  int bt_unicast_client_stop(struct bt_audio_stream *stream)
  {
  	struct bt_audio_ep *ep = stream->ep;
  	struct net_buf_simple *buf;
  	struct bt_ascs_start_op *req;
  	int err;
  
  	BT_DBG("stream %p", stream);
  
  	buf = bt_unicast_client_ep_create_pdu(BT_ASCS_STOP_OP);
  
  	req = net_buf_simple_add(buf, sizeof(*req));
  	req->num_ases = 0x00;
  
  	/* When initiated by the client, valid only if Direction field
  	 * parameter value = 0x02 (Server is Audio Source)
  	 */
  	if (ep->dir == BT_AUDIO_DIR_SOURCE) {
  		err = unicast_client_ep_stop(ep, buf);
  		if (err) {
  			return err;
  		}
  		req->num_ases++;
  
  		return bt_unicast_client_ep_send(stream->conn, ep, buf);
  	}
  
  	return 0;
  }
  
  int bt_unicast_client_release(struct bt_audio_stream *stream)
  {
  	struct bt_audio_ep *ep = stream->ep;
  	struct net_buf_simple *buf;
  	struct bt_ascs_disable_op *req;
  	int err, len;
  
  	BT_DBG("stream %p", stream);
  
  	if (stream->conn == NULL || stream->conn->state != BT_CONN_CONNECTED) {
  		return -ENOTCONN;
  	}
  
  	buf = bt_unicast_client_ep_create_pdu(BT_ASCS_RELEASE_OP);
  
  	req = net_buf_simple_add(buf, sizeof(*req));
  	req->num_ases = 0x01;
  	len = buf->len;
  
  	/* Only attempt to release if not IDLE already */
  	if (stream->ep->status.state == BT_AUDIO_EP_STATE_IDLE) {
  		bt_audio_stream_reset(stream);
  	} else {
  		err = unicast_client_ep_release(ep, buf);
  		if (err) {
  			return err;
  		}
  	}
  
  	/* Check if anything needs to be send */
  	if (len == buf->len) {
  		return 0;
  	}
  
  	return bt_unicast_client_ep_send(stream->conn, ep, buf);
  }
  
  static uint8_t unicast_client_cp_discover_func(struct bt_conn *conn,
  					       const struct bt_gatt_attr *attr,
  					       struct bt_gatt_discover_params *discover)
  {
  	struct bt_audio_discover_params *params;
  	struct bt_gatt_chrc *chrc;
  
  	params = CONTAINER_OF(discover, struct bt_audio_discover_params,
  			      discover);
  
  	if (!attr) {
  		if (params->err) {
  			BT_ERR("Unable to find ASE Control Point");
  		}
  		params->func(conn, NULL, NULL, params);
  		return BT_GATT_ITER_STOP;
  	}
  
  	chrc = attr->user_data;
  
  	BT_DBG("conn %p attr %p handle 0x%04x", conn, attr, chrc->value_handle);
  
  	params->err = 0;
  	unicast_client_ep_set_cp(conn, chrc->value_handle);
  
  	params->func(conn, NULL, NULL, params);
  
  	return BT_GATT_ITER_STOP;
  }
  
  static int unicast_client_ase_cp_discover(struct bt_conn *conn,
  					  struct bt_audio_discover_params *params)
  {
  	BT_DBG("conn %p params %p", conn, params);
  
  	params->err = BT_ATT_ERR_ATTRIBUTE_NOT_FOUND;
  	params->discover.uuid = cp_uuid;
  	params->discover.func = unicast_client_cp_discover_func;
  	params->discover.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
  	params->discover.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
  	params->discover.type = BT_GATT_DISCOVER_CHARACTERISTIC;
  
  	return bt_gatt_discover(conn, &params->discover);
  }
  
  static uint8_t unicast_client_ase_read_func(struct bt_conn *conn, uint8_t err,
  					    struct bt_gatt_read_params *read,
  					    const void *data, uint16_t length)
  {
  	struct bt_audio_discover_params *params;
  	struct net_buf_simple buf;
  	struct bt_audio_ep *ep;
  
  	params = CONTAINER_OF(read, struct bt_audio_discover_params, read);
  
  	BT_DBG("conn %p err 0x%02x len %u", conn, err, length);
  
  	if (err) {
  		if (err == BT_ATT_ERR_ATTRIBUTE_NOT_FOUND && params->num_eps) {
  			if (unicast_client_ase_cp_discover(conn, params) < 0) {
  				BT_ERR("Unable to discover ASE Control Point");
  				err = BT_ATT_ERR_UNLIKELY;
  				goto fail;
  			}
  			return BT_GATT_ITER_STOP;
  		}
  		params->err = err;
  		params->func(conn, NULL, NULL, params);
  		return BT_GATT_ITER_STOP;
  	}
  
  	if (!data) {
  		if (params->num_eps &&
  		    unicast_client_ase_cp_discover(conn, params) < 0) {
  			BT_ERR("Unable to discover ASE Control Point");
  			err = BT_ATT_ERR_UNLIKELY;
  			goto fail;
  		}
  		return BT_GATT_ITER_STOP;
  	}
  
  	BT_DBG("handle 0x%04x", read->by_uuid.start_handle);
  
  	net_buf_simple_init_with_data(&buf, (void *)data, length);
  
  	if (buf.len < sizeof(struct bt_ascs_ase_status)) {
  		BT_ERR("Read response too small");
  		goto fail;
  	}
  
  	ep = unicast_client_ep_get(conn, params->dir,
  				   read->by_uuid.start_handle);
  	if (!ep) {
  		BT_WARN("No space left to parse ASE");
  		if (params->num_eps) {
  			if (unicast_client_ase_cp_discover(conn, params) < 0) {
  				BT_ERR("Unable to discover ASE Control Point");
  				err = BT_ATT_ERR_UNLIKELY;
  				goto fail;
  			}
  			return BT_GATT_ITER_STOP;
  		}
  		goto fail;
  	}
  
  	unicast_client_ep_set_status(ep, &buf);
  	unicast_client_ep_subscribe(conn, ep);
  
  	params->func(conn, NULL, ep, params);
  
  	params->num_eps++;
  
  	return BT_GATT_ITER_CONTINUE;
  
  fail:
  	params->func(conn, NULL, NULL, params);
  	return BT_GATT_ITER_STOP;
  }
  
  static int unicast_client_ase_discover(struct bt_conn *conn,
  				       struct bt_audio_discover_params *params)
  {
  	BT_DBG("conn %p params %p", conn, params);
  
  	params->read.func = unicast_client_ase_read_func;
  	params->read.handle_count = 0u;
  
  	if (params->dir == BT_AUDIO_DIR_SINK) {
  		params->read.by_uuid.uuid = ase_snk_uuid;
  	} else if (params->dir == BT_AUDIO_DIR_SOURCE) {
  		params->read.by_uuid.uuid = ase_src_uuid;
  	} else {
  		return -EINVAL;
  	}
  
  	params->read.by_uuid.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
  	params->read.by_uuid.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
  
  	return bt_gatt_read(conn, &params->read);
  }
  
  static uint8_t unicast_client_pacs_context_read_func(struct bt_conn *conn,
  						     uint8_t err,
  						     struct bt_gatt_read_params *read,
  						     const void *data,
  						     uint16_t length)
  {
  	struct bt_audio_discover_params *params;
  	struct net_buf_simple buf;
  	struct bt_pacs_context *context;
  	int i, index;
  
  	params = CONTAINER_OF(read, struct bt_audio_discover_params, read);
  
  	BT_DBG("conn %p err 0x%02x len %u", conn, err, length);
  
  	if (err || length < sizeof(uint16_t) * 2) {
  		goto discover_ase;
  	}
  
  	net_buf_simple_init_with_data(&buf, (void *)data, length);
  	context = net_buf_simple_pull_mem(&buf, sizeof(*context));
  
  	index = bt_conn_index(conn);
  
  	for (i = 0; i < CONFIG_BT_AUDIO_UNICAST_CLIENT_PAC_COUNT; i++) {
  		struct unicast_client_pac *pac = &cache[index][i];
  
  		if (PAC_DIR_UNUSED(pac->dir)) {
  			continue;
  		}
  
  		switch (pac->dir) {
  		case BT_AUDIO_DIR_SINK:
  			pac->context = sys_le16_to_cpu(context->snk);
  			break;
  		case BT_AUDIO_DIR_SOURCE:
  			pac->context = sys_le16_to_cpu(context->src);
  			break;
  		default:
  			BT_WARN("Cached pac with invalid dir: %u",
  				pac->dir);
  		}
  
  		BT_DBG("pac %p context 0x%04x", pac, pac->context);
  	}
  
  discover_ase:
  	/* Read ASE instances */
  	if (unicast_client_ase_discover(conn, params) < 0) {
  		BT_ERR("Unable to read ASE");
  
  		params->func(conn, NULL, NULL, params);
  	}
  
  	return BT_GATT_ITER_STOP;
  }
  
  static int unicast_client_pacs_context_discover(struct bt_conn *conn,
  						struct bt_audio_discover_params *params)
  {
  	BT_DBG("conn %p params %p", conn, params);
  
  	params->read.func = unicast_client_pacs_context_read_func;
  	params->read.handle_count = 0u;
  	params->read.by_uuid.uuid = pacs_context_uuid;
  	params->read.by_uuid.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
  	params->read.by_uuid.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
  
  	return bt_gatt_read(conn, &params->read);
  }
  
  static struct unicast_client_pac *unicast_client_pac_alloc(struct bt_conn *conn,
  							   enum bt_audio_dir dir)
  {
  	int i, index;
  
  	index = bt_conn_index(conn);
  
  	for (i = 0; i < CONFIG_BT_AUDIO_UNICAST_CLIENT_PAC_COUNT; i++) {
  		struct unicast_client_pac *pac = &cache[index][i];
  
  		if (PAC_DIR_UNUSED(pac->dir)) {
  			pac->dir = dir;
  			return pac;
  		}
  	}
  
  	return NULL;
  }
  
  static uint8_t unicast_client_read_func(struct bt_conn *conn, uint8_t err,
  					struct bt_gatt_read_params *read,
  					const void *data, uint16_t length)
  {
  	struct bt_audio_discover_params *params;
  	struct net_buf_simple buf;
  	struct bt_pacs_read_rsp *rsp;
  
  	params = CONTAINER_OF(read, struct bt_audio_discover_params, read);
  
  	BT_DBG("conn %p err 0x%02x len %u", conn, err, length);
  
  	if (err || !data) {
  		params->err = err;
  		goto fail;
  	}
  
  	BT_DBG("handle 0x%04x", read->by_uuid.start_handle);
  
  	net_buf_simple_init_with_data(&buf, (void *)data, length);
  
  	if (buf.len < sizeof(*rsp)) {
  		BT_ERR("Read response too small");
  		goto fail;
  	}
  
  	rsp = net_buf_simple_pull_mem(&buf, sizeof(*rsp));
  
  	/* If no PAC was found don't bother discovering ASE and ASE CP */
  	if (!rsp->num_pac) {
  		goto fail;
  	}
  
  	while (rsp->num_pac) {
  		struct unicast_client_pac *bpac;
  		struct bt_pac *pac;
  		struct bt_pac_meta *meta;
  
  		if (buf.len < sizeof(*pac)) {
  			BT_ERR("Malformed PAC: remaining len %u expected %zu",
  			       buf.len, sizeof(*pac));
  			break;
  		}
  
  		pac = net_buf_simple_pull_mem(&buf, sizeof(*pac));
  
  		BT_DBG("pac #%u: cc len %u", params->num_caps, pac->cc_len);
  
  		if (buf.len < pac->cc_len) {
  			BT_ERR("Malformed PAC: buf->len %u < %u pac->cc_len",
  			       buf.len, pac->cc_len);
  			break;
  		}
  
  		bpac = unicast_client_pac_alloc(conn, params->dir);
  		if (!bpac) {
  			BT_WARN("No space left to parse PAC");
  			break;
  		}
  
  		bpac->codec.id = pac->codec.id;
  		bpac->codec.cid = sys_le16_to_cpu(pac->codec.cid);
  		bpac->codec.vid = sys_le16_to_cpu(pac->codec.vid);
  
  		if (unicast_client_ep_set_codec(NULL, pac->codec.id,
  						sys_le16_to_cpu(pac->codec.cid),
  						sys_le16_to_cpu(pac->codec.vid),
  						&buf, pac->cc_len,
  						&bpac->codec)) {
  			BT_ERR("Unable to parse Codec");
  			break;
  		}
  
  		meta = net_buf_simple_pull_mem(&buf, sizeof(*meta));
  
  		if (buf.len < meta->len) {
  			BT_ERR("Malformed PAC: remaining len %u expected %u",
  			       buf.len, meta->len);
  			break;
  		}
  
  		if (unicast_client_ep_set_metadata(NULL, &buf, meta->len,
  						   &bpac->codec)) {
  			BT_ERR("Unable to parse Codec Metadata");
  			break;
  		}
  
  		BT_DBG("pac %p codec 0x%02x config count %u meta count %u ",
  		       pac, bpac->codec.id, bpac->codec.data_count,
  		       bpac->codec.meta_count);
  
  		params->func(conn, &bpac->codec, NULL, params);
  
  		rsp->num_pac--;
  		params->num_caps++;
  	}
  
  	if (!params->num_caps) {
  		goto fail;
  	}
  
  	/* Read PACS contexts */
  	if (unicast_client_pacs_context_discover(conn, params) < 0) {
  		BT_ERR("Unable to read PACS context");
  		goto fail;
  	}
  
  	return BT_GATT_ITER_STOP;
  
  fail:
  	params->func(conn, NULL, NULL, params);
  	return BT_GATT_ITER_STOP;
  }
  
  static void unicast_client_pac_reset(struct bt_conn *conn)
  {
  	int index = bt_conn_index(conn);
  	int i;
  
  	for (i = 0; i < CONFIG_BT_AUDIO_UNICAST_CLIENT_PAC_COUNT; i++) {
  		struct unicast_client_pac *pac = &cache[index][i];
  
  		if (!PAC_DIR_UNUSED(pac->dir)) {
  			(void)memset(pac, 0, sizeof(*pac));
  		}
  	}
  }
  
  static void unicast_client_disconnected(struct bt_conn *conn, uint8_t reason)
  {
  	BT_DBG("conn %p reason 0x%02x", conn, reason);
  
  	unicast_client_ep_reset(conn);
  	unicast_client_pac_reset(conn);
  }
  
  static struct bt_conn_cb conn_cbs = {
  	.disconnected = unicast_client_disconnected,
  };
  
  int bt_audio_discover(struct bt_conn *conn,
  		      struct bt_audio_discover_params *params)
  {
  	static bool conn_cb_registered;
  	uint8_t role;
  
  	if (!conn || conn->state != BT_CONN_CONNECTED) {
  		return -ENOTCONN;
  	}
  
  	role = conn->role;
  	if (role != BT_HCI_ROLE_CENTRAL) {
  		BT_DBG("Invalid conn role: %u, shall be central", role);
  		return -EINVAL;
  	}
  
  	if (params->dir == BT_AUDIO_DIR_SINK) {
  		params->read.by_uuid.uuid = snk_uuid;
  	} else if (params->dir == BT_AUDIO_DIR_SOURCE) {
  		params->read.by_uuid.uuid = src_uuid;
  	} else {
  		return -EINVAL;
  	}
  
  	params->read.func = unicast_client_read_func;
  	params->read.handle_count = 0u;
  
  	if (!params->read.by_uuid.start_handle) {
  		params->read.by_uuid.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
  	}
  
  	if (!params->read.by_uuid.end_handle) {
  		params->read.by_uuid.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
  	}
  
  	if (!conn_cb_registered) {
  		bt_conn_cb_register(&conn_cbs);
  		conn_cb_registered = true;
  	}
  
  	params->num_caps = 0u;
  	params->num_eps = 0u;
  
  	return bt_gatt_read(conn, &params->read);
  }
  
  #endif /* CONFIG_BT_AUDIO_UNICAST_CLIENT */
  