689e3216fc
Add bt_bap_base_subgroup_get_bis_indexes that gets the BIS indexes of a subgroup. This work very similar to bt_bap_base_get_bis_indexes, except that it works for subgroups rather than BASEs. Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
586 lines
13 KiB
C
586 lines
13 KiB
C
/* 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, <v_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, <v_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_subgroup_get_bis_indexes(const struct bt_bap_base_subgroup *subgroup,
|
|
uint32_t *bis_indexes)
|
|
{
|
|
CHECKIF(subgroup == NULL) {
|
|
LOG_DBG("subgroup is NULL");
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(bis_indexes == NULL) {
|
|
LOG_DBG("bis_indexes is NULL");
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
*bis_indexes = 0U;
|
|
|
|
return bt_bap_base_subgroup_foreach_bis(subgroup, base_subgroup_bis_cb, bis_indexes);
|
|
}
|
|
|
|
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);
|
|
}
|