Refactor the bt_audio_codec_cfg to use flat arrays to store metadata and codec specific configurations. The purpose of this is to make it easier to copy the data between layers, but also to support non-LTV data for non-LC3 codec configurations. Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
366 lines
8.3 KiB
C
366 lines
8.3 KiB
C
/* Common functions for LE Audio services */
|
|
|
|
/*
|
|
* Copyright (c) 2022 Codecoup
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/bluetooth/att.h>
|
|
#include <zephyr/bluetooth/conn.h>
|
|
#include <zephyr/bluetooth/hci.h>
|
|
#include <zephyr/bluetooth/audio/audio.h>
|
|
#include <zephyr/bluetooth/audio/bap.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/sys/check.h>
|
|
|
|
#include "audio_internal.h"
|
|
|
|
LOG_MODULE_REGISTER(bt_audio, CONFIG_BT_AUDIO_LOG_LEVEL);
|
|
|
|
int bt_audio_data_parse(const uint8_t ltv[], size_t size,
|
|
bool (*func)(struct bt_data *data, void *user_data), void *user_data)
|
|
{
|
|
CHECKIF(ltv == NULL) {
|
|
LOG_DBG("ltv is NULL");
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(func == NULL) {
|
|
LOG_DBG("func is NULL");
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (size_t i = 0; i < size;) {
|
|
const uint8_t len = ltv[i++];
|
|
struct bt_data data;
|
|
|
|
if (i + len > size || len < sizeof(data.type)) {
|
|
LOG_DBG("Invalid len %u at i = %zu", len, i - 1);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
data.type = ltv[i++];
|
|
data.data_len = len - sizeof(data.type);
|
|
|
|
if (data.data_len > 0) {
|
|
data.data = <v[i];
|
|
} else {
|
|
data.data = NULL;
|
|
}
|
|
|
|
if (!func(&data, user_data)) {
|
|
return -ECANCELED;
|
|
}
|
|
|
|
/* Since we are incrementing i by the value_len, we don't need to increment it
|
|
* further in the `for` statement
|
|
*/
|
|
i += data.data_len;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_CONN)
|
|
|
|
static uint8_t bt_audio_security_check(const struct bt_conn *conn)
|
|
{
|
|
struct bt_conn_info info;
|
|
int err;
|
|
|
|
err = bt_conn_get_info(conn, &info);
|
|
if (err < 0) {
|
|
return BT_ATT_ERR_UNLIKELY;
|
|
}
|
|
|
|
/* Require an encryption key with at least 128 bits of entropy, derived from SC or OOB
|
|
* method.
|
|
*/
|
|
if ((info.security.flags & (BT_SECURITY_FLAG_OOB | BT_SECURITY_FLAG_SC)) == 0) {
|
|
/* If the client has insufficient security to read/write the requested attribute
|
|
* then an ATT_ERROR_RSP PDU shall be sent with the Error Code parameter set to
|
|
* Insufficient Authentication (0x05).
|
|
*/
|
|
return BT_ATT_ERR_AUTHENTICATION;
|
|
}
|
|
|
|
if (info.security.enc_key_size < BT_ENC_KEY_SIZE_MAX) {
|
|
return BT_ATT_ERR_ENCRYPTION_KEY_SIZE;
|
|
}
|
|
|
|
return BT_ATT_ERR_SUCCESS;
|
|
}
|
|
|
|
ssize_t bt_audio_read_chrc(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
void *buf, uint16_t len, uint16_t offset)
|
|
{
|
|
const struct bt_audio_attr_user_data *user_data = attr->user_data;
|
|
|
|
if (user_data->read == NULL) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_READ_NOT_PERMITTED);
|
|
}
|
|
|
|
if (conn != NULL) {
|
|
uint8_t err;
|
|
|
|
err = bt_audio_security_check(conn);
|
|
if (err != 0) {
|
|
return BT_GATT_ERR(err);
|
|
}
|
|
}
|
|
|
|
return user_data->read(conn, attr, buf, len, offset);
|
|
}
|
|
|
|
ssize_t bt_audio_write_chrc(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
|
|
{
|
|
const struct bt_audio_attr_user_data *user_data = attr->user_data;
|
|
|
|
if (user_data->write == NULL) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_WRITE_NOT_PERMITTED);
|
|
}
|
|
|
|
if (conn != NULL) {
|
|
uint8_t err;
|
|
|
|
err = bt_audio_security_check(conn);
|
|
if (err != 0) {
|
|
return BT_GATT_ERR(err);
|
|
}
|
|
}
|
|
|
|
return user_data->write(conn, attr, buf, len, offset, flags);
|
|
}
|
|
|
|
ssize_t bt_audio_ccc_cfg_write(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
uint16_t value)
|
|
{
|
|
if (conn != NULL) {
|
|
uint8_t err;
|
|
|
|
err = bt_audio_security_check(conn);
|
|
if (err != 0) {
|
|
return BT_GATT_ERR(err);
|
|
}
|
|
}
|
|
|
|
return sizeof(value);
|
|
}
|
|
#endif /* CONFIG_BT_CONN */
|
|
|
|
/* Broadcast sink depends on Scan Delegator, so we can just guard it with the Scan Delegator */
|
|
#if defined(CONFIG_BT_BAP_SCAN_DELEGATOR)
|
|
|
|
static int decode_bis_data(struct net_buf_simple *buf, struct bt_bap_base_bis_data *bis)
|
|
{
|
|
uint8_t len;
|
|
|
|
if (buf->len < BT_BAP_BASE_BIS_DATA_MIN_SIZE) {
|
|
LOG_DBG("Not enough bytes (%u) to decode BIS data", buf->len);
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
bis->index = net_buf_simple_pull_u8(buf);
|
|
if (!IN_RANGE(bis->index, BT_ISO_BIS_INDEX_MIN, BT_ISO_BIS_INDEX_MAX)) {
|
|
LOG_DBG("Invalid BIS index %u", bis->index);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* codec config data length */
|
|
len = net_buf_simple_pull_u8(buf);
|
|
if (len > buf->len) {
|
|
LOG_DBG("Invalid BIS specific codec config data length: %u (buf is %u)", len,
|
|
buf->len);
|
|
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
if (len > 0) {
|
|
#if CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0
|
|
void *ltv_data;
|
|
|
|
if (len > sizeof(bis->data)) {
|
|
LOG_DBG("Cannot store codec config data of length %u", len);
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ltv_data = net_buf_simple_pull_mem(buf, len);
|
|
|
|
bis->data_len = len;
|
|
memcpy(bis->data, ltv_data, len);
|
|
#else /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE == 0 */
|
|
LOG_DBG("Cannot store codec config data");
|
|
|
|
return -ENOMEM;
|
|
#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int decode_subgroup(struct net_buf_simple *buf, struct bt_bap_base_subgroup *subgroup)
|
|
{
|
|
struct bt_audio_codec_cfg *codec_cfg;
|
|
uint8_t len;
|
|
|
|
codec_cfg = &subgroup->codec_cfg;
|
|
|
|
subgroup->bis_count = net_buf_simple_pull_u8(buf);
|
|
if (subgroup->bis_count > ARRAY_SIZE(subgroup->bis_data)) {
|
|
LOG_DBG("BASE has more BIS %u than we support %u", subgroup->bis_count,
|
|
(uint8_t)ARRAY_SIZE(subgroup->bis_data));
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
codec_cfg->id = net_buf_simple_pull_u8(buf);
|
|
codec_cfg->cid = net_buf_simple_pull_le16(buf);
|
|
codec_cfg->vid = net_buf_simple_pull_le16(buf);
|
|
|
|
/* codec configuration data length */
|
|
len = net_buf_simple_pull_u8(buf);
|
|
if (len > buf->len) {
|
|
LOG_DBG("Invalid codec config data length: %u (buf is %u)", len, buf->len);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
#if CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE > 0
|
|
void *cfg_ltv_data;
|
|
|
|
if (len > sizeof(subgroup->codec_cfg.data)) {
|
|
LOG_DBG("Cannot store codec config data of length %u", len);
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
cfg_ltv_data = net_buf_simple_pull_mem(buf, len);
|
|
|
|
subgroup->codec_cfg.data_len = len;
|
|
memcpy(subgroup->codec_cfg.data, cfg_ltv_data, len);
|
|
#else /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE == 0 */
|
|
if (len > 0) {
|
|
LOG_DBG("Cannot store codec config data of length %u", len);
|
|
|
|
return -ENOMEM;
|
|
}
|
|
#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE > 0 */
|
|
|
|
/* codec metadata length */
|
|
len = net_buf_simple_pull_u8(buf);
|
|
if (len > buf->len) {
|
|
LOG_DBG("Invalid codec config data length: %u (buf is %u)", len, buf->len);
|
|
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
#if CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0
|
|
void *meta_ltv_data;
|
|
|
|
if (len > sizeof(subgroup->codec_cfg.meta)) {
|
|
LOG_DBG("Cannot store codec config meta of length %u", len);
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
meta_ltv_data = net_buf_simple_pull_mem(buf, len);
|
|
|
|
subgroup->codec_cfg.meta_len = len;
|
|
memcpy(subgroup->codec_cfg.meta, meta_ltv_data, len);
|
|
#else /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE == 0 */
|
|
if (len > 0) {
|
|
LOG_DBG("Cannot store metadata");
|
|
|
|
return -ENOMEM;
|
|
}
|
|
#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE */
|
|
|
|
for (size_t i = 0U; i < subgroup->bis_count; i++) {
|
|
const int err = decode_bis_data(buf, &subgroup->bis_data[i]);
|
|
|
|
if (err != 0) {
|
|
LOG_DBG("Failed to decode BIS data for bis[%zu]: %d", i, err);
|
|
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_bap_decode_base(struct bt_data *data, struct bt_bap_base *base)
|
|
{
|
|
struct bt_uuid_16 broadcast_uuid;
|
|
struct net_buf_simple net_buf;
|
|
void *uuid;
|
|
|
|
CHECKIF(data == NULL) {
|
|
LOG_DBG("data is NULL");
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(base == NULL) {
|
|
LOG_DBG("base is NULL");
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (data->type != BT_DATA_SVC_DATA16) {
|
|
LOG_DBG("Invalid type: %u", data->type);
|
|
|
|
return -ENOMSG;
|
|
}
|
|
|
|
if (data->data_len < BT_BAP_BASE_MIN_SIZE) {
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
net_buf_simple_init_with_data(&net_buf, (void *)data->data,
|
|
data->data_len);
|
|
|
|
uuid = net_buf_simple_pull_mem(&net_buf, BT_UUID_SIZE_16);
|
|
if (!bt_uuid_create(&broadcast_uuid.uuid, uuid, BT_UUID_SIZE_16)) {
|
|
LOG_ERR("bt_uuid_create failed");
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (bt_uuid_cmp(&broadcast_uuid.uuid, BT_UUID_BASIC_AUDIO) != 0) {
|
|
LOG_DBG("Invalid UUID");
|
|
|
|
return -ENOMSG;
|
|
}
|
|
|
|
base->pd = net_buf_simple_pull_le24(&net_buf);
|
|
base->subgroup_count = net_buf_simple_pull_u8(&net_buf);
|
|
|
|
if (base->subgroup_count > ARRAY_SIZE(base->subgroups)) {
|
|
LOG_DBG("Cannot decode BASE with %u subgroups (max supported is %zu)",
|
|
base->subgroup_count, ARRAY_SIZE(base->subgroups));
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (size_t i = 0U; i < base->subgroup_count; i++) {
|
|
const int err = decode_subgroup(&net_buf, &base->subgroups[i]);
|
|
|
|
if (err != 0) {
|
|
LOG_DBG("Failed to decode subgroup[%zu]: %d", i, err);
|
|
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_BT_BAP_SCAN_DELEGATOR */
|