zephyr/subsys/bluetooth/audio/ascs.c
Fredrik Danebjer c9da274eb2 Bluetooth: ascs: Add dynamic ASE registration
Added option to set the ASE count through the bap API, making ASE
configuration runtime available. The upper limit of ASEs are still
bound by the Kconfig options set for ASEs.

Signed-off-by: Fredrik Danebjer <frdn@demant.com>
2024-09-11 07:41:12 -04:00

3285 lines
85 KiB
C

/* @file
* @brief Bluetooth ASCS
*/
/*
* Copyright (c) 2020 Intel Corporation
* Copyright (c) 2022-2023 Nordic Semiconductor ASA
* Copyright (c) 2024 Demant A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <zephyr/autoconf.h>
#include <zephyr/bluetooth/att.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/pacs.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/hci_types.h>
#include <zephyr/bluetooth/iso.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/net_buf.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/check.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/util_macro.h>
#include <zephyr/toolchain.h>
LOG_MODULE_REGISTER(bt_ascs, CONFIG_BT_ASCS_LOG_LEVEL);
#include "common/bt_str.h"
#include "common/assert.h"
#include "../host/att_internal.h"
#include "ascs_internal.h"
#include "audio_internal.h"
#include "bap_endpoint.h"
#include "bap_iso.h"
#include "bap_stream.h"
#include "bap_unicast_server.h"
#include "pacs_internal.h"
#include "cap_internal.h"
#define ASE_BUF_SEM_TIMEOUT K_MSEC(CONFIG_BT_ASCS_ASE_BUF_TIMEOUT)
#define MAX_ASES_SESSIONS CONFIG_BT_MAX_CONN * \
(CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT + \
CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT)
#define NTF_HEADER_SIZE (3) /* opcode (1) + handle (2) */
BUILD_ASSERT(CONFIG_BT_ASCS_MAX_ACTIVE_ASES <= MAX(MAX_ASES_SESSIONS,
CONFIG_BT_ISO_MAX_CHAN),
"Max active ASEs are set to more than actual number of ASEs or ISOs");
#if defined(CONFIG_BT_BAP_UNICAST_SERVER)
#define ASE_ID(_ase) ase->ep.status.id
#define ASE_DIR(_id) \
(_id > CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT ? BT_AUDIO_DIR_SOURCE : BT_AUDIO_DIR_SINK)
#define ASE_UUID(_id) \
(_id > CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT ? BT_UUID_ASCS_ASE_SRC : BT_UUID_ASCS_ASE_SNK)
#define ASE_COUNT (CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT + CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT)
#define BT_BAP_ASCS_RSP_NULL ((struct bt_bap_ascs_rsp[]) { BT_BAP_ASCS_RSP(0, 0) })
struct bt_ascs_ase {
struct bt_conn *conn;
struct bt_bap_ep ep;
const struct bt_gatt_attr *attr;
struct k_work_delayable disconnect_work;
struct k_work_delayable state_transition_work;
enum bt_bap_ep_state state_pending;
bool unexpected_iso_link_loss;
};
struct bt_ascs {
/* Whether the service has been registered or not */
bool registered;
struct bt_ascs_ase ase_pool[CONFIG_BT_ASCS_MAX_ACTIVE_ASES];
} ascs;
/* Minimum state size when in the codec configured state */
#define MIN_CONFIG_STATE_SIZE (1 + 1 + 1 + 1 + 1 + 2 + 3 + 3 + 3 + 3 + 5 + 1)
/* Minimum state size when in the QoS configured state */
#define MIN_QOS_STATE_SIZE (1 + 1 + 1 + 1 + 3 + 1 + 1 + 2 + 1 + 2 + 3 + 1 + 1 + 1)
/* Calculate the size requirement of the ASE BUF, based on the maximum possible
* size of the Codec Configured state or the QoS Configured state, as either
* of them can be the largest state
*/
#define ASE_BUF_SIZE \
MIN(BT_ATT_MAX_ATTRIBUTE_LEN, \
MAX(MIN_CONFIG_STATE_SIZE + CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE, \
MIN_QOS_STATE_SIZE + CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE))
/* Verify that the prepare count is large enough to cover the maximum value we support a client
* writing
*/
BUILD_ASSERT(
(BT_ATT_BUF_SIZE - NTF_HEADER_SIZE) >= ASE_BUF_SIZE ||
DIV_ROUND_UP(ASE_BUF_SIZE, (BT_ATT_BUF_SIZE - NTF_HEADER_SIZE)) <=
CONFIG_BT_ATT_PREPARE_COUNT,
"CONFIG_BT_ATT_PREPARE_COUNT not large enough to cover the maximum supported ASCS value");
/* It is mandatory to support long writes in ASCS unconditionally, and thus
* CONFIG_BT_ATT_PREPARE_COUNT must be at least 1 to support the feature
*/
BUILD_ASSERT(CONFIG_BT_ATT_PREPARE_COUNT > 0, "CONFIG_BT_ATT_PREPARE_COUNT shall be at least 1");
static const struct bt_bap_unicast_server_cb *unicast_server_cb;
static K_SEM_DEFINE(ase_buf_sem, 1, 1);
NET_BUF_SIMPLE_DEFINE_STATIC(ase_buf, ASE_BUF_SIZE);
static int control_point_notify(struct bt_conn *conn, const void *data, uint16_t len);
static int ascs_ep_get_status(struct bt_bap_ep *ep, struct net_buf_simple *buf);
static void ascs_app_rsp_warn_valid(const struct bt_bap_ascs_rsp *rsp)
{
/* Validate application error code */
switch (rsp->code) {
case BT_BAP_ASCS_RSP_CODE_SUCCESS:
case BT_BAP_ASCS_RSP_CODE_CAP_UNSUPPORTED:
case BT_BAP_ASCS_RSP_CODE_NO_MEM:
case BT_BAP_ASCS_RSP_CODE_UNSPECIFIED:
case BT_BAP_ASCS_RSP_CODE_CONF_UNSUPPORTED:
case BT_BAP_ASCS_RSP_CODE_CONF_REJECTED:
case BT_BAP_ASCS_RSP_CODE_METADATA_UNSUPPORTED:
case BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED:
break;
default:
LOG_WRN("Invalid application error code: %u", rsp->code);
return;
}
/* Validate application error code and reason combinations */
switch (rsp->code) {
case BT_BAP_ASCS_RSP_CODE_SUCCESS:
case BT_BAP_ASCS_RSP_CODE_CAP_UNSUPPORTED:
case BT_BAP_ASCS_RSP_CODE_NO_MEM:
case BT_BAP_ASCS_RSP_CODE_UNSPECIFIED:
if (rsp->reason != BT_BAP_ASCS_REASON_NONE) {
LOG_WRN("Invalid reason %u for code %u", rsp->reason, rsp->code);
}
break;
case BT_BAP_ASCS_RSP_CODE_CONF_UNSUPPORTED:
case BT_BAP_ASCS_RSP_CODE_CONF_REJECTED:
if (!IN_RANGE(rsp->reason, BT_BAP_ASCS_REASON_NONE, BT_BAP_ASCS_REASON_CIS)) {
LOG_WRN("Invalid reason %u for code %u", rsp->reason, rsp->code);
}
break;
case BT_BAP_ASCS_RSP_CODE_METADATA_UNSUPPORTED:
case BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED:
if (!BT_AUDIO_METADATA_TYPE_IS_KNOWN(rsp->metadata_type)) {
LOG_WRN("Invalid metadata type %u for code %u", rsp->metadata_type,
rsp->code);
}
break;
default:
break;
}
}
static bool is_valid_ase_id(uint8_t ase_id)
{
return IN_RANGE(ase_id, 1, ASE_COUNT);
}
static enum bt_bap_ep_state ascs_ep_get_state(struct bt_bap_ep *ep)
{
return ep->status.state;
}
static void ase_free(struct bt_ascs_ase *ase)
{
__ASSERT(ase && ase->conn, "Non-existing ASE");
LOG_DBG("conn %p ase %p id 0x%02x", (void *)ase->conn, ase, ase->ep.status.id);
if (ase->ep.iso != NULL) {
bt_bap_iso_unbind_ep(ase->ep.iso, &ase->ep);
}
bt_conn_unref(ase->conn);
ase->conn = NULL;
(void)k_work_cancel_delayable(&ase->disconnect_work);
(void)k_work_cancel_delayable(&ase->state_transition_work);
}
static uint16_t get_max_ntf_size(struct bt_conn *conn)
{
const uint16_t mtu = conn == NULL ? 0 : bt_gatt_get_mtu(conn);
if (mtu > NTF_HEADER_SIZE) {
return mtu - NTF_HEADER_SIZE;
}
return 0U;
}
static int ase_state_notify(struct bt_ascs_ase *ase)
{
struct bt_conn *conn = ase->conn;
struct bt_conn_info conn_info;
uint16_t max_ntf_size;
uint16_t ntf_size;
int err;
__ASSERT_NO_MSG(conn != NULL);
err = bt_conn_get_info(conn, &conn_info);
__ASSERT_NO_MSG(err == 0);
if (conn_info.state != BT_CONN_STATE_CONNECTED ||
!bt_gatt_is_subscribed(conn, ase->attr, BT_GATT_CCC_NOTIFY)) {
return 0;
}
err = k_sem_take(&ase_buf_sem, ASE_BUF_SEM_TIMEOUT);
if (err != 0) {
LOG_WRN("Failed to take ase_buf_sem: %d", err);
return err;
}
ascs_ep_get_status(&ase->ep, &ase_buf);
max_ntf_size = get_max_ntf_size(conn);
ntf_size = MIN(max_ntf_size, ase_buf.len);
if (ntf_size < ase_buf.len) {
LOG_DBG("Sending truncated notification (%u / %u)",
ntf_size, ase_buf.len);
}
err = bt_gatt_notify(conn, ase->attr, ase_buf.data, ntf_size);
k_sem_give(&ase_buf_sem);
return err;
}
static void ascs_disconnect_stream_work_handler(struct k_work *work)
{
struct k_work_delayable *d_work = k_work_delayable_from_work(work);
struct bt_ascs_ase *ase = CONTAINER_OF(d_work, struct bt_ascs_ase,
disconnect_work);
struct bt_bap_ep *ep = &ase->ep;
struct bt_bap_stream *stream = ep->stream;
struct bt_bap_stream *pair_stream;
__ASSERT(ep != NULL && ep->iso && stream != NULL,
"Invalid endpoint %p, iso %p or stream %p",
ep, ep == NULL ? NULL : ep->iso, stream);
if (ep->dir == BT_AUDIO_DIR_SINK) {
pair_stream = ep->iso->tx.stream;
} else {
pair_stream = ep->iso->rx.stream;
}
LOG_DBG("ase %p ep %p stream %p pair_stream %p",
ase, ep, stream, pair_stream);
if (pair_stream != NULL) {
struct bt_ascs_ase *pair_ase;
__ASSERT(pair_stream->ep != NULL, "Invalid pair_stream %p",
pair_stream);
if (pair_stream->ep->status.state == BT_BAP_EP_STATE_STREAMING) {
/* Should not disconnect ISO if the stream is paired
* with another one in the streaming state
*/
return;
}
pair_ase = CONTAINER_OF(pair_stream->ep, struct bt_ascs_ase,
ep);
/* Cancel pair ASE disconnect work if pending */
(void)k_work_cancel_delayable(&pair_ase->disconnect_work);
}
if (stream != NULL &&
ep->iso != NULL &&
(ep->iso->chan.state == BT_ISO_STATE_CONNECTED ||
ep->iso->chan.state == BT_ISO_STATE_CONNECTING)) {
const int err = bt_bap_stream_disconnect(stream);
if (err != 0) {
LOG_ERR("Failed to disconnect CIS %p: %d",
stream, err);
}
}
}
static int ascs_disconnect_stream(struct bt_bap_stream *stream)
{
struct bt_ascs_ase *ase = CONTAINER_OF(stream->ep, struct bt_ascs_ase,
ep);
LOG_DBG("%p", stream);
return k_work_reschedule(&ase->disconnect_work,
K_MSEC(CONFIG_BT_ASCS_ISO_DISCONNECT_DELAY));
}
static void ase_enter_state_idle(struct bt_ascs_ase *ase)
{
struct bt_bap_stream *stream = ase->ep.stream;
struct bt_bap_stream_ops *ops;
__ASSERT_NO_MSG(stream != NULL);
ase->ep.receiver_ready = false;
if (stream->conn != NULL) {
bt_conn_unref(stream->conn);
stream->conn = NULL;
}
ops = stream->ops;
if (ops != NULL && ops->released != NULL) {
ops->released(stream);
}
ase_free(ase);
}
static void ase_enter_state_codec_configured(struct bt_ascs_ase *ase)
{
struct bt_bap_stream *stream = ase->ep.stream;
struct bt_bap_stream_ops *ops;
__ASSERT_NO_MSG(stream != NULL);
ase->ep.receiver_ready = false;
ops = stream->ops;
if (ops != NULL && ops->configured != NULL) {
ops->configured(stream, &ase->ep.qos_pref);
}
}
static void ase_enter_state_qos_configured(struct bt_ascs_ase *ase)
{
struct bt_bap_stream *stream = ase->ep.stream;
struct bt_bap_stream_ops *ops;
__ASSERT_NO_MSG(stream != NULL);
ase->ep.receiver_ready = false;
ops = stream->ops;
if (ops != NULL && ops->qos_set != NULL) {
ops->qos_set(stream);
}
}
static void ase_enter_state_enabling(struct bt_ascs_ase *ase)
{
struct bt_bap_stream *stream = ase->ep.stream;
struct bt_bap_stream_ops *ops;
__ASSERT_NO_MSG(stream != NULL);
ops = stream->ops;
if (ops != NULL && ops->enabled != NULL) {
ops->enabled(stream);
}
/* SINK ASEs can autonomously go into the streaming state if the CIS is connected */
if (ase->ep.dir == BT_AUDIO_DIR_SINK && ase->ep.receiver_ready && ase->ep.iso != NULL &&
ase->ep.iso->chan.state == BT_ISO_STATE_CONNECTED) {
ascs_ep_set_state(&ase->ep, BT_BAP_EP_STATE_STREAMING);
}
}
static void ase_enter_state_streaming(struct bt_ascs_ase *ase)
{
struct bt_bap_stream *stream = ase->ep.stream;
struct bt_bap_stream_ops *ops;
__ASSERT_NO_MSG(stream != NULL);
ops = stream->ops;
if (ops != NULL && ops->started != NULL) {
ops->started(stream);
}
}
static void ase_metadata_updated(struct bt_ascs_ase *ase)
{
struct bt_bap_stream *stream = ase->ep.stream;
struct bt_bap_stream_ops *ops;
__ASSERT_NO_MSG(stream != NULL);
ops = stream->ops;
if (ops != NULL && ops->metadata_updated != NULL) {
ops->metadata_updated(stream);
}
}
static void ase_exit_state_streaming(struct bt_ascs_ase *ase)
{
struct bt_bap_stream *stream = ase->ep.stream;
struct bt_bap_stream_ops *ops;
const enum bt_bap_ep_state next_state = ascs_ep_get_state(&ase->ep);
uint8_t reason = ase->ep.reason;
__ASSERT_NO_MSG(stream != NULL);
if (reason == BT_HCI_ERR_SUCCESS) {
/* Default to BT_HCI_ERR_UNSPECIFIED if no other reason is set */
reason = BT_HCI_ERR_UNSPECIFIED;
}
ops = stream->ops;
/*
* On link-loss we go from streaming state to QOS configured state,
* and it makes sense to do the disabled callback before entering the
* QOS configured state
*/
if (next_state == BT_BAP_EP_STATE_QOS_CONFIGURED) {
if (ops != NULL && ops->disabled != NULL) {
ops->disabled(stream);
} else {
LOG_WRN("No callback for disabled set");
}
}
if (ops != NULL && ops->stopped != NULL) {
ops->stopped(stream, reason);
} else {
LOG_WRN("No callback for stopped set");
}
}
static void ase_exit_state_enabling(struct bt_ascs_ase *ase)
{
struct bt_bap_stream *stream = ase->ep.stream;
struct bt_bap_stream_ops *ops;
const enum bt_bap_ep_state next_state = ascs_ep_get_state(&ase->ep);
ops = stream->ops;
/*
* When the EP direction is BT_AUDIO_DIR_SOURCE the state machine goes from
* enabled to disabled where the disabled callback will be called,
* for BT_AUDIO_DIR_SINK we go from enabled to qos_configured,
* and logically we have to do the disabled callback first
*/
if (next_state == BT_BAP_EP_STATE_QOS_CONFIGURED && ase->ep.dir == BT_AUDIO_DIR_SINK) {
if (ops != NULL && ops->disabled != NULL) {
ops->disabled(stream);
} else {
LOG_WRN("No callback for disabled set");
}
}
}
static void ase_enter_state_disabling(struct bt_ascs_ase *ase)
{
struct bt_bap_stream *stream = ase->ep.stream;
struct bt_bap_stream_ops *ops;
__ASSERT_NO_MSG(stream != NULL);
ase->ep.receiver_ready = false;
ops = stream->ops;
if (ops != NULL && ops->disabled != NULL) {
ops->disabled(stream);
}
}
static void ase_enter_state_releasing(struct bt_ascs_ase *ase)
{
struct bt_bap_stream *stream = ase->ep.stream;
__ASSERT_NO_MSG(stream != NULL);
ase->ep.receiver_ready = false;
/* Either the client or the server may disconnect the CISes when entering the releasing
* state.
*/
if (bt_bap_stream_can_disconnect(stream)) {
int err;
err = ascs_disconnect_stream(stream);
if (err < 0) {
LOG_ERR("Failed to disconnect stream %p: %d", stream, err);
}
} else {
ascs_ep_set_state(&ase->ep, BT_BAP_EP_STATE_IDLE);
}
}
static void state_transition_work_handler(struct k_work *work)
{
struct k_work_delayable *d_work = k_work_delayable_from_work(work);
struct bt_ascs_ase *ase = CONTAINER_OF(d_work, struct bt_ascs_ase, state_transition_work);
const enum bt_bap_ep_state old_state = ascs_ep_get_state(&ase->ep);
const enum bt_bap_ep_state new_state = ase->state_pending;
int err;
ase->ep.status.state = new_state;
/* Notify ASE state */
if (ase->conn != NULL) {
err = ase_state_notify(ase);
if (err == -ENOMEM) {
struct bt_conn_info info;
uint32_t retry_delay_ms;
/* Revert back to old state */
ase->ep.status.state = old_state;
err = bt_conn_get_info(ase->conn, &info);
__ASSERT_NO_MSG(err == 0);
retry_delay_ms = BT_CONN_INTERVAL_TO_MS(info.le.interval);
/* Reschedule the state transition */
err = k_work_reschedule(d_work, K_MSEC(retry_delay_ms));
if (err >= 0) {
LOG_WRN("Out of buffers for ase state notification. "
"Will retry in %dms", retry_delay_ms);
return;
}
}
if (err < 0) {
LOG_ERR("Failed to notify ASE state (err %d)", err);
}
}
LOG_DBG("ase %p ep %p id 0x%02x %s -> %s", ase, &ase->ep, ase->ep.status.id,
bt_bap_ep_state_str(old_state), bt_bap_ep_state_str(new_state));
if (old_state == new_state) {
switch (new_state) {
case BT_BAP_EP_STATE_ENABLING:
case BT_BAP_EP_STATE_STREAMING:
ase_metadata_updated(ase);
return;
default:
break;
}
}
/* Actions needed for exiting the old state */
switch (old_state) {
case BT_BAP_EP_STATE_STREAMING:
ase_exit_state_streaming(ase);
break;
case BT_BAP_EP_STATE_ENABLING:
ase_exit_state_enabling(ase);
break;
default:
break;
}
/* Actions needed for entering the new state */
switch (new_state) {
case BT_BAP_EP_STATE_IDLE:
ase_enter_state_idle(ase);
break;
case BT_BAP_EP_STATE_CODEC_CONFIGURED:
ase_enter_state_codec_configured(ase);
break;
case BT_BAP_EP_STATE_QOS_CONFIGURED:
ase_enter_state_qos_configured(ase);
break;
case BT_BAP_EP_STATE_ENABLING:
ase_enter_state_enabling(ase);
break;
case BT_BAP_EP_STATE_STREAMING:
ase_enter_state_streaming(ase);
break;
case BT_BAP_EP_STATE_DISABLING:
ase_enter_state_disabling(ase);
break;
case BT_BAP_EP_STATE_RELEASING:
ase_enter_state_releasing(ase);
break;
default:
__ASSERT_PRINT("Invalid state %d", new_state);
}
}
int ascs_ep_set_state(struct bt_bap_ep *ep, uint8_t state)
{
struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep);
const enum bt_bap_ep_state old_state = ascs_ep_get_state(&ase->ep);
bool valid_state_transition = false;
int err;
switch (state) {
case BT_BAP_EP_STATE_IDLE:
valid_state_transition = true;
break;
case BT_BAP_EP_STATE_CODEC_CONFIGURED:
switch (old_state) {
case BT_BAP_EP_STATE_IDLE:
case BT_BAP_EP_STATE_CODEC_CONFIGURED:
case BT_BAP_EP_STATE_QOS_CONFIGURED:
case BT_BAP_EP_STATE_RELEASING:
valid_state_transition = true;
break;
default:
break;
} break;
case BT_BAP_EP_STATE_QOS_CONFIGURED:
switch (old_state) {
case BT_BAP_EP_STATE_CODEC_CONFIGURED:
case BT_BAP_EP_STATE_QOS_CONFIGURED:
valid_state_transition = true;
break;
case BT_BAP_EP_STATE_DISABLING:
valid_state_transition = ase->ep.dir == BT_AUDIO_DIR_SOURCE;
break;
case BT_BAP_EP_STATE_ENABLING:
case BT_BAP_EP_STATE_STREAMING:
/* Source ASE transition Streaming->QoS configured is valid on case of CIS
* link-loss.
*/
valid_state_transition = ase->ep.dir == BT_AUDIO_DIR_SINK ||
ase->unexpected_iso_link_loss;
break;
default:
break;
} break;
case BT_BAP_EP_STATE_ENABLING:
switch (old_state) {
case BT_BAP_EP_STATE_QOS_CONFIGURED:
case BT_BAP_EP_STATE_ENABLING:
valid_state_transition = true;
break;
default:
break;
} break;
case BT_BAP_EP_STATE_STREAMING:
switch (old_state) {
case BT_BAP_EP_STATE_ENABLING:
case BT_BAP_EP_STATE_STREAMING:
valid_state_transition = true;
break;
default:
break;
} break;
case BT_BAP_EP_STATE_DISABLING:
switch (old_state) {
case BT_BAP_EP_STATE_ENABLING:
case BT_BAP_EP_STATE_STREAMING:
valid_state_transition = ase->ep.dir == BT_AUDIO_DIR_SOURCE;
break;
default:
break;
} break;
case BT_BAP_EP_STATE_RELEASING:
switch (old_state) {
case BT_BAP_EP_STATE_CODEC_CONFIGURED:
case BT_BAP_EP_STATE_QOS_CONFIGURED:
case BT_BAP_EP_STATE_ENABLING:
case BT_BAP_EP_STATE_STREAMING:
valid_state_transition = true;
break;
case BT_BAP_EP_STATE_DISABLING:
valid_state_transition = ase->ep.dir == BT_AUDIO_DIR_SOURCE;
break;
default:
break;
} break;
}
if (!valid_state_transition) {
BT_ASSERT_MSG(false, "Invalid state transition: %s -> %s",
bt_bap_ep_state_str(old_state), bt_bap_ep_state_str(state));
return -EBADMSG;
}
ase->state_pending = state;
err = k_work_schedule(&ase->state_transition_work, K_NO_WAIT);
if (err < 0) {
LOG_ERR("Failed to schedule state transition work err %d", err);
return err;
}
return 0;
}
static void ascs_ep_get_status_config(struct bt_bap_ep *ep, struct net_buf_simple *buf)
{
struct bt_ascs_ase_status_config *cfg;
struct bt_audio_codec_qos_pref *pref = &ep->qos_pref;
cfg = net_buf_simple_add(buf, sizeof(*cfg));
cfg->framing = pref->unframed_supported ? BT_ASCS_QOS_FRAMING_UNFRAMED
: BT_ASCS_QOS_FRAMING_FRAMED;
cfg->phy = pref->phy;
cfg->rtn = pref->rtn;
cfg->latency = sys_cpu_to_le16(pref->latency);
sys_put_le24(pref->pd_min, cfg->pd_min);
sys_put_le24(pref->pd_max, cfg->pd_max);
sys_put_le24(pref->pref_pd_min, cfg->prefer_pd_min);
sys_put_le24(pref->pref_pd_max, cfg->prefer_pd_max);
cfg->codec.id = ep->codec_cfg.id;
cfg->codec.cid = sys_cpu_to_le16(ep->codec_cfg.cid);
cfg->codec.vid = sys_cpu_to_le16(ep->codec_cfg.vid);
LOG_DBG("dir %s unframed_supported 0x%02x phy 0x%02x rtn %u "
"latency %u pd_min %u pd_max %u pref_pd_min %u pref_pd_max %u codec id 0x%02x",
bt_audio_dir_str(ep->dir), pref->unframed_supported, pref->phy, pref->rtn,
pref->latency, pref->pd_min, pref->pd_max, pref->pref_pd_min, pref->pref_pd_max,
ep->stream->codec_cfg->id);
cfg->cc_len = ep->codec_cfg.data_len;
net_buf_simple_add_mem(buf, ep->codec_cfg.data, ep->codec_cfg.data_len);
}
static void ascs_ep_get_status_qos(struct bt_bap_ep *ep, struct net_buf_simple *buf)
{
struct bt_ascs_ase_status_qos *qos;
qos = net_buf_simple_add(buf, sizeof(*qos));
qos->cig_id = ep->cig_id;
qos->cis_id = ep->cis_id;
sys_put_le24(ep->stream->qos->interval, qos->interval);
qos->framing = ep->stream->qos->framing;
qos->phy = ep->stream->qos->phy;
qos->sdu = sys_cpu_to_le16(ep->stream->qos->sdu);
qos->rtn = ep->stream->qos->rtn;
qos->latency = sys_cpu_to_le16(ep->stream->qos->latency);
sys_put_le24(ep->stream->qos->pd, qos->pd);
LOG_DBG("dir %s codec id 0x%02x interval %u framing 0x%02x phy 0x%02x "
"rtn %u latency %u pd %u",
bt_audio_dir_str(ep->dir), ep->stream->codec_cfg->id, ep->stream->qos->interval,
ep->stream->qos->framing, ep->stream->qos->phy, ep->stream->qos->rtn,
ep->stream->qos->latency, ep->stream->qos->pd);
}
static void ascs_ep_get_status_enable(struct bt_bap_ep *ep, struct net_buf_simple *buf)
{
struct bt_ascs_ase_status_enable *enable;
enable = net_buf_simple_add(buf, sizeof(*enable));
enable->cig_id = ep->cig_id;
enable->cis_id = ep->cis_id;
enable->metadata_len = ep->codec_cfg.meta_len;
net_buf_simple_add_mem(buf, ep->codec_cfg.meta, ep->codec_cfg.meta_len);
LOG_DBG("dir %s cig 0x%02x cis 0x%02x",
bt_audio_dir_str(ep->dir), ep->cig_id, ep->cis_id);
}
static ssize_t ascs_ase_read_status_idle(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
struct bt_ascs_ase_status status = {
.id = POINTER_TO_UINT(BT_AUDIO_CHRC_USER_DATA(attr)),
.state = BT_BAP_EP_STATE_IDLE,
};
LOG_DBG("conn %p id 0x%02x", (void *)conn, status.id);
return bt_gatt_attr_read(conn, attr, buf, len, offset, &status, sizeof(status));
}
static int ascs_ep_get_status(struct bt_bap_ep *ep, struct net_buf_simple *buf)
{
if (!ep || !buf) {
return -EINVAL;
}
LOG_DBG("ep %p id 0x%02x state %s", ep, ep->status.id,
bt_bap_ep_state_str(ep->status.state));
/* Reset if buffer before using */
net_buf_simple_reset(buf);
(void)net_buf_simple_add_mem(buf, &ep->status, sizeof(ep->status));
switch (ep->status.state) {
case BT_BAP_EP_STATE_IDLE:
/* Fallthrough */
case BT_BAP_EP_STATE_RELEASING:
break;
case BT_BAP_EP_STATE_CODEC_CONFIGURED:
ascs_ep_get_status_config(ep, buf);
break;
case BT_BAP_EP_STATE_QOS_CONFIGURED:
ascs_ep_get_status_qos(ep, buf);
break;
case BT_BAP_EP_STATE_ENABLING:
/* Fallthrough */
case BT_BAP_EP_STATE_STREAMING:
/* Fallthrough */
case BT_BAP_EP_STATE_DISABLING:
ascs_ep_get_status_enable(ep, buf);
break;
default:
LOG_ERR("Invalid Endpoint state");
break;
}
return 0;
}
static int ascs_iso_accept(const struct bt_iso_accept_info *info, struct bt_iso_chan **iso_chan)
{
LOG_DBG("conn %p", (void *)info->acl);
for (size_t i = 0; i < ARRAY_SIZE(ascs.ase_pool); i++) {
struct bt_ascs_ase *ase = &ascs.ase_pool[i];
enum bt_bap_ep_state state;
struct bt_iso_chan *chan;
if (ase->conn != info->acl ||
ase->ep.cig_id != info->cig_id ||
ase->ep.cis_id != info->cis_id) {
continue;
}
state = ascs_ep_get_state(&ase->ep);
if (state != BT_BAP_EP_STATE_ENABLING && state != BT_BAP_EP_STATE_QOS_CONFIGURED) {
LOG_WRN("ase %p cannot accept ISO connection", ase);
break;
}
__ASSERT(ase->ep.iso != NULL, "ep %p not bound with ISO", &ase->ep);
chan = &ase->ep.iso->chan;
if (chan->iso != NULL) {
LOG_WRN("ase %p chan %p already connected", ase, chan);
return -EALREADY;
}
*iso_chan = chan;
LOG_DBG("iso_chan %p", *iso_chan);
return 0;
}
return -EACCES;
}
#if defined(CONFIG_BT_AUDIO_RX)
static void ascs_iso_recv(struct bt_iso_chan *chan,
const struct bt_iso_recv_info *info,
struct net_buf *buf)
{
struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan);
const struct bt_bap_stream_ops *ops;
struct bt_bap_stream *stream;
struct bt_bap_ep *ep;
ep = iso->rx.ep;
if (ep == NULL) {
/* In the case that the CIS has been setup as bidirectional, and
* only one of the directions have an ASE configured yet,
* we should only care about valid ISO packets when doing this
* check. The reason is that some controllers send HCI ISO data
* packets to the host, even if no SDU was sent on the remote
* side. This basically means that empty PDUs are sent to the
* host as HCI ISO data packets, which we should just ignore
*/
if ((info->flags & BT_ISO_FLAGS_VALID) != 0) {
LOG_DBG("Valid ISO packet of len %zu received for iso %p not bound with ep",
net_buf_frags_len(buf), chan);
}
return;
}
if (ep->status.state != BT_BAP_EP_STATE_STREAMING) {
if (IS_ENABLED(CONFIG_BT_BAP_DEBUG_STREAM_DATA)) {
LOG_DBG("ep %p is not in the streaming state: %s", ep,
bt_bap_ep_state_str(ep->status.state));
}
return;
}
stream = ep->stream;
if (stream == NULL) {
LOG_ERR("No stream for ep %p", ep);
return;
}
ops = stream->ops;
if (IS_ENABLED(CONFIG_BT_BAP_DEBUG_STREAM_DATA)) {
LOG_DBG("stream %p ep %p len %zu", stream, stream->ep, net_buf_frags_len(buf));
}
if (ops != NULL && ops->recv != NULL) {
ops->recv(stream, info, buf);
} else {
LOG_WRN("No callback for recv set");
}
}
#endif /* CONFIG_BT_AUDIO_RX */
#if defined(CONFIG_BT_AUDIO_TX)
static void ascs_iso_sent(struct bt_iso_chan *chan)
{
struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan);
const struct bt_bap_stream_ops *ops;
struct bt_bap_stream *stream;
struct bt_bap_ep *ep;
ep = iso->tx.ep;
if (ep == NULL) {
LOG_ERR("iso %p not bound with ep", chan);
return;
}
stream = ep->stream;
if (stream == NULL) {
LOG_ERR("No stream for ep %p", ep);
return;
}
ops = stream->ops;
if (IS_ENABLED(CONFIG_BT_BAP_DEBUG_STREAM_DATA)) {
LOG_DBG("stream %p ep %p", stream, stream->ep);
}
if (ops != NULL && ops->sent != NULL) {
ops->sent(stream);
}
}
#endif /* CONFIG_BT_AUDIO_TX */
static void ascs_update_sdu_size(struct bt_bap_ep *ep)
{
struct bt_iso_chan_io_qos *io_qos;
struct bt_audio_codec_qos *codec_qos = &ep->qos;
if (ep->dir == BT_AUDIO_DIR_SINK) {
io_qos = ep->iso->chan.qos->rx;
} else if (ep->dir == BT_AUDIO_DIR_SOURCE) {
io_qos = ep->iso->chan.qos->tx;
} else {
return;
}
io_qos->sdu = codec_qos->sdu;
io_qos->rtn = codec_qos->rtn;
}
static void ascs_ep_iso_connected(struct bt_bap_ep *ep)
{
struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep);
const struct bt_bap_stream_ops *stream_ops;
struct bt_bap_stream *stream;
if (ep->status.state != BT_BAP_EP_STATE_ENABLING) {
LOG_DBG("ep %p not in enabling state: %s", ep,
bt_bap_ep_state_str(ep->status.state));
return;
}
stream = ep->stream;
if (stream == NULL) {
LOG_ERR("No stream for ep %p", ep);
return;
}
/* Reset reason */
ep->reason = BT_HCI_ERR_SUCCESS;
ase->unexpected_iso_link_loss = false;
/* Some values are not provided by the HCI events when the CIS is established for the
* peripheral, so we update them here based on the parameters provided by the BAP Unicast
* Client
*/
ascs_update_sdu_size(ep);
LOG_DBG("stream %p ep %p dir %s", stream, ep, bt_audio_dir_str(ep->dir));
#if defined(CONFIG_BT_BAP_DEBUG_STREAM_SEQ_NUM)
/* reset sequence number */
stream->_prev_seq_num = 0U;
#endif /* CONFIG_BT_BAP_DEBUG_STREAM_SEQ_NUM */
stream_ops = stream->ops;
if (stream_ops != NULL && stream_ops->connected != NULL) {
stream_ops->connected(stream);
}
if (ep->dir == BT_AUDIO_DIR_SINK && ep->receiver_ready) {
/* Source ASEs shall be ISO connected first, and then receive
* the receiver start ready command to enter the streaming
* state
*/
ascs_ep_set_state(ep, BT_BAP_EP_STATE_STREAMING);
}
}
static void ascs_iso_connected(struct bt_iso_chan *chan)
{
struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan);
if (iso->rx.ep == NULL && iso->tx.ep == NULL) {
LOG_ERR("iso %p not bound with ep", chan);
return;
}
if (iso->rx.ep != NULL) {
ascs_ep_iso_connected(iso->rx.ep);
}
if (iso->tx.ep != NULL) {
ascs_ep_iso_connected(iso->tx.ep);
}
}
static void ascs_ep_iso_disconnected(struct bt_bap_ep *ep, uint8_t reason)
{
struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep);
const struct bt_bap_stream_ops *stream_ops;
struct bt_bap_stream *stream;
stream = ep->stream;
if (stream == NULL) {
LOG_ERR("No stream for ep %p", ep);
return;
}
LOG_DBG("stream %p ep %p state %s reason 0x%02x", stream, stream->ep,
bt_bap_ep_state_str(ep->status.state), reason);
stream_ops = stream->ops;
if (stream_ops != NULL && stream_ops->disconnected != NULL) {
stream_ops->disconnected(stream, reason);
}
/* Cancel ASE disconnect work if pending */
(void)k_work_cancel_delayable(&ase->disconnect_work);
ep->reason = reason;
if (ep->status.state == BT_BAP_EP_STATE_RELEASING) {
ascs_ep_set_state(ep, BT_BAP_EP_STATE_IDLE);
} else if (ep->status.state == BT_BAP_EP_STATE_STREAMING ||
ep->status.state == BT_BAP_EP_STATE_DISABLING) {
/* ASCS_v1.0 3.2 ASE state machine transitions
*
* If the server detects link loss of a CIS for an ASE in the Streaming
* state or the Disabling state, the server shall immediately transition
* that ASE to the QoS Configured state.
*/
ase->unexpected_iso_link_loss = true;
ascs_ep_set_state(ep, BT_BAP_EP_STATE_QOS_CONFIGURED);
}
}
static void ascs_iso_disconnected(struct bt_iso_chan *chan, uint8_t reason)
{
struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan);
if (iso->rx.ep == NULL && iso->tx.ep == NULL) {
return;
}
if (iso->rx.ep != NULL) {
ascs_ep_iso_disconnected(iso->rx.ep, reason);
}
if (iso->tx.ep != NULL) {
ascs_ep_iso_disconnected(iso->tx.ep, reason);
}
}
static struct bt_iso_chan_ops ascs_iso_ops = {
#if defined(CONFIG_BT_AUDIO_RX)
.recv = ascs_iso_recv,
#endif /* CONFIG_BT_AUDIO_RX */
#if defined(CONFIG_BT_AUDIO_TX)
.sent = ascs_iso_sent,
#endif /* CONFIG_BT_AUDIO_TX */
.connected = ascs_iso_connected,
.disconnected = ascs_iso_disconnected,
};
static void ascs_ase_cfg_changed(const struct bt_gatt_attr *attr,
uint16_t value)
{
LOG_DBG("attr %p value 0x%04x", attr, value);
}
#define CP_RSP_BUF_SIZE \
(sizeof(struct bt_ascs_cp_rsp) + (ASE_COUNT * sizeof(struct bt_ascs_cp_ase_rsp)))
/* Ensure that the cp_rsp_buf can fit in any notification
* (sizeof buffer - header for notification)
*/
BUILD_ASSERT(BT_ATT_BUF_SIZE - NTF_HEADER_SIZE >= CP_RSP_BUF_SIZE,
"BT_ATT_BUF_SIZE not large enough to hold responses for all ASEs");
NET_BUF_SIMPLE_DEFINE_STATIC(cp_rsp_buf, CP_RSP_BUF_SIZE);
static void ascs_cp_rsp_init(uint8_t op)
{
struct bt_ascs_cp_rsp *rsp;
net_buf_simple_reset(&cp_rsp_buf);
rsp = net_buf_simple_add(&cp_rsp_buf, sizeof(*rsp));
rsp->op = op;
rsp->num_ase = 0;
}
/* Add response to an opcode/ASE ID */
static void ascs_cp_rsp_add(uint8_t id, uint8_t code, uint8_t reason)
{
struct bt_ascs_cp_rsp *rsp = (void *)cp_rsp_buf.__buf;
struct bt_ascs_cp_ase_rsp *ase_rsp;
LOG_DBG("id 0x%02x code %s (0x%02x) reason %s (0x%02x)", id,
bt_ascs_rsp_str(code), code, bt_ascs_reason_str(reason), reason);
if (rsp->num_ase == BT_ASCS_UNSUPP_OR_LENGTH_ERR_NUM_ASE) {
return;
}
switch (code) {
/* If the Response_Code value is 0x01 or 0x02, Number_of_ASEs shall be
* set to 0xFF.
*/
case BT_BAP_ASCS_RSP_CODE_NOT_SUPPORTED:
case BT_BAP_ASCS_RSP_CODE_INVALID_LENGTH:
rsp->num_ase = BT_ASCS_UNSUPP_OR_LENGTH_ERR_NUM_ASE;
break;
default:
rsp->num_ase++;
break;
}
ase_rsp = net_buf_simple_add(&cp_rsp_buf, sizeof(*ase_rsp));
ase_rsp->id = id;
ase_rsp->code = code;
ase_rsp->reason = reason;
}
static void ascs_cp_rsp_success(uint8_t id)
{
ascs_cp_rsp_add(id, BT_BAP_ASCS_RSP_CODE_SUCCESS, BT_BAP_ASCS_REASON_NONE);
}
static int ase_release(struct bt_ascs_ase *ase, uint8_t reason, struct bt_bap_ascs_rsp *rsp)
{
enum bt_bap_ep_state state = ascs_ep_get_state(&ase->ep);
int err;
if (state == BT_BAP_EP_STATE_IDLE || state == BT_BAP_EP_STATE_RELEASING) {
LOG_WRN("Invalid operation in state: %s", bt_bap_ep_state_str(state));
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE,
BT_BAP_ASCS_REASON_NONE);
return -EBADMSG;
}
if (unicast_server_cb == NULL || unicast_server_cb->release == NULL) {
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
return -ENOTSUP;
}
err = unicast_server_cb->release(ase->ep.stream, rsp);
if (err) {
if (rsp->code == BT_BAP_ASCS_RSP_CODE_SUCCESS) {
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
}
LOG_ERR("Release failed: err %d, code %u, reason %u", err, rsp->code, rsp->reason);
return err;
}
/* Set reason in case this exits the streaming state */
ase->ep.reason = reason;
ascs_ep_set_state(&ase->ep, BT_BAP_EP_STATE_RELEASING);
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS, BT_BAP_ASCS_REASON_NONE);
return 0;
}
int bt_ascs_release_ase(struct bt_bap_ep *ep)
{
struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep);
const enum bt_bap_ep_state state = ascs_ep_get_state(&ase->ep);
if (state == BT_BAP_EP_STATE_IDLE) {
ase_free(ase);
return 0;
}
return ase_release(ase, BT_HCI_ERR_LOCALHOST_TERM_CONN, BT_BAP_ASCS_RSP_NULL);
}
static int ase_disable(struct bt_ascs_ase *ase, uint8_t reason, struct bt_bap_ascs_rsp *rsp)
{
struct bt_bap_stream *stream;
struct bt_bap_ep *ep;
int err;
LOG_DBG("ase %p", ase);
ep = &ase->ep;
switch (ep->status.state) {
/* Valid only if ASE_State field = 0x03 (Enabling) */
case BT_BAP_EP_STATE_ENABLING:
/* or 0x04 (Streaming) */
case BT_BAP_EP_STATE_STREAMING:
break;
default:
LOG_WRN("Invalid operation in state: %s", bt_bap_ep_state_str(ep->status.state));
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE,
BT_BAP_ASCS_REASON_NONE);
return -EBADMSG;
}
stream = ep->stream;
if (unicast_server_cb == NULL || unicast_server_cb->disable == NULL) {
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, BT_BAP_ASCS_REASON_NONE);
return -ENOTSUP;
}
err = unicast_server_cb->disable(stream, rsp);
if (err) {
if (rsp->code == BT_BAP_ASCS_RSP_CODE_SUCCESS) {
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
}
LOG_ERR("Disable failed: err %d, code %u, reason %u", err, rsp->code, rsp->reason);
return err;
}
/* Set reason in case this exits the streaming state */
ep->reason = reason;
/* The ASE state machine goes into different states from this operation
* based on whether it is a source or a sink ASE.
*/
if (ep->dir == BT_AUDIO_DIR_SOURCE) {
ascs_ep_set_state(ep, BT_BAP_EP_STATE_DISABLING);
} else {
ascs_ep_set_state(ep, BT_BAP_EP_STATE_QOS_CONFIGURED);
}
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS, BT_BAP_ASCS_REASON_NONE);
return 0;
}
int bt_ascs_disable_ase(struct bt_bap_ep *ep)
{
struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep);
return ase_disable(ase, BT_HCI_ERR_LOCALHOST_TERM_CONN, BT_BAP_ASCS_RSP_NULL);
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
for (size_t i = 0; i < ARRAY_SIZE(ascs.ase_pool); i++) {
struct bt_ascs_ase *ase = &ascs.ase_pool[i];
if (ase->conn != conn) {
continue;
}
if (ase->ep.status.state != BT_BAP_EP_STATE_IDLE) {
/* We must set the state to idle when the ACL is disconnected immediately,
* as when the ACL disconnect callbacks have been called, the application
* should expect there to be only a single reference to the bt_conn pointer
* from the stack.
* We trigger the work handler directly rather than e.g. calling
* ase_enter_state_idle to trigger "regular" state change behavior (such) as
* calling stream->stopped when leaving the streaming state.
*/
ase->ep.reason = reason;
ase->state_pending = BT_BAP_EP_STATE_IDLE;
state_transition_work_handler(&ase->state_transition_work.work);
/* At this point, `ase` object have been free'd */
}
}
}
BT_CONN_CB_DEFINE(conn_cb) = {
.disconnected = disconnected,
};
struct bap_iso_find_params {
struct bt_conn *acl;
uint8_t cig_id;
uint8_t cis_id;
};
static bool bap_iso_find_func(struct bt_bap_iso *iso, void *user_data)
{
struct bap_iso_find_params *params = user_data;
const struct bt_bap_ep *ep;
if (iso->rx.ep != NULL) {
ep = iso->rx.ep;
} else if (iso->tx.ep != NULL) {
ep = iso->tx.ep;
} else {
return false;
}
return ep->stream->conn == params->acl &&
ep->cig_id == params->cig_id &&
ep->cis_id == params->cis_id;
}
static struct bt_bap_iso *bap_iso_get_or_new(struct bt_conn *conn, uint8_t cig_id, uint8_t cis_id)
{
struct bt_bap_iso *iso;
struct bap_iso_find_params params = {
.acl = bt_conn_ref(conn),
.cig_id = cig_id,
.cis_id = cis_id,
};
iso = bt_bap_iso_find(bap_iso_find_func, &params);
bt_conn_unref(conn);
if (iso) {
return iso;
}
iso = bt_bap_iso_new();
if (!iso) {
return NULL;
}
bt_bap_iso_init(iso, &ascs_iso_ops);
return iso;
}
static uint8_t ase_attr_cb(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
struct bt_ascs_ase *ase = user_data;
if (ase->ep.status.id == POINTER_TO_UINT(BT_AUDIO_CHRC_USER_DATA(attr))) {
ase->attr = attr;
return BT_GATT_ITER_STOP;
}
return BT_GATT_ITER_CONTINUE;
}
void ascs_ep_init(struct bt_bap_ep *ep, uint8_t id)
{
LOG_DBG("ep %p id 0x%02x", ep, id);
(void)memset(ep, 0, sizeof(*ep));
ep->status.id = id;
ep->dir = ASE_DIR(id);
ep->reason = BT_HCI_ERR_SUCCESS;
}
static void ase_init(struct bt_ascs_ase *ase, struct bt_conn *conn, uint8_t id)
{
memset(ase, 0, sizeof(*ase));
ascs_ep_init(&ase->ep, id);
ase->conn = bt_conn_ref(conn);
/* Lookup ASE characteristic */
bt_gatt_foreach_attr_type(0x0001, 0xffff, ASE_UUID(id), NULL, 0, ase_attr_cb, ase);
__ASSERT(ase->attr, "ASE characteristic not found\n");
k_work_init_delayable(&ase->disconnect_work, ascs_disconnect_stream_work_handler);
k_work_init_delayable(&ase->state_transition_work, state_transition_work_handler);
}
static struct bt_ascs_ase *ase_new(struct bt_conn *conn, uint8_t id)
{
struct bt_ascs_ase *ase = NULL;
__ASSERT(id > 0 && id <= ASE_COUNT, "invalid ASE_ID 0x%02x", id);
for (size_t i = 0; i < ARRAY_SIZE(ascs.ase_pool); i++) {
if (ascs.ase_pool[i].conn == NULL) {
ase = &ascs.ase_pool[i];
break;
}
}
if (ase == NULL) {
return NULL;
}
ase_init(ase, conn, id);
LOG_DBG("conn %p new ase %p id 0x%02x", (void *)conn, ase, id);
return ase;
}
static struct bt_ascs_ase *ase_find(struct bt_conn *conn, uint8_t id)
{
for (size_t i = 0; i < ARRAY_SIZE(ascs.ase_pool); i++) {
struct bt_ascs_ase *ase = &ascs.ase_pool[i];
if (ase->conn == conn && ase->ep.status.id == id) {
return ase;
}
}
return NULL;
}
static ssize_t ascs_ase_read(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
uint8_t ase_id = POINTER_TO_UINT(BT_AUDIO_CHRC_USER_DATA(attr));
struct bt_ascs_ase *ase = NULL;
ssize_t ret_val;
int err;
LOG_DBG("conn %p attr %p buf %p len %u offset %u", (void *)conn, attr, buf, len, offset);
/* The callback can be used locally to read the ASE_ID in which case conn won't be set. */
if (conn != NULL) {
ase = ase_find(conn, ase_id);
}
/* If NULL, we haven't assigned an ASE, this also means that we are currently in IDLE */
if (ase == NULL) {
return ascs_ase_read_status_idle(conn, attr, buf, len, offset);
}
err = k_sem_take(&ase_buf_sem, ASE_BUF_SEM_TIMEOUT);
if (err != 0) {
LOG_DBG("Failed to take ase_buf_sem: %d", err);
return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES);
}
ascs_ep_get_status(&ase->ep, &ase_buf);
ret_val = bt_gatt_attr_read(conn, attr, buf, len, offset, ase_buf.data, ase_buf.len);
k_sem_give(&ase_buf_sem);
return ret_val;
}
static void ascs_cp_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
LOG_DBG("attr %p value 0x%04x", attr, value);
}
struct codec_cap_lookup_id_data {
uint8_t id;
uint16_t cid;
uint16_t vid;
const struct bt_audio_codec_cap *codec_cap;
};
static bool codec_lookup_id(const struct bt_pacs_cap *cap, void *user_data)
{
struct codec_cap_lookup_id_data *data = user_data;
if (cap->codec_cap->id == data->id && cap->codec_cap->cid == data->cid &&
cap->codec_cap->vid == data->vid) {
data->codec_cap = cap->codec_cap;
return false;
}
return true;
}
static int ascs_ep_set_codec(struct bt_bap_ep *ep, uint8_t id, uint16_t cid, uint16_t vid,
uint8_t *cc, uint8_t len, struct bt_bap_ascs_rsp *rsp)
{
struct bt_audio_codec_cfg *codec_cfg;
struct codec_cap_lookup_id_data lookup_data = {
.id = id,
.cid = cid,
.vid = vid,
};
if (ep == NULL) {
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_CONF_INVALID,
BT_BAP_ASCS_REASON_CODEC_DATA);
return -EINVAL;
}
codec_cfg = &ep->codec_cfg;
LOG_DBG("ep %p dir %s codec id 0x%02x cid 0x%04x vid 0x%04x len %u",
ep, bt_audio_dir_str(ep->dir), id, cid, vid, len);
bt_pacs_cap_foreach(ep->dir, codec_lookup_id, &lookup_data);
if (lookup_data.codec_cap == NULL) {
LOG_DBG("Codec with id %u for dir %s is not supported by our capabilities",
id, bt_audio_dir_str(ep->dir));
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_CONF_INVALID,
BT_BAP_ASCS_REASON_CODEC);
return -ENOENT;
}
codec_cfg->id = id;
codec_cfg->cid = cid;
codec_cfg->vid = vid;
codec_cfg->data_len = len;
memcpy(codec_cfg->data, cc, len);
codec_cfg->path_id = lookup_data.codec_cap->path_id;
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS, BT_BAP_ASCS_REASON_NONE);
return 0;
}
static int ase_config(struct bt_ascs_ase *ase, const struct bt_ascs_config *cfg)
{
struct bt_bap_stream *stream;
struct bt_audio_codec_cfg codec_cfg;
struct bt_bap_ascs_rsp rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS,
BT_BAP_ASCS_REASON_NONE);
int err;
LOG_DBG("ase %p latency 0x%02x phy 0x%02x codec 0x%02x "
"cid 0x%04x vid 0x%04x codec config len 0x%02x",
ase, cfg->latency, cfg->phy, cfg->codec.id, cfg->codec.cid, cfg->codec.vid,
cfg->cc_len);
if (cfg->latency < BT_ASCS_CONFIG_LATENCY_LOW ||
cfg->latency > BT_ASCS_CONFIG_LATENCY_HIGH) {
LOG_WRN("Invalid latency: 0x%02x", cfg->latency);
ascs_cp_rsp_add(ASE_ID(ase), BT_BAP_ASCS_RSP_CODE_CONF_INVALID,
BT_BAP_ASCS_REASON_LATENCY);
return -EINVAL;
}
if (cfg->phy < BT_ASCS_CONFIG_PHY_LE_1M ||
cfg->phy > BT_ASCS_CONFIG_PHY_LE_CODED) {
LOG_WRN("Invalid PHY: 0x%02x", cfg->phy);
ascs_cp_rsp_add(ASE_ID(ase), BT_BAP_ASCS_RSP_CODE_CONF_INVALID,
BT_BAP_ASCS_REASON_PHY);
return -EINVAL;
}
if (cfg->cc_len > CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE) {
LOG_DBG("Can not store %u codec configuration data", cfg->cc_len);
return -ENOMEM;
}
switch (ase->ep.status.state) {
/* Valid only if ASE_State field = 0x00 (Idle) */
case BT_BAP_EP_STATE_IDLE:
/* or 0x01 (Codec Configured) */
case BT_BAP_EP_STATE_CODEC_CONFIGURED:
/* or 0x02 (QoS Configured) */
case BT_BAP_EP_STATE_QOS_CONFIGURED:
break;
default:
LOG_WRN("Invalid operation in state: %s",
bt_bap_ep_state_str(ase->ep.status.state));
ascs_cp_rsp_add(ASE_ID(ase), BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE,
BT_BAP_ASCS_REASON_NONE);
return -EINVAL;
}
/* Store current codec configuration to be able to restore it
* in case of error.
*/
(void)memcpy(&codec_cfg, &ase->ep.codec_cfg, sizeof(codec_cfg));
err = ascs_ep_set_codec(&ase->ep, cfg->codec.id, sys_le16_to_cpu(cfg->codec.cid),
sys_le16_to_cpu(cfg->codec.vid), (uint8_t *)cfg->cc, cfg->cc_len,
&rsp);
if (err) {
ascs_app_rsp_warn_valid(&rsp);
(void)memcpy(&ase->ep.codec_cfg, &codec_cfg, sizeof(codec_cfg));
ascs_cp_rsp_add(ASE_ID(ase), rsp.code, rsp.reason);
return err;
}
if (ase->ep.stream != NULL) {
if (unicast_server_cb != NULL &&
unicast_server_cb->reconfig != NULL) {
err = unicast_server_cb->reconfig(ase->ep.stream, ase->ep.dir,
&ase->ep.codec_cfg, &ase->ep.qos_pref,
&rsp);
} else {
err = -ENOTSUP;
rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
}
if (err) {
ascs_app_rsp_warn_valid(&rsp);
if (rsp.code == BT_BAP_ASCS_RSP_CODE_SUCCESS) {
rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
}
LOG_ERR("Reconfig failed: err %d, code %u, reason %u",
err, rsp.code, rsp.reason);
(void)memcpy(&ase->ep.codec_cfg, &codec_cfg, sizeof(codec_cfg));
ascs_cp_rsp_add(ASE_ID(ase), rsp.code, rsp.reason);
return err;
}
stream = ase->ep.stream;
} else {
stream = NULL;
if (unicast_server_cb != NULL &&
unicast_server_cb->config != NULL) {
err = unicast_server_cb->config(ase->conn, &ase->ep, ase->ep.dir,
&ase->ep.codec_cfg, &stream,
&ase->ep.qos_pref, &rsp);
} else {
err = -ENOTSUP;
rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
}
if (err || stream == NULL) {
ascs_app_rsp_warn_valid(&rsp);
if (rsp.code == BT_BAP_ASCS_RSP_CODE_SUCCESS) {
rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
}
LOG_ERR("Config failed: err %d, stream %p, code %u, reason %u",
err, stream, rsp.code, rsp.reason);
(void)memcpy(&ase->ep.codec_cfg, &codec_cfg, sizeof(codec_cfg));
ascs_cp_rsp_add(ASE_ID(ase), rsp.code, rsp.reason);
return err ? err : -ENOMEM;
}
bt_bap_stream_init(stream);
}
ascs_cp_rsp_success(ASE_ID(ase));
bt_bap_stream_attach(ase->conn, stream, &ase->ep, &ase->ep.codec_cfg);
ascs_ep_set_state(&ase->ep, BT_BAP_EP_STATE_CODEC_CONFIGURED);
return 0;
}
static struct bt_bap_ep *ep_lookup_stream(struct bt_conn *conn, struct bt_bap_stream *stream)
{
for (size_t i = 0; i < ARRAY_SIZE(ascs.ase_pool); i++) {
struct bt_ascs_ase *ase = &ascs.ase_pool[i];
if (ase->conn == conn && ase->ep.stream == stream) {
return &ase->ep;
}
}
return NULL;
}
int bt_ascs_config_ase(struct bt_conn *conn, struct bt_bap_stream *stream,
struct bt_audio_codec_cfg *codec_cfg,
const struct bt_audio_codec_qos_pref *qos_pref)
{
int err;
struct bt_ascs_ase *ase = NULL;
struct bt_bap_ep *ep;
struct codec_cap_lookup_id_data lookup_data;
CHECKIF(conn == NULL || stream == NULL || codec_cfg == NULL || qos_pref == NULL) {
LOG_DBG("NULL value(s) supplied)");
return -EINVAL;
}
ep = ep_lookup_stream(conn, stream);
if (ep != NULL) {
LOG_DBG("Stream already configured for conn %p", (void *)stream->conn);
return -EALREADY;
}
/* Get a free ASE or NULL if all ASE instances are already in use */
for (int i = 1; i <= ASE_COUNT; i++) {
if (ase_find(conn, i) == NULL) {
ase = ase_new(conn, i);
break;
}
}
if (ase == NULL) {
LOG_WRN("No free ASE found.");
return -ENOTSUP;
}
ep = &ase->ep;
if (ep == NULL) {
return -EINVAL;
}
lookup_data.id = codec_cfg->id;
lookup_data.cid = codec_cfg->cid;
lookup_data.vid = codec_cfg->vid;
bt_pacs_cap_foreach(ep->dir, codec_lookup_id, &lookup_data);
if (lookup_data.codec_cap == NULL) {
LOG_DBG("Codec with id %u for dir %s is not supported by our capabilities",
codec_cfg->id, bt_audio_dir_str(ep->dir));
return -ENOENT;
}
(void)memcpy(&ep->codec_cfg, codec_cfg, sizeof(ep->codec_cfg));
ep->qos_pref = *qos_pref;
bt_bap_stream_attach(conn, stream, ep, &ep->codec_cfg);
err = ascs_ep_set_state(ep, BT_BAP_EP_STATE_CODEC_CONFIGURED);
if (err != 0) {
bt_bap_stream_detach(stream);
ase_free(ase);
return err;
}
return 0;
}
static uint16_t get_max_ase_rsp_for_conn(struct bt_conn *conn)
{
const uint16_t max_ntf_size = get_max_ntf_size(conn);
const size_t rsp_hdr_size = sizeof(struct bt_ascs_cp_rsp);
if (max_ntf_size > rsp_hdr_size) {
return (max_ntf_size - rsp_hdr_size) / sizeof(struct bt_ascs_cp_ase_rsp);
}
return 0U;
}
static bool is_valid_num_ases(struct bt_conn *conn, uint8_t num_ases)
{
const uint16_t max_ase_rsp = get_max_ase_rsp_for_conn(conn);
if (num_ases < 1U) {
LOG_WRN("Number_of_ASEs parameter value is less than 1");
return false;
} else if (num_ases > ASE_COUNT) {
/* If the request is for more ASEs than we have, we just reject the request */
LOG_DBG("Number_of_ASEs parameter value (%u) is greater than %d", num_ases,
ASE_COUNT);
return false;
} else if (num_ases > max_ase_rsp) {
/* If the request is for more ASEs than we can respond to, we reject the request */
LOG_DBG("Number_of_ASEs parameter value (%u) is greater than what we can respond "
"to (%u) based on the MTU",
num_ases, max_ase_rsp);
return false;
}
return true;
}
static bool is_valid_config_len(struct bt_conn *conn, struct net_buf_simple *buf)
{
const struct bt_ascs_config_op *op;
struct net_buf_simple_state state;
net_buf_simple_save(buf, &state);
if (buf->len < sizeof(*op)) {
LOG_WRN("Invalid length %u < %zu", buf->len, sizeof(*op));
return false;
}
op = net_buf_simple_pull_mem(buf, sizeof(*op));
if (!is_valid_num_ases(conn, op->num_ases)) {
return false;
}
for (uint8_t i = 0U; i < op->num_ases; i++) {
const struct bt_ascs_config *config;
if (buf->len < sizeof(*config)) {
LOG_WRN("Malformed params array");
return false;
}
config = net_buf_simple_pull_mem(buf, sizeof(*config));
if (buf->len < config->cc_len) {
LOG_WRN("Malformed codec specific config");
return false;
}
(void)net_buf_simple_pull_mem(buf, config->cc_len);
}
if (buf->len > 0) {
LOG_WRN("Unexpected data");
return false;
}
net_buf_simple_restore(buf, &state);
return true;
}
static ssize_t ascs_config(struct bt_conn *conn, struct net_buf_simple *buf)
{
const struct bt_ascs_config_op *req;
const struct bt_ascs_config *cfg;
if (!is_valid_config_len(conn, buf)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
req = net_buf_simple_pull_mem(buf, sizeof(*req));
LOG_DBG("num_ases %u", req->num_ases);
for (uint8_t i = 0; i < req->num_ases; i++) {
struct bt_ascs_ase *ase;
int err;
cfg = net_buf_simple_pull_mem(buf, sizeof(*cfg));
(void)net_buf_simple_pull(buf, cfg->cc_len);
LOG_DBG("ase 0x%02x cc_len %u", cfg->ase, cfg->cc_len);
if (!cfg->ase || cfg->ase > ASE_COUNT) {
LOG_WRN("Invalid ASE ID: %u", cfg->ase);
ascs_cp_rsp_add(cfg->ase, BT_BAP_ASCS_RSP_CODE_INVALID_ASE,
BT_BAP_ASCS_REASON_NONE);
continue;
}
ase = ase_find(conn, cfg->ase);
if (ase != NULL) {
ase_config(ase, cfg);
continue;
}
ase = ase_new(conn, cfg->ase);
if (!ase) {
ascs_cp_rsp_add(cfg->ase, BT_BAP_ASCS_RSP_CODE_NO_MEM,
BT_BAP_ASCS_REASON_NONE);
LOG_WRN("No free ASE found for config ASE ID 0x%02x", cfg->ase);
continue;
}
err = ase_config(ase, cfg);
if (err != 0) {
ase_free(ase);
}
}
return buf->size;
}
void bt_ascs_foreach_ep(struct bt_conn *conn, bt_bap_ep_func_t func, void *user_data)
{
for (size_t i = 0; i < ARRAY_SIZE(ascs.ase_pool); i++) {
struct bt_ascs_ase *ase = &ascs.ase_pool[i];
if (ase->conn == conn) {
func(&ase->ep, user_data);
}
}
}
static void ase_qos(struct bt_ascs_ase *ase, uint8_t cig_id, uint8_t cis_id,
struct bt_audio_codec_qos *qos, struct bt_bap_ascs_rsp *rsp)
{
struct bt_bap_ep *ep = &ase->ep;
struct bt_bap_stream *stream;
LOG_DBG("ase %p cig 0x%02x cis 0x%02x interval %u framing 0x%02x phy 0x%02x sdu %u rtn %u "
"latency %u pd %u", ase, cig_id, cis_id, qos->interval, qos->framing, qos->phy,
qos->sdu, qos->rtn, qos->latency, qos->pd);
switch (ep->status.state) {
/* Valid only if ASE_State field = 0x01 (Codec Configured) */
case BT_BAP_EP_STATE_CODEC_CONFIGURED:
/* or 0x02 (QoS Configured) */
case BT_BAP_EP_STATE_QOS_CONFIGURED:
break;
default:
LOG_WRN("Invalid operation in state: %s", bt_bap_ep_state_str(ep->status.state));
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE,
BT_BAP_ASCS_REASON_NONE);
return;
}
stream = ep->stream;
if (stream == NULL) {
LOG_ERR("NULL stream");
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, BT_BAP_ASCS_REASON_NONE);
return;
}
if (stream->ep == NULL) {
LOG_ERR("NULL stream->ep");
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, BT_BAP_ASCS_REASON_NONE);
return;
}
rsp->reason = bt_audio_verify_qos(qos);
if (rsp->reason != BT_BAP_ASCS_REASON_NONE) {
rsp->code = BT_BAP_ASCS_RSP_CODE_CONF_INVALID;
return;
}
rsp->reason = bt_bap_stream_verify_qos(stream, qos);
if (rsp->reason != BT_BAP_ASCS_REASON_NONE) {
rsp->code = BT_BAP_ASCS_RSP_CODE_CONF_INVALID;
return;
}
if (unicast_server_cb != NULL && unicast_server_cb->qos != NULL) {
int err = unicast_server_cb->qos(stream, qos, rsp);
if (err) {
if (rsp->code == BT_BAP_ASCS_RSP_CODE_SUCCESS) {
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
}
LOG_DBG("Application returned error: err %d status %u reason %u",
err, rsp->code, rsp->reason);
return;
}
}
/* QoS->QoS transition. Unbind ISO if CIG/CIS changed. */
if (ep->iso != NULL && (ep->cig_id != cig_id || ep->cis_id != cis_id)) {
bt_bap_iso_unbind_ep(ep->iso, ep);
}
if (ep->iso == NULL) {
struct bt_bap_iso *iso;
iso = bap_iso_get_or_new(ase->conn, cig_id, cis_id);
if (iso == NULL) {
LOG_ERR("Could not allocate bap_iso");
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_NO_MEM,
BT_BAP_ASCS_REASON_NONE);
return;
}
if (bt_bap_iso_get_ep(false, iso, ep->dir) != NULL) {
LOG_ERR("iso %p already in use in dir %s",
&iso->chan, bt_audio_dir_str(ep->dir));
bt_bap_iso_unref(iso);
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_CONF_INVALID,
BT_BAP_ASCS_REASON_CIS);
return;
}
bt_bap_iso_bind_ep(iso, ep);
bt_bap_iso_unref(iso);
}
/* Store the QoS once accepted */
ep->qos = *qos;
stream->qos = &ep->qos;
/* We setup the data path here, as this is the earliest where
* we have the ISO <-> EP coupling completed (due to setting
* the CIS ID in the QoS procedure).
*/
bt_bap_iso_configure_data_path(ep, stream->codec_cfg);
ep->cig_id = cig_id;
ep->cis_id = cis_id;
ascs_ep_set_state(ep, BT_BAP_EP_STATE_QOS_CONFIGURED);
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS, BT_BAP_ASCS_REASON_NONE);
}
static bool is_valid_qos_len(struct bt_conn *conn, struct net_buf_simple *buf)
{
const struct bt_ascs_qos_op *op;
struct net_buf_simple_state state;
size_t params_size;
net_buf_simple_save(buf, &state);
if (buf->len < sizeof(*op)) {
LOG_WRN("Invalid length %u < %zu", buf->len, sizeof(*op));
return false;
}
op = net_buf_simple_pull_mem(buf, sizeof(*op));
if (!is_valid_num_ases(conn, op->num_ases)) {
return false;
}
params_size = sizeof(struct bt_ascs_qos) * op->num_ases;
if (buf->len < params_size) {
LOG_WRN("Malformed params array");
return false;
}
(void)net_buf_simple_pull_mem(buf, params_size);
if (buf->len > 0) {
LOG_WRN("Unexpected data");
return false;
}
net_buf_simple_restore(buf, &state);
return true;
}
static ssize_t ascs_qos(struct bt_conn *conn, struct net_buf_simple *buf)
{
const struct bt_ascs_qos_op *req;
if (!is_valid_qos_len(conn, buf)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
req = net_buf_simple_pull_mem(buf, sizeof(*req));
LOG_DBG("num_ases %u", req->num_ases);
for (uint8_t i = 0; i < req->num_ases; i++) {
struct bt_bap_ascs_rsp rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
struct bt_audio_codec_qos cqos;
const struct bt_ascs_qos *qos;
struct bt_ascs_ase *ase;
qos = net_buf_simple_pull_mem(buf, sizeof(*qos));
LOG_DBG("ase 0x%02x", qos->ase);
if (!is_valid_ase_id(qos->ase)) {
ascs_cp_rsp_add(qos->ase, BT_BAP_ASCS_RSP_CODE_INVALID_ASE,
BT_BAP_ASCS_REASON_NONE);
LOG_WRN("Unknown ase 0x%02x", qos->ase);
continue;
}
ase = ase_find(conn, qos->ase);
if (!ase) {
LOG_DBG("Invalid operation for idle ASE");
ascs_cp_rsp_add(qos->ase, BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE,
BT_BAP_ASCS_REASON_NONE);
continue;
}
cqos.interval = sys_get_le24(qos->interval);
cqos.framing = qos->framing;
cqos.phy = qos->phy;
cqos.sdu = sys_le16_to_cpu(qos->sdu);
cqos.rtn = qos->rtn;
cqos.latency = sys_le16_to_cpu(qos->latency);
cqos.pd = sys_get_le24(qos->pd);
ase_qos(ase, qos->cig, qos->cis, &cqos, &rsp);
ascs_cp_rsp_add(qos->ase, rsp.code, rsp.reason);
}
return buf->size;
}
struct ascs_parse_result {
int err;
struct bt_conn *conn;
struct bt_bap_ascs_rsp *rsp;
const struct bt_bap_ep *ep;
};
static bool is_context_available(struct bt_conn *conn, enum bt_audio_dir dir, uint16_t context)
{
return (context & bt_pacs_get_available_contexts_for_conn(conn, dir)) == context;
}
static bool ascs_parse_metadata(struct bt_data *data, void *user_data)
{
struct ascs_parse_result *result = user_data;
const struct bt_bap_ep *ep = result->ep;
const uint8_t data_len = data->data_len;
const uint8_t data_type = data->type;
const uint8_t *data_value = data->data;
LOG_DBG("type 0x%02x len %u", data_type, data_len);
if (!BT_AUDIO_METADATA_TYPE_IS_KNOWN(data_type)) {
LOG_WRN("Unknown metadata type 0x%02x", data_type);
return true;
}
switch (data_type) {
/* TODO: Consider rejecting BT_AUDIO_METADATA_TYPE_PREF_CONTEXT type */
case BT_AUDIO_METADATA_TYPE_PREF_CONTEXT:
case BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT: {
uint16_t context;
if (data_len != sizeof(context)) {
*result->rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_INVALID,
data_type);
result->err = -EBADMSG;
return false;
}
context = sys_get_le16(data_value);
if (context == BT_AUDIO_CONTEXT_TYPE_PROHIBITED) {
*result->rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_INVALID,
data_type);
result->err = -EINVAL;
return false;
}
/* The CAP acceptor shall not accept metadata with unsupported stream context. */
if (IS_ENABLED(CONFIG_BT_CAP_ACCEPTOR) &&
data_type == BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT) {
if (!is_context_available(result->conn, ep->dir, context)) {
LOG_WRN("Context 0x%04x is unavailable", context);
*result->rsp = BT_BAP_ASCS_RSP(
BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED, data_type);
result->err = -EACCES;
return false;
}
}
break;
}
case BT_AUDIO_METADATA_TYPE_LANG:
if (data_len != BT_AUDIO_LANG_SIZE) {
*result->rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_INVALID,
data_type);
result->err = -EBADMSG;
return false;
}
break;
case BT_AUDIO_METADATA_TYPE_CCID_LIST: {
/* Verify that the CCID is a known CCID on the writing device */
if (IS_ENABLED(CONFIG_BT_CAP_ACCEPTOR)) {
for (uint8_t i = 0; i < data_len; i++) {
const uint8_t ccid = data_value[i];
if (!bt_cap_acceptor_ccid_exist(ep->stream->conn, ccid)) {
LOG_WRN("CCID %u is unknown", ccid);
/* TBD:
* Should we reject the Metadata?
*
* Should unknown CCIDs trigger a
* discovery procedure for TBS or MCS?
*
* Or should we just accept as is, and
* then let the application decide?
*/
}
}
}
break;
}
case BT_AUDIO_METADATA_TYPE_PARENTAL_RATING:
if (data_len != 1) {
*result->rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_INVALID,
data_type);
result->err = -EBADMSG;
return false;
}
break;
case BT_AUDIO_METADATA_TYPE_AUDIO_STATE: {
uint8_t state;
if (data_len != sizeof(state)) {
*result->rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_INVALID,
data_type);
result->err = -EBADMSG;
return false;
}
break;
}
/* TODO: Consider rejecting BT_AUDIO_METADATA_TYPE_BROADCAST_IMMEDIATE type */
case BT_AUDIO_METADATA_TYPE_BROADCAST_IMMEDIATE:
if (data_len != 0) {
*result->rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_INVALID,
data_type);
result->err = -EBADMSG;
return false;
}
break;
default:
break;
}
return true;
}
static int ascs_verify_metadata(struct bt_bap_ep *ep, const struct bt_ascs_metadata *meta,
struct bt_bap_ascs_rsp *rsp)
{
struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep);
struct ascs_parse_result result = {
.conn = ase->conn,
.rsp = rsp,
.err = 0,
.ep = ep,
};
int err;
if (meta->len > CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) {
LOG_WRN("Not enough space for Codec Config Metadata: %u > %d", meta->len,
CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE);
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_NO_MEM, BT_BAP_ASCS_REASON_NONE);
return -ENOMEM;
}
/* Parse LTV entries */
err = bt_audio_data_parse(meta->data, meta->len, ascs_parse_metadata, &result);
if (err != 0 && err != -ECANCELED) {
/* ECANCELED is called if the callback stops the parsing prematurely, in which case
* result.err will be set
*/
LOG_DBG("Failed to parse metadata: %d", err);
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_INVALID,
BT_BAP_ASCS_REASON_NONE);
return err;
}
return result.err;
}
static void ase_metadata(struct bt_ascs_ase *ase, struct bt_ascs_metadata *meta)
{
struct bt_bap_stream *stream;
struct bt_bap_ep *ep;
struct bt_bap_ascs_rsp rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS,
BT_BAP_ASCS_REASON_NONE);
uint8_t state;
int err;
LOG_DBG("ase %p meta->len %u", ase, meta->len);
ep = &ase->ep;
state = ep->status.state;
switch (state) {
/* Valid for an ASE only if ASE_State field = 0x03 (Enabling) */
case BT_BAP_EP_STATE_ENABLING:
/* or 0x04 (Streaming) */
case BT_BAP_EP_STATE_STREAMING:
break;
default:
LOG_WRN("Invalid operation in state: %s", bt_bap_ep_state_str(state));
ascs_cp_rsp_add(ASE_ID(ase), BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE,
BT_BAP_ASCS_REASON_NONE);
return;
}
stream = ep->stream;
err = ascs_verify_metadata(ep, meta, &rsp);
if (err != 0) {
LOG_DBG("Invalid metadata from client: %d", err);
/* rsp will be set by ascs_verify_metadata*/
} else if (unicast_server_cb != NULL && unicast_server_cb->metadata != NULL) {
err = unicast_server_cb->metadata(stream, meta->data, meta->len, &rsp);
} else {
err = -ENOTSUP;
rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
}
if (err) {
ascs_app_rsp_warn_valid(&rsp);
if (rsp.code == BT_BAP_ASCS_RSP_CODE_SUCCESS) {
rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
}
LOG_ERR("Metadata failed: err %d, code %u, reason %u", err, rsp.code, rsp.reason);
ascs_cp_rsp_add(ASE_ID(ase), rsp.code, rsp.reason);
return;
}
ep->codec_cfg.meta_len = meta->len;
(void)memcpy(ep->codec_cfg.meta, meta->data, meta->len);
/* Set the state to the same state to trigger the notifications */
ascs_ep_set_state(ep, ep->status.state);
ascs_cp_rsp_success(ASE_ID(ase));
}
static int ase_enable(struct bt_ascs_ase *ase, struct bt_ascs_metadata *meta)
{
struct bt_bap_stream *stream;
struct bt_bap_ep *ep;
struct bt_bap_ascs_rsp rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS,
BT_BAP_ASCS_REASON_NONE);
int err;
LOG_DBG("ase %p meta->len %u", ase, meta->len);
ep = &ase->ep;
/* Valid for an ASE only if ASE_State field = 0x02 (QoS Configured) */
if (ep->status.state != BT_BAP_EP_STATE_QOS_CONFIGURED) {
err = -EBADMSG;
LOG_WRN("Invalid operation in state: %s", bt_bap_ep_state_str(ep->status.state));
ascs_cp_rsp_add(ASE_ID(ase), BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE,
BT_BAP_ASCS_REASON_NONE);
return err;
}
stream = ep->stream;
err = ascs_verify_metadata(ep, meta, &rsp);
if (err != 0) {
LOG_DBG("Invalid metadata from client: %d", err);
/* rsp will be set by ascs_verify_metadata*/
} else if (unicast_server_cb != NULL && unicast_server_cb->enable != NULL) {
err = unicast_server_cb->enable(stream, meta->data, meta->len, &rsp);
} else {
err = -ENOTSUP;
rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
}
if (err) {
ascs_app_rsp_warn_valid(&rsp);
if (rsp.code == BT_BAP_ASCS_RSP_CODE_SUCCESS) {
rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
}
LOG_ERR("Enable rejected: err %d, code %u, reason %u", err, rsp.code, rsp.reason);
ascs_cp_rsp_add(ASE_ID(ase), rsp.code, rsp.reason);
return -EFAULT;
}
ep->codec_cfg.meta_len = meta->len;
(void)memcpy(ep->codec_cfg.meta, meta->data, meta->len);
ascs_ep_set_state(ep, BT_BAP_EP_STATE_ENABLING);
ascs_cp_rsp_success(ASE_ID(ase));
return 0;
}
static bool is_valid_enable_len(struct bt_conn *conn, struct net_buf_simple *buf)
{
const struct bt_ascs_enable_op *op;
struct net_buf_simple_state state;
net_buf_simple_save(buf, &state);
if (buf->len < sizeof(*op)) {
LOG_WRN("Invalid length %u < %zu", buf->len, sizeof(*op));
return false;
}
op = net_buf_simple_pull_mem(buf, sizeof(*op));
if (!is_valid_num_ases(conn, op->num_ases)) {
return false;
}
for (uint8_t i = 0U; i < op->num_ases; i++) {
const struct bt_ascs_metadata *metadata;
if (buf->len < sizeof(*metadata)) {
LOG_WRN("Malformed params array");
return false;
}
metadata = net_buf_simple_pull_mem(buf, sizeof(*metadata));
if (buf->len < metadata->len) {
LOG_WRN("Malformed metadata");
return false;
}
(void)net_buf_simple_pull_mem(buf, metadata->len);
}
if (buf->len > 0) {
LOG_WRN("Unexpected data");
return false;
}
net_buf_simple_restore(buf, &state);
return true;
}
static ssize_t ascs_enable(struct bt_conn *conn, struct net_buf_simple *buf)
{
const struct bt_ascs_enable_op *req;
struct bt_ascs_metadata *meta;
int i;
if (!is_valid_enable_len(conn, buf)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
req = net_buf_simple_pull_mem(buf, sizeof(*req));
LOG_DBG("num_ases %u", req->num_ases);
for (i = 0; i < req->num_ases; i++) {
struct bt_ascs_ase *ase;
meta = net_buf_simple_pull_mem(buf, sizeof(*meta));
(void)net_buf_simple_pull(buf, meta->len);
if (!is_valid_ase_id(meta->ase)) {
ascs_cp_rsp_add(meta->ase, BT_BAP_ASCS_RSP_CODE_INVALID_ASE,
BT_BAP_ASCS_REASON_NONE);
LOG_WRN("Unknown ase 0x%02x", meta->ase);
continue;
}
ase = ase_find(conn, meta->ase);
if (!ase) {
LOG_DBG("Invalid operation for idle ase 0x%02x", meta->ase);
ascs_cp_rsp_add(meta->ase, BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE,
BT_BAP_ASCS_REASON_NONE);
continue;
}
ase_enable(ase, meta);
}
return buf->size;
}
static void ase_start(struct bt_ascs_ase *ase)
{
struct bt_bap_ep *ep;
struct bt_bap_ascs_rsp rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS,
BT_BAP_ASCS_REASON_NONE);
int err;
LOG_DBG("ase %p", ase);
ep = &ase->ep;
/* Valid for an ASE only if ASE_State field = 0x02 (QoS Configured) */
if (ep->status.state != BT_BAP_EP_STATE_ENABLING) {
LOG_WRN("Invalid operation in state: %s", bt_bap_ep_state_str(ep->status.state));
ascs_cp_rsp_add(ASE_ID(ase), BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE,
BT_BAP_ASCS_REASON_NONE);
return;
}
if (ep->iso->chan.state != BT_ISO_STATE_CONNECTED) {
/* An ASE may not go into the streaming state unless the CIS
* is connected
*/
LOG_WRN("Start failed: CIS not connected: %u",
ep->iso->chan.state);
ascs_cp_rsp_add(ASE_ID(ase), BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE,
BT_BAP_ASCS_REASON_NONE);
return;
}
if (unicast_server_cb != NULL && unicast_server_cb->start != NULL) {
err = unicast_server_cb->start(ep->stream, &rsp);
} else {
err = -ENOTSUP;
rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
}
if (err) {
ascs_app_rsp_warn_valid(&rsp);
if (rsp.code == BT_BAP_ASCS_RSP_CODE_SUCCESS) {
rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
}
LOG_ERR("Start failed: err %d, code %u, reason %u", err, rsp.code, rsp.reason);
ascs_cp_rsp_add(ASE_ID(ase), rsp.code, rsp.reason);
return;
}
ep->receiver_ready = true;
ascs_ep_set_state(ep, BT_BAP_EP_STATE_STREAMING);
ascs_cp_rsp_success(ASE_ID(ase));
}
static bool is_valid_start_len(struct bt_conn *conn, struct net_buf_simple *buf)
{
const struct bt_ascs_start_op *op;
struct net_buf_simple_state state;
net_buf_simple_save(buf, &state);
if (buf->len < sizeof(*op)) {
LOG_WRN("Invalid length %u < %zu", buf->len, sizeof(*op));
return false;
}
op = net_buf_simple_pull_mem(buf, sizeof(*op));
if (!is_valid_num_ases(conn, op->num_ases)) {
return false;
}
if (buf->len != op->num_ases) {
LOG_WRN("Number_of_ASEs mismatch");
return false;
}
net_buf_simple_restore(buf, &state);
return true;
}
static ssize_t ascs_start(struct bt_conn *conn, struct net_buf_simple *buf)
{
const struct bt_ascs_start_op *req;
int i;
if (!is_valid_start_len(conn, buf)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
req = net_buf_simple_pull_mem(buf, sizeof(*req));
LOG_DBG("num_ases %u", req->num_ases);
for (i = 0; i < req->num_ases; i++) {
struct bt_ascs_ase *ase;
uint8_t id;
id = net_buf_simple_pull_u8(buf);
LOG_DBG("ase 0x%02x", id);
if (!is_valid_ase_id(id)) {
ascs_cp_rsp_add(id, BT_BAP_ASCS_RSP_CODE_INVALID_ASE,
BT_BAP_ASCS_REASON_NONE);
LOG_WRN("Unknown ase 0x%02x", id);
continue;
}
/* If the ASE_ID written by the client represents a Sink ASE, the
* server shall not accept the Receiver Start Ready operation for that
* ASE. The server shall send a notification of the ASE Control Point
* characteristic to the client, and the server shall set the
* Response_Code value for that ASE to 0x05 (Invalid ASE direction).
*/
if (ASE_DIR(id) == BT_AUDIO_DIR_SINK) {
LOG_WRN("Start failed: invalid operation for Sink");
ascs_cp_rsp_add(id, BT_BAP_ASCS_RSP_CODE_INVALID_DIR,
BT_BAP_ASCS_REASON_NONE);
continue;
}
ase = ase_find(conn, id);
if (!ase) {
LOG_DBG("Invalid operation for idle ASE");
ascs_cp_rsp_add(id, BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE,
BT_BAP_ASCS_REASON_NONE);
continue;
}
ase_start(ase);
}
return buf->size;
}
static bool is_valid_disable_len(struct bt_conn *conn, struct net_buf_simple *buf)
{
const struct bt_ascs_disable_op *op;
struct net_buf_simple_state state;
net_buf_simple_save(buf, &state);
if (buf->len < sizeof(*op)) {
LOG_WRN("Invalid length %u < %zu", buf->len, sizeof(*op));
return false;
}
op = net_buf_simple_pull_mem(buf, sizeof(*op));
if (!is_valid_num_ases(conn, op->num_ases)) {
return false;
}
if (buf->len != op->num_ases) {
LOG_WRN("Number_of_ASEs mismatch");
return false;
}
net_buf_simple_restore(buf, &state);
return true;
}
static ssize_t ascs_disable(struct bt_conn *conn, struct net_buf_simple *buf)
{
const struct bt_ascs_disable_op *req;
if (!is_valid_disable_len(conn, buf)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
req = net_buf_simple_pull_mem(buf, sizeof(*req));
LOG_DBG("num_ases %u", req->num_ases);
for (uint8_t i = 0; i < req->num_ases; i++) {
struct bt_bap_ascs_rsp rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
struct bt_ascs_ase *ase;
uint8_t id;
id = net_buf_simple_pull_u8(buf);
LOG_DBG("ase 0x%02x", id);
if (!is_valid_ase_id(id)) {
ascs_cp_rsp_add(id, BT_BAP_ASCS_RSP_CODE_INVALID_ASE,
BT_BAP_ASCS_REASON_NONE);
LOG_WRN("Unknown ase 0x%02x", id);
continue;
}
ase = ase_find(conn, id);
if (!ase) {
LOG_DBG("Invalid operation for idle ASE");
ascs_cp_rsp_add(id, BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE,
BT_BAP_ASCS_REASON_NONE);
continue;
}
ase_disable(ase, BT_HCI_ERR_REMOTE_USER_TERM_CONN, &rsp);
ascs_cp_rsp_add(id, rsp.code, rsp.reason);
}
return buf->size;
}
static void ase_stop(struct bt_ascs_ase *ase)
{
struct bt_bap_stream *stream;
struct bt_bap_ep *ep;
struct bt_bap_ascs_rsp rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS,
BT_BAP_ASCS_REASON_NONE);
int err;
LOG_DBG("ase %p", ase);
ep = &ase->ep;
if (ep->status.state != BT_BAP_EP_STATE_DISABLING) {
LOG_WRN("Invalid operation in state: %s", bt_bap_ep_state_str(ep->status.state));
ascs_cp_rsp_add(ASE_ID(ase), BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE,
BT_BAP_ASCS_REASON_NONE);
return;
}
stream = ep->stream;
if (unicast_server_cb != NULL && unicast_server_cb->stop != NULL) {
err = unicast_server_cb->stop(stream, &rsp);
} else {
err = -ENOTSUP;
rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
}
if (err) {
ascs_app_rsp_warn_valid(&rsp);
if (rsp.code == BT_BAP_ASCS_RSP_CODE_SUCCESS) {
rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
}
LOG_ERR("Stop failed: err %d, code %u, reason %u", err, rsp.code, rsp.reason);
ascs_cp_rsp_add(ASE_ID(ase), rsp.code, rsp.reason);
return;
}
/* If the Receiver Stop Ready operation has completed successfully the
* Unicast Client or the Unicast Server may terminate a CIS established
* for that ASE by following the Connected Isochronous Stream Terminate
* procedure defined in Volume 3, Part C, Section 9.3.15.
*/
if (bt_bap_stream_can_disconnect(stream)) {
err = ascs_disconnect_stream(stream);
if (err < 0) {
LOG_ERR("Failed to disconnect stream %p: %d", stream, err);
}
}
ascs_ep_set_state(ep, BT_BAP_EP_STATE_QOS_CONFIGURED);
ascs_cp_rsp_success(ASE_ID(ase));
}
static bool is_valid_stop_len(struct bt_conn *conn, struct net_buf_simple *buf)
{
const struct bt_ascs_stop_op *op;
struct net_buf_simple_state state;
net_buf_simple_save(buf, &state);
if (buf->len < sizeof(*op)) {
LOG_WRN("Invalid length %u < %zu", buf->len, sizeof(*op));
return false;
}
op = net_buf_simple_pull_mem(buf, sizeof(*op));
if (op->num_ases < 1U) {
LOG_WRN("Number_of_ASEs parameter value is less than 1");
return false;
}
if (buf->len != op->num_ases) {
LOG_WRN("Number_of_ASEs mismatch");
return false;
}
net_buf_simple_restore(buf, &state);
return true;
}
static ssize_t ascs_stop(struct bt_conn *conn, struct net_buf_simple *buf)
{
const struct bt_ascs_start_op *req;
int i;
if (!is_valid_stop_len(conn, buf)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
req = net_buf_simple_pull_mem(buf, sizeof(*req));
LOG_DBG("num_ases %u", req->num_ases);
for (i = 0; i < req->num_ases; i++) {
struct bt_ascs_ase *ase;
uint8_t id;
id = net_buf_simple_pull_u8(buf);
LOG_DBG("ase 0x%02x", id);
if (!is_valid_ase_id(id)) {
ascs_cp_rsp_add(id, BT_BAP_ASCS_RSP_CODE_INVALID_ASE,
BT_BAP_ASCS_REASON_NONE);
LOG_WRN("Unknown ase 0x%02x", id);
continue;
}
/* If the ASE_ID written by the client represents a Sink ASE, the
* server shall not accept the Receiver Stop Ready operation for that
* ASE. The server shall send a notification of the ASE Control Point
* characteristic to the client, and the server shall set the
* Response_Code value for that ASE to 0x05 (Invalid ASE direction).
*/
if (ASE_DIR(id) == BT_AUDIO_DIR_SINK) {
LOG_WRN("Stop failed: invalid operation for Sink");
ascs_cp_rsp_add(id, BT_BAP_ASCS_RSP_CODE_INVALID_DIR,
BT_BAP_ASCS_REASON_NONE);
continue;
}
ase = ase_find(conn, id);
if (!ase) {
LOG_DBG("Invalid operation for idle ASE");
ascs_cp_rsp_add(id, BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE,
BT_BAP_ASCS_REASON_NONE);
continue;
}
ase_stop(ase);
}
return buf->size;
}
static bool is_valid_metadata_len(struct bt_conn *conn, struct net_buf_simple *buf)
{
const struct bt_ascs_metadata_op *op;
struct net_buf_simple_state state;
net_buf_simple_save(buf, &state);
if (buf->len < sizeof(*op)) {
LOG_WRN("Invalid length %u < %zu", buf->len, sizeof(*op));
return false;
}
op = net_buf_simple_pull_mem(buf, sizeof(*op));
if (!is_valid_num_ases(conn, op->num_ases)) {
return false;
}
for (uint8_t i = 0U; i < op->num_ases; i++) {
const struct bt_ascs_metadata *metadata;
if (buf->len < sizeof(*metadata)) {
LOG_WRN("Malformed params array");
return false;
}
metadata = net_buf_simple_pull_mem(buf, sizeof(*metadata));
if (buf->len < metadata->len) {
LOG_WRN("Malformed metadata");
return false;
}
(void)net_buf_simple_pull_mem(buf, metadata->len);
}
if (buf->len > 0) {
LOG_WRN("Unexpected data");
return false;
}
net_buf_simple_restore(buf, &state);
return true;
}
static ssize_t ascs_metadata(struct bt_conn *conn, struct net_buf_simple *buf)
{
const struct bt_ascs_metadata_op *req;
struct bt_ascs_metadata *meta;
int i;
if (!is_valid_metadata_len(conn, buf)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
req = net_buf_simple_pull_mem(buf, sizeof(*req));
LOG_DBG("num_ases %u", req->num_ases);
for (i = 0; i < req->num_ases; i++) {
struct bt_ascs_ase *ase;
meta = net_buf_simple_pull_mem(buf, sizeof(*meta));
(void)net_buf_simple_pull(buf, meta->len);
if (meta->len > CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) {
LOG_DBG("Cannot store %u octets of metadata", meta->len);
ascs_cp_rsp_add(meta->ase, BT_BAP_ASCS_RSP_CODE_NO_MEM,
BT_BAP_ASCS_REASON_NONE);
continue;
}
if (!is_valid_ase_id(meta->ase)) {
ascs_cp_rsp_add(meta->ase, BT_BAP_ASCS_RSP_CODE_INVALID_ASE,
BT_BAP_ASCS_REASON_NONE);
LOG_WRN("Unknown ase 0x%02x", meta->ase);
continue;
}
ase = ase_find(conn, meta->ase);
if (!ase) {
LOG_DBG("Invalid operation for idle ase 0x%02x", meta->ase);
ascs_cp_rsp_add(meta->ase, BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE,
BT_BAP_ASCS_REASON_NONE);
continue;
}
ase_metadata(ase, meta);
}
return buf->size;
}
static bool is_valid_release_len(struct bt_conn *conn, struct net_buf_simple *buf)
{
const struct bt_ascs_release_op *op;
struct net_buf_simple_state state;
net_buf_simple_save(buf, &state);
if (buf->len < sizeof(*op)) {
LOG_WRN("Invalid length %u < %zu", buf->len, sizeof(*op));
return false;
}
op = net_buf_simple_pull_mem(buf, sizeof(*op));
if (!is_valid_num_ases(conn, op->num_ases)) {
return false;
}
if (buf->len != op->num_ases) {
LOG_WRN("Number_of_ASEs mismatch");
return false;
}
net_buf_simple_restore(buf, &state);
return true;
}
static ssize_t ascs_release(struct bt_conn *conn, struct net_buf_simple *buf)
{
const struct bt_ascs_release_op *req;
int i;
if (!is_valid_release_len(conn, buf)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
req = net_buf_simple_pull_mem(buf, sizeof(*req));
LOG_DBG("num_ases %u", req->num_ases);
for (i = 0; i < req->num_ases; i++) {
struct bt_bap_ascs_rsp rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED,
BT_BAP_ASCS_REASON_NONE);
struct bt_ascs_ase *ase;
uint8_t id;
id = net_buf_simple_pull_u8(buf);
LOG_DBG("ase 0x%02x", id);
if (!is_valid_ase_id(id)) {
ascs_cp_rsp_add(id, BT_BAP_ASCS_RSP_CODE_INVALID_ASE,
BT_BAP_ASCS_REASON_NONE);
LOG_WRN("Unknown ase 0x%02x", id);
continue;
}
ase = ase_find(conn, id);
if (!ase) {
LOG_DBG("Invalid operation for idle ASE");
ascs_cp_rsp_add(id, BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE,
BT_BAP_ASCS_REASON_NONE);
continue;
}
ase_release(ase, BT_HCI_ERR_REMOTE_USER_TERM_CONN, &rsp);
ascs_cp_rsp_add(id, rsp.code, rsp.reason);
}
return buf->size;
}
static ssize_t ascs_cp_write(struct bt_conn *conn,
const struct bt_gatt_attr *attr, const void *data,
uint16_t len, uint16_t offset, uint8_t flags)
{
const struct bt_ascs_ase_cp *req;
struct net_buf_simple buf;
ssize_t ret;
if (flags & BT_GATT_WRITE_FLAG_PREPARE) {
/* Return 0 to allow long writes */
return 0;
}
if (offset != 0 && (flags & BT_GATT_WRITE_FLAG_EXECUTE) == 0) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (len < sizeof(*req)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
net_buf_simple_init_with_data(&buf, (void *) data, len);
req = net_buf_simple_pull_mem(&buf, sizeof(*req));
LOG_DBG("conn %p attr %p buf %p len %u op %s (0x%02x)",
(void *)conn, attr, data, len, bt_ascs_op_str(req->op), req->op);
ascs_cp_rsp_init(req->op);
switch (req->op) {
case BT_ASCS_CONFIG_OP:
ret = ascs_config(conn, &buf);
break;
case BT_ASCS_QOS_OP:
ret = ascs_qos(conn, &buf);
break;
case BT_ASCS_ENABLE_OP:
ret = ascs_enable(conn, &buf);
break;
case BT_ASCS_START_OP:
ret = ascs_start(conn, &buf);
break;
case BT_ASCS_DISABLE_OP:
ret = ascs_disable(conn, &buf);
break;
case BT_ASCS_STOP_OP:
ret = ascs_stop(conn, &buf);
break;
case BT_ASCS_METADATA_OP:
ret = ascs_metadata(conn, &buf);
break;
case BT_ASCS_RELEASE_OP:
ret = ascs_release(conn, &buf);
break;
default:
ascs_cp_rsp_add(BT_ASCS_ASE_ID_NONE, BT_BAP_ASCS_RSP_CODE_NOT_SUPPORTED,
BT_BAP_ASCS_REASON_NONE);
LOG_DBG("Unknown opcode");
goto respond;
}
if (ret == BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN)) {
ascs_cp_rsp_add(BT_ASCS_ASE_ID_NONE, BT_BAP_ASCS_RSP_CODE_INVALID_LENGTH,
BT_BAP_ASCS_REASON_NONE);
}
respond:
control_point_notify(conn, cp_rsp_buf.data, cp_rsp_buf.len);
return len;
}
#define BT_ASCS_ASE_DEFINE(_uuid, _id) \
BT_AUDIO_CHRC(_uuid, \
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
BT_GATT_PERM_READ_ENCRYPT, \
ascs_ase_read, NULL, UINT_TO_POINTER(_id)), \
BT_AUDIO_CCC(ascs_ase_cfg_changed)
#define BT_ASCS_ASE_SNK_DEFINE(_n, ...) BT_ASCS_ASE_DEFINE(BT_UUID_ASCS_ASE_SNK, (_n) + 1)
#define BT_ASCS_ASE_SRC_DEFINE(_n, ...) BT_ASCS_ASE_DEFINE(BT_UUID_ASCS_ASE_SRC, (_n) + 1 + \
CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT)
#define BT_ASCS_CHR_ASE_CONTROL_POINT \
BT_AUDIO_CHRC(BT_UUID_ASCS_ASE_CP, \
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP | BT_GATT_CHRC_NOTIFY, \
BT_GATT_PERM_WRITE_ENCRYPT | BT_GATT_PERM_PREPARE_WRITE, \
NULL, ascs_cp_write, NULL), \
BT_AUDIO_CCC(ascs_cp_cfg_changed)
#if CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT > 0
#define BT_ASCS_ASE_SINKS \
LISTIFY(CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT, BT_ASCS_ASE_SNK_DEFINE, (,)),
#else
#define BT_ASCS_ASE_SINKS
#endif /* CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT > 0 */
#if CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT > 0
#define BT_ASCS_ASE_SOURCES \
LISTIFY(CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT, BT_ASCS_ASE_SRC_DEFINE, (,)),
#else
#define BT_ASCS_ASE_SOURCES
#endif /* CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT > 0 */
#define BT_ASCS_SERVICE_DEFINITION() { \
BT_GATT_PRIMARY_SERVICE(BT_UUID_ASCS), \
BT_AUDIO_CHRC(BT_UUID_ASCS_ASE_CP, \
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP | BT_GATT_CHRC_NOTIFY, \
BT_GATT_PERM_WRITE_ENCRYPT | BT_GATT_PERM_PREPARE_WRITE, \
NULL, ascs_cp_write, NULL), \
BT_AUDIO_CCC(ascs_cp_cfg_changed), \
BT_ASCS_ASE_SINKS \
BT_ASCS_ASE_SOURCES \
}
#define ASCS_ASE_CHAR_ATTR_COUNT 3 /* declaration + value + cccd */
static struct bt_gatt_attr ascs_attrs[] = BT_ASCS_SERVICE_DEFINITION();
static struct bt_gatt_service ascs_svc = (struct bt_gatt_service)BT_GATT_SERVICE(ascs_attrs);
static void configure_ase_char(uint8_t snk_cnt, uint8_t src_cnt)
{
uint8_t snk_ases_to_rem = CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT - snk_cnt;
uint8_t src_ases_to_rem = CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT - src_cnt;
size_t attrs_to_rem;
/* Remove the Source ASEs. The ones to remove will always be at the very tail of the
* attributes, so we just decrease the count withe the amount of sources we want to remove.
*/
attrs_to_rem = src_ases_to_rem * ASCS_ASE_CHAR_ATTR_COUNT;
ascs_svc.attr_count -= attrs_to_rem;
/* Remove the Sink ASEs.
*/
attrs_to_rem = snk_ases_to_rem * ASCS_ASE_CHAR_ATTR_COUNT;
if (CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT == 0 || CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT
== src_ases_to_rem) {
/* If there are no Source ASEs present, then we can just decrease the
* attribute count
*/
ascs_svc.attr_count -= attrs_to_rem;
} else {
/* As Source ASEs are present, we need to iterate backwards (as this will likely be
* the shortest distance). Find the first Sink to save, and move all Sources
* backwards to it.
*/
size_t src_start_idx = ascs_svc.attr_count - (src_cnt * ASCS_ASE_CHAR_ATTR_COUNT);
size_t new_src_start_idx = src_start_idx - (snk_ases_to_rem *
ASCS_ASE_CHAR_ATTR_COUNT);
for (size_t i = 0; i < src_cnt * ASCS_ASE_CHAR_ATTR_COUNT; i++) {
ascs_svc.attrs[new_src_start_idx + i] = ascs_svc.attrs[src_start_idx + i];
}
ascs_svc.attr_count -= attrs_to_rem;
}
}
int bt_ascs_register(uint8_t snk_cnt, uint8_t src_cnt)
{
int err = 0;
if (ascs.registered) {
LOG_DBG("ASCS already registered");
return -EALREADY;
}
if (snk_cnt > CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT ||
src_cnt > CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT) {
LOG_DBG("Provided ASE count above maximum");
return -EINVAL;
}
/* At least one ASE has been registered */
if (snk_cnt == 0 && src_cnt == 0) {
LOG_DBG("Can't register ASCS with zero ASEs");
return -EINVAL;
}
configure_ase_char(snk_cnt, src_cnt);
err = bt_gatt_service_register(&ascs_svc);
if (err != 0) {
LOG_DBG("Failed to register ASCS in gatt DB");
return err;
}
ascs.registered = true;
return err;
}
static int control_point_notify(struct bt_conn *conn, const void *data, uint16_t len)
{
return bt_gatt_notify_uuid(conn, BT_UUID_ASCS_ASE_CP, ascs_svc.attrs, data, len);
}
static struct bt_iso_server iso_server = {
.sec_level = BT_SECURITY_L2,
.accept = ascs_iso_accept,
};
int bt_ascs_init(const struct bt_bap_unicast_server_cb *cb)
{
int err;
if (!ascs.registered) {
return -ENOTSUP;
}
if (unicast_server_cb != NULL) {
return -EALREADY;
}
err = bt_iso_server_register(&iso_server);
if (err) {
LOG_ERR("Failed to register ISO server %d", err);
return err;
}
unicast_server_cb = cb;
return 0;
}
void bt_ascs_cleanup(void)
{
for (size_t i = 0; i < ARRAY_SIZE(ascs.ase_pool); i++) {
struct bt_ascs_ase *ase = &ascs.ase_pool[i];
if (ase->conn != NULL) {
bt_ascs_release_ase(&ase->ep);
}
}
if (unicast_server_cb != NULL) {
bt_iso_server_unregister(&iso_server);
unicast_server_cb = NULL;
}
}
int bt_ascs_unregister(void)
{
int err;
struct bt_gatt_attr _ascs_attrs[] = BT_ASCS_SERVICE_DEFINITION();
if (!ascs.registered) {
LOG_DBG("No ascs instance registered");
return -EALREADY;
}
for (size_t i = 0; i < ARRAY_SIZE(ascs.ase_pool); i++) {
if (ascs.ase_pool[i].ep.status.state != BT_BAP_EP_STATE_IDLE) {
return -EBUSY;
}
}
err = bt_gatt_service_unregister(&ascs_svc);
/* If unregistration was succesfull, make sure to reset ascs_attrs so it can be used for
* new registrations
*/
if (err != 0) {
LOG_DBG("Failed to unregister ASCS");
return err;
}
memcpy(&ascs_attrs, &_ascs_attrs, sizeof(struct bt_gatt_attr));
ascs.registered = false;
return err;
}
#endif /* BT_BAP_UNICAST_SERVER */