| /* bap_base.c - BAP BASE handling */ |
| |
| /* |
| * Copyright (c) 2023 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <errno.h> |
| #include <stdbool.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <string.h> |
| |
| #include <zephyr/autoconf.h> |
| #include <zephyr/bluetooth/audio/audio.h> |
| #include <zephyr/bluetooth/audio/bap.h> |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/gap.h> |
| #include <zephyr/bluetooth/iso.h> |
| #include <zephyr/bluetooth/uuid.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/net/buf.h> |
| #include <zephyr/sys/check.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/sys/util_macro.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_size(const struct bt_bap_base *base) |
| { |
| struct net_buf_simple net_buf; |
| uint8_t subgroup_count; |
| size_t size = 0; |
| |
| 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); |
| size += BASE_PD_SIZE; |
| subgroup_count = net_buf_simple_pull_u8(&net_buf); |
| size += BASE_SUBGROUP_COUNT_SIZE; |
| |
| /* Parse subgroup data */ |
| for (uint8_t i = 0U; i < subgroup_count; i++) { |
| uint8_t bis_count; |
| uint8_t len; |
| |
| bis_count = base_pull_bis_count(&net_buf); |
| size += BASE_NUM_BIS_SIZE; |
| |
| base_pull_codec_id(&net_buf, NULL); |
| size += BASE_CODEC_ID_SIZE; |
| |
| /* Codec config */ |
| len = base_pull_ltv(&net_buf, NULL); |
| size += len + BASE_CC_LEN_SIZE; |
| |
| /* meta */ |
| len = base_pull_ltv(&net_buf, NULL); |
| size += len + BASE_META_LEN_SIZE; |
| |
| /* Parse BIS data */ |
| for (uint8_t j = 0U; j < bis_count; j++) { |
| /* BIS index */ |
| net_buf_simple_pull_u8(&net_buf); |
| size += BASE_BIS_INDEX_SIZE; |
| |
| /* Codec config */ |
| len = base_pull_ltv(&net_buf, NULL); |
| size += len + BASE_BIS_CC_LEN_SIZE; |
| } |
| } |
| |
| return (int)size; |
| } |
| |
| 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); |
| } |