blob: eac88637e4bb0b97f5b8aa215e0b0cc0b0f36c5b [file] [log] [blame]
/* bap_base.c - BAP BASE handling */
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/iso.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/check.h>
LOG_MODULE_REGISTER(bt_bap_base, CONFIG_BT_BAP_BASE_LOG_LEVEL);
/* The BASE and the following defines are defined by BAP v1.0.1, section 3.7.2.2 Basic Audio
* Announcements
*/
#define BASE_MAX_SIZE (UINT8_MAX - 1 /* type */ - BT_UUID_SIZE_16)
#define BASE_CODEC_ID_SIZE (1 /* id */ + 2 /* cid */ + 2 /* vid */)
#define BASE_PD_SIZE 3
#define BASE_SUBGROUP_COUNT_SIZE 1
#define BASE_NUM_BIS_SIZE 1
#define BASE_CC_LEN_SIZE 1
#define BASE_META_LEN_SIZE 1
#define BASE_BIS_INDEX_SIZE 1
#define BASE_BIS_CC_LEN_SIZE 1
#define BASE_SUBGROUP_MAX_SIZE (BASE_MAX_SIZE - BASE_PD_SIZE - BASE_SUBGROUP_COUNT_SIZE)
#define BASE_SUBGROUP_MIN_SIZE \
(BASE_NUM_BIS_SIZE + BASE_CODEC_ID_SIZE + BASE_CC_LEN_SIZE + BASE_META_LEN_SIZE + \
BASE_BIS_INDEX_SIZE + BASE_BIS_CC_LEN_SIZE)
#define BASE_MIN_SIZE \
(BT_UUID_SIZE_16 + BASE_PD_SIZE + BASE_SUBGROUP_COUNT_SIZE + BASE_SUBGROUP_MIN_SIZE)
#define BASE_SUBGROUP_MAX_COUNT \
((BASE_MAX_SIZE - BASE_PD_SIZE - BASE_SUBGROUP_COUNT_SIZE) / BASE_SUBGROUP_MIN_SIZE)
static uint32_t base_pull_pd(struct net_buf_simple *net_buf)
{
return net_buf_simple_pull_le24(net_buf);
}
static uint8_t base_pull_bis_count(struct net_buf_simple *net_buf)
{
return net_buf_simple_pull_u8(net_buf);
}
static void base_pull_codec_id(struct net_buf_simple *net_buf,
struct bt_bap_base_codec_id *codec_id)
{
struct bt_bap_base_codec_id codec;
codec.id = net_buf_simple_pull_u8(net_buf); /* coding format */
codec.cid = net_buf_simple_pull_le16(net_buf); /* company id */
codec.vid = net_buf_simple_pull_le16(net_buf); /* VS codec id */
if (codec_id != NULL) {
*codec_id = codec;
}
}
static uint8_t base_pull_ltv(struct net_buf_simple *net_buf, uint8_t **data)
{
const uint8_t len = net_buf_simple_pull_u8(net_buf);
if (data == NULL) {
net_buf_simple_pull_mem(net_buf, len);
} else {
*data = net_buf_simple_pull_mem(net_buf, len);
}
return len;
}
static bool check_pull_ltv(struct net_buf_simple *net_buf)
{
uint8_t ltv_len;
if (net_buf->len < sizeof(ltv_len)) {
return false;
}
ltv_len = net_buf_simple_pull_u8(net_buf);
if (net_buf->len < ltv_len) {
return false;
}
net_buf_simple_pull_mem(net_buf, ltv_len);
return true;
}
const struct bt_bap_base *bt_bap_base_get_base_from_ad(const struct bt_data *ad)
{
struct bt_uuid_16 broadcast_uuid;
const struct bt_bap_base *base;
struct net_buf_simple net_buf;
uint8_t subgroup_count;
void *uuid;
CHECKIF(ad == NULL) {
LOG_DBG("data is NULL");
return NULL;
}
if (ad->type != BT_DATA_SVC_DATA16) {
LOG_DBG("Invalid type: %u", ad->type);
return NULL;
}
if (ad->data_len < BASE_MIN_SIZE) {
LOG_DBG("Invalid len: %u", ad->data_len);
return NULL;
}
net_buf_simple_init_with_data(&net_buf, (void *)ad->data, ad->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 NULL;
}
if (bt_uuid_cmp(&broadcast_uuid.uuid, BT_UUID_BASIC_AUDIO) != 0) {
LOG_DBG("Invalid UUID");
return NULL;
}
/* Store the start of the BASE */
base = (const struct bt_bap_base *)net_buf.data;
/* Pull all data to verify that the result BASE is valid */
base_pull_pd(&net_buf);
subgroup_count = net_buf_simple_pull_u8(&net_buf);
if (subgroup_count == 0 || subgroup_count > BASE_SUBGROUP_MAX_COUNT) {
LOG_DBG("Invalid subgroup count: %u", subgroup_count);
return NULL;
}
for (uint8_t i = 0U; i < subgroup_count; i++) {
uint8_t bis_count;
if (net_buf.len < sizeof(bis_count)) {
LOG_DBG("Invalid BASE length: %u", ad->data_len);
return NULL;
}
bis_count = base_pull_bis_count(&net_buf);
if (bis_count == 0 || bis_count > BT_ISO_MAX_GROUP_ISO_COUNT) {
LOG_DBG("Subgroup[%u]: Invalid BIS count: %u", i, bis_count);
return NULL;
}
if (net_buf.len < BASE_CODEC_ID_SIZE) {
LOG_DBG("Invalid BASE length: %u", ad->data_len);
return NULL;
}
base_pull_codec_id(&net_buf, NULL);
/* Pull CC */
if (!check_pull_ltv(&net_buf)) {
LOG_DBG("Invalid BASE length: %u", ad->data_len);
return NULL;
}
/* Pull meta */
if (!check_pull_ltv(&net_buf)) {
LOG_DBG("Invalid BASE length: %u", ad->data_len);
return NULL;
}
for (uint8_t j = 0U; j < bis_count; j++) {
uint8_t bis_index;
if (net_buf.len < sizeof(bis_index)) {
LOG_DBG("Invalid BASE length: %u", ad->data_len);
return NULL;
}
bis_index = net_buf_simple_pull_u8(&net_buf);
if (bis_index == 0 || bis_index > BT_ISO_BIS_INDEX_MAX) {
LOG_DBG("Subgroup[%u]: Invalid BIS index: %u", i, bis_index);
return NULL;
}
/* Pull BIS CC data */
if (!check_pull_ltv(&net_buf)) {
LOG_DBG("Invalid BASE length: %u", ad->data_len);
return NULL;
}
}
}
return base;
}
int bt_bap_base_get_pres_delay(const struct bt_bap_base *base)
{
struct net_buf_simple net_buf;
uint32_t pd;
CHECKIF(base == NULL) {
LOG_DBG("base is NULL");
return -EINVAL;
}
net_buf_simple_init_with_data(&net_buf, (void *)base, sizeof(pd));
pd = base_pull_pd(&net_buf);
return (int)pd; /* PD is 24-bit so it fits in an int */
}
int bt_bap_base_get_subgroup_count(const struct bt_bap_base *base)
{
struct net_buf_simple net_buf;
uint8_t subgroup_count;
CHECKIF(base == NULL) {
LOG_DBG("base is NULL");
return -EINVAL;
}
net_buf_simple_init_with_data(&net_buf, (void *)base, BASE_MAX_SIZE);
base_pull_pd(&net_buf);
subgroup_count = net_buf_simple_pull_u8(&net_buf);
return (int)subgroup_count; /* subgroup_count is 8-bit so it fits in an int */
}
int bt_bap_base_foreach_subgroup(const struct bt_bap_base *base,
bool (*func)(const struct bt_bap_base_subgroup *data,
void *user_data),
void *user_data)
{
struct bt_bap_base_subgroup *subgroup;
struct net_buf_simple net_buf;
uint8_t subgroup_count;
CHECKIF(base == NULL) {
LOG_DBG("base is NULL");
return -EINVAL;
}
CHECKIF(func == NULL) {
LOG_DBG("func is NULL");
return -EINVAL;
}
net_buf_simple_init_with_data(&net_buf, (void *)base, BASE_MAX_SIZE);
base_pull_pd(&net_buf);
subgroup_count = net_buf_simple_pull_u8(&net_buf);
for (uint8_t i = 0U; i < subgroup_count; i++) {
subgroup = (struct bt_bap_base_subgroup *)net_buf.data;
if (!func(subgroup, user_data)) {
LOG_DBG("user stopped parsing");
return -ECANCELED;
}
/* Parse subgroup data to get next subgroup pointer */
if (subgroup_count > 1) { /* Only parse data if it isn't the last one */
uint8_t bis_count;
bis_count = base_pull_bis_count(&net_buf);
base_pull_codec_id(&net_buf, NULL);
/* Codec config */
base_pull_ltv(&net_buf, NULL);
/* meta */
base_pull_ltv(&net_buf, NULL);
for (uint8_t j = 0U; j < bis_count; j++) {
net_buf_simple_pull_u8(&net_buf); /* index */
/* Codec config */
base_pull_ltv(&net_buf, NULL);
}
}
}
return 0;
}
int bt_bap_base_get_subgroup_codec_id(const struct bt_bap_base_subgroup *subgroup,
struct bt_bap_base_codec_id *codec_id)
{
struct net_buf_simple net_buf;
CHECKIF(subgroup == NULL) {
LOG_DBG("subgroup is NULL");
return -EINVAL;
}
CHECKIF(codec_id == NULL) {
LOG_DBG("codec_id is NULL");
return -EINVAL;
}
net_buf_simple_init_with_data(&net_buf, (void *)subgroup, BASE_SUBGROUP_MAX_SIZE);
base_pull_bis_count(&net_buf);
base_pull_codec_id(&net_buf, codec_id);
return 0;
}
int bt_bap_base_get_subgroup_codec_data(const struct bt_bap_base_subgroup *subgroup, uint8_t **data)
{
struct net_buf_simple net_buf;
CHECKIF(subgroup == NULL) {
LOG_DBG("subgroup is NULL");
return -EINVAL;
}
CHECKIF(data == NULL) {
LOG_DBG("data is NULL");
return -EINVAL;
}
net_buf_simple_init_with_data(&net_buf, (void *)subgroup, BASE_SUBGROUP_MAX_SIZE);
base_pull_bis_count(&net_buf);
base_pull_codec_id(&net_buf, NULL);
/* Codec config */
return base_pull_ltv(&net_buf, data);
}
int bt_bap_base_get_subgroup_codec_meta(const struct bt_bap_base_subgroup *subgroup, uint8_t **meta)
{
struct net_buf_simple net_buf;
CHECKIF(subgroup == NULL) {
LOG_DBG("subgroup is NULL");
return -EINVAL;
}
CHECKIF(meta == NULL) {
LOG_DBG("meta is NULL");
return -EINVAL;
}
net_buf_simple_init_with_data(&net_buf, (void *)subgroup, BASE_SUBGROUP_MAX_SIZE);
base_pull_bis_count(&net_buf);
base_pull_codec_id(&net_buf, NULL);
/* Codec config */
base_pull_ltv(&net_buf, NULL);
/* meta */
return base_pull_ltv(&net_buf, meta);
}
int bt_bap_base_subgroup_codec_to_codec_cfg(const struct bt_bap_base_subgroup *subgroup,
struct bt_audio_codec_cfg *codec_cfg)
{
struct bt_bap_base_codec_id codec_id;
struct net_buf_simple net_buf;
uint8_t *ltv_data;
uint8_t ltv_len;
CHECKIF(subgroup == NULL) {
LOG_DBG("subgroup is NULL");
return -EINVAL;
}
CHECKIF(codec_cfg == NULL) {
LOG_DBG("codec_cfg is NULL");
return -EINVAL;
}
net_buf_simple_init_with_data(&net_buf, (void *)subgroup, BASE_SUBGROUP_MAX_SIZE);
base_pull_bis_count(&net_buf);
base_pull_codec_id(&net_buf, &codec_id);
codec_cfg->id = codec_id.id;
codec_cfg->cid = codec_id.cid;
codec_cfg->vid = codec_id.vid;
/* Codec config */
ltv_len = base_pull_ltv(&net_buf, &ltv_data);
if (ltv_len > ARRAY_SIZE(codec_cfg->data)) {
LOG_DBG("Cannot fit %u octets of codec data (max %zu)", ltv_len,
ARRAY_SIZE(codec_cfg->data));
return -ENOMEM;
}
codec_cfg->data_len = ltv_len;
memcpy(codec_cfg->data, ltv_data, ltv_len);
/* Meta */
ltv_len = base_pull_ltv(&net_buf, &ltv_data);
if (ltv_len > ARRAY_SIZE(codec_cfg->meta)) {
LOG_DBG("Cannot fit %u octets of codec meta (max %zu)", ltv_len,
ARRAY_SIZE(codec_cfg->meta));
return -ENOMEM;
}
codec_cfg->meta_len = ltv_len;
memcpy(codec_cfg->meta, ltv_data, ltv_len);
return 0;
}
int bt_bap_base_get_subgroup_bis_count(const struct bt_bap_base_subgroup *subgroup)
{
struct net_buf_simple net_buf;
CHECKIF(subgroup == NULL) {
LOG_DBG("subgroup is NULL");
return -EINVAL;
}
net_buf_simple_init_with_data(&net_buf, (void *)subgroup, BASE_SUBGROUP_MAX_SIZE);
return base_pull_bis_count(&net_buf);
}
int bt_bap_base_subgroup_foreach_bis(const struct bt_bap_base_subgroup *subgroup,
bool (*func)(const struct bt_bap_base_subgroup_bis *subgroup,
void *user_data),
void *user_data)
{
struct net_buf_simple net_buf;
uint8_t bis_count;
CHECKIF(subgroup == NULL) {
LOG_DBG("subgroup is NULL");
return -EINVAL;
}
CHECKIF(func == NULL) {
LOG_DBG("func is NULL");
return -EINVAL;
}
net_buf_simple_init_with_data(&net_buf, (void *)subgroup, BASE_SUBGROUP_MAX_SIZE);
bis_count = base_pull_bis_count(&net_buf);
base_pull_codec_id(&net_buf, NULL);
/* Codec config */
base_pull_ltv(&net_buf, NULL);
/* meta */
base_pull_ltv(&net_buf, NULL);
for (uint8_t i = 0U; i < bis_count; i++) {
struct bt_bap_base_subgroup_bis bis;
bis.index = net_buf_simple_pull_u8(&net_buf); /* index */
/* Codec config */
bis.data_len = base_pull_ltv(&net_buf, &bis.data);
if (!func(&bis, user_data)) {
LOG_DBG("user stopped parsing");
return -ECANCELED;
}
}
return 0;
}
int bt_bap_base_subgroup_bis_codec_to_codec_cfg(const struct bt_bap_base_subgroup_bis *bis,
struct bt_audio_codec_cfg *codec_cfg)
{
CHECKIF(bis == NULL) {
LOG_DBG("bis is NULL");
return -EINVAL;
}
CHECKIF(codec_cfg == NULL) {
LOG_DBG("codec_cfg is NULL");
return -EINVAL;
}
if (bis->data_len > ARRAY_SIZE(codec_cfg->data)) {
LOG_DBG("Cannot fit %u octets of codec data (max %zu)", bis->data_len,
ARRAY_SIZE(codec_cfg->data));
return -ENOMEM;
}
codec_cfg->data_len = bis->data_len;
memcpy(codec_cfg->data, bis->data, bis->data_len);
return 0;
}
static bool base_subgroup_bis_cb(const struct bt_bap_base_subgroup_bis *bis, void *user_data)
{
uint32_t *base_bis_index_bitfield = user_data;
*base_bis_index_bitfield |= BIT(bis->index);
return true;
}
static bool base_subgroup_cb(const struct bt_bap_base_subgroup *subgroup, void *user_data)
{
const int err = bt_bap_base_subgroup_foreach_bis(subgroup, base_subgroup_bis_cb, user_data);
if (err != 0) {
LOG_DBG("Failed to parse all BIS: %d", err);
return false;
}
return true;
}
int bt_bap_base_get_bis_indexes(const struct bt_bap_base *base, uint32_t *bis_indexes)
{
CHECKIF(base == NULL) {
LOG_DBG("base is NULL");
return -EINVAL;
}
CHECKIF(bis_indexes == NULL) {
LOG_DBG("bis_indexes is NULL");
return -EINVAL;
}
*bis_indexes = 0U;
return bt_bap_base_foreach_subgroup(base, base_subgroup_cb, bis_indexes);
}