blob: 5f872171daf9970c4587a13a64975c192351f84f [file] [log] [blame]
/* Bluetooth Audio Broadcast Sink */
/*
* Copyright (c) 2021-2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/check.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/pacs.h>
#include <zephyr/bluetooth/audio/bap.h>
#include "../host/conn_internal.h"
#include "../host/iso_internal.h"
#include "bap_iso.h"
#include "bap_endpoint.h"
#include "audio_internal.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_bap_broadcast_sink, CONFIG_BT_BAP_BROADCAST_SINK_LOG_LEVEL);
#include "common/bt_str.h"
#define PA_SYNC_SKIP 5
#define SYNC_RETRY_COUNT 6 /* similar to retries for connections */
#define BROADCAST_SYNC_MIN_INDEX (BIT(1))
/* any value above 0xFFFFFF is invalid, so we can just use 0xFFFFFFFF to denote
* invalid broadcast ID
*/
#define INVALID_BROADCAST_ID 0xFFFFFFFF
static struct bt_bap_ep broadcast_sink_eps[CONFIG_BT_BAP_BROADCAST_SNK_COUNT]
[BROADCAST_SNK_STREAM_CNT];
static struct bt_bap_broadcast_sink broadcast_sinks[CONFIG_BT_BAP_BROADCAST_SNK_COUNT];
static struct bt_le_scan_cb broadcast_scan_cb;
struct codec_lookup_id_data {
uint8_t id;
struct bt_codec *codec;
};
static sys_slist_t sink_cbs = SYS_SLIST_STATIC_INIT(&sink_cbs);
static void broadcast_sink_cleanup(struct bt_bap_broadcast_sink *sink);
static enum bt_bap_scan_delegator_iter
find_recv_state_by_sink_cb(const struct bt_bap_scan_delegator_recv_state *recv_state,
void *user_data)
{
const struct bt_bap_broadcast_sink *sink = user_data;
if (atomic_test_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID) &&
sink->bass_src_id == recv_state->src_id) {
return BT_BAP_SCAN_DELEGATOR_ITER_STOP;
}
return BT_BAP_SCAN_DELEGATOR_ITER_CONTINUE;
}
static enum bt_bap_scan_delegator_iter
find_recv_state_by_pa_sync_cb(const struct bt_bap_scan_delegator_recv_state *recv_state,
void *user_data)
{
struct bt_le_per_adv_sync *sync = user_data;
struct bt_le_per_adv_sync_info sync_info;
int err;
err = bt_le_per_adv_sync_get_info(sync, &sync_info);
if (err != 0) {
LOG_DBG("Failed to get sync info: %d", err);
return BT_BAP_SCAN_DELEGATOR_ITER_CONTINUE;
}
if (bt_addr_le_eq(&recv_state->addr, &sync_info.addr) &&
recv_state->adv_sid == sync_info.sid) {
return BT_BAP_SCAN_DELEGATOR_ITER_STOP;
}
return BT_BAP_SCAN_DELEGATOR_ITER_CONTINUE;
};
static void update_recv_state_big_synced(const struct bt_bap_broadcast_sink *sink)
{
const struct bt_bap_scan_delegator_recv_state *recv_state;
struct bt_bap_scan_delegator_mod_src_param mod_src_param = { 0 };
const struct bt_bap_base *base;
int err;
recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_sink_cb, (void *)sink);
if (recv_state == NULL) {
LOG_WRN("Failed to find receive state for sink %p", sink);
return;
}
base = &sink->base;
mod_src_param.num_subgroups = base->subgroup_count;
for (uint8_t i = 0U; i < base->subgroup_count; i++) {
struct bt_bap_scan_delegator_subgroup *subgroup_param = &mod_src_param.subgroups[i];
const struct bt_bap_base_subgroup *subgroup = &base->subgroups[i];
/* Update the BIS sync indexes for the subgroup */
for (size_t j = 0U; j < subgroup->bis_count; j++) {
const struct bt_bap_base_bis_data *bis_data = &subgroup->bis_data[j];
subgroup_param->bis_sync |= BIT(bis_data->index);
}
}
if (recv_state->encrypt_state == BT_BAP_BIG_ENC_STATE_BCODE_REQ) {
mod_src_param.encrypt_state = BT_BAP_BIG_ENC_STATE_DEC;
} else {
mod_src_param.encrypt_state = recv_state->encrypt_state;
}
/* Since the mod_src_param struct is 0-initialized the metadata won't
* be modified by this
*/
/* Copy existing unchanged data */
mod_src_param.src_id = recv_state->src_id;
mod_src_param.broadcast_id = recv_state->broadcast_id;
err = bt_bap_scan_delegator_mod_src(&mod_src_param);
if (err != 0) {
LOG_WRN("Failed to modify Receive State for sink %p: %d", sink, err);
}
}
static void update_recv_state_big_cleared(const struct bt_bap_broadcast_sink *sink,
uint8_t reason)
{
struct bt_bap_scan_delegator_mod_src_param mod_src_param = { 0 };
const struct bt_bap_scan_delegator_recv_state *recv_state;
int err;
recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_sink_cb, (void *)sink);
if (recv_state == NULL) {
LOG_WRN("Failed to find receive state for sink %p", sink);
return;
}
if (recv_state->encrypt_state == BT_BAP_BIG_ENC_STATE_BCODE_REQ &&
reason == BT_HCI_ERR_TERM_DUE_TO_MIC_FAIL) {
/* Sync failed due to bad broadcast code */
mod_src_param.encrypt_state = BT_BAP_BIG_ENC_STATE_BAD_CODE;
} else {
mod_src_param.encrypt_state = recv_state->encrypt_state;
}
/* BIS syncs will be automatically cleared since the mod_src_param
* struct is 0-initialized
*
* Since the metadata_len is also 0, then the metadata won't be
* modified by the operation either.
*/
/* Copy existing unchanged data */
mod_src_param.num_subgroups = recv_state->num_subgroups;
mod_src_param.src_id = recv_state->src_id;
mod_src_param.broadcast_id = recv_state->broadcast_id;
err = bt_bap_scan_delegator_mod_src(&mod_src_param);
if (err != 0) {
LOG_WRN("Failed to modify Receive State for sink %p: %d",
sink, err);
}
}
static void broadcast_sink_clear_big(struct bt_bap_broadcast_sink *sink,
uint8_t reason)
{
sink->big = NULL;
update_recv_state_big_cleared(sink, reason);
}
static struct bt_bap_broadcast_sink *broadcast_sink_lookup_iso_chan(
const struct bt_iso_chan *chan)
{
for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sinks); i++) {
for (uint8_t j = 0U; j < broadcast_sinks[i].stream_count; j++) {
if (broadcast_sinks[i].bis[j] == chan) {
return &broadcast_sinks[i];
}
}
}
return NULL;
}
static void broadcast_sink_set_ep_state(struct bt_bap_ep *ep, uint8_t state)
{
uint8_t old_state;
old_state = ep->status.state;
LOG_DBG("ep %p id 0x%02x %s -> %s", ep, ep->status.id, bt_bap_ep_state_str(old_state),
bt_bap_ep_state_str(state));
switch (old_state) {
case BT_BAP_EP_STATE_IDLE:
if (state != BT_BAP_EP_STATE_QOS_CONFIGURED) {
LOG_DBG("Invalid broadcast sync endpoint state transition");
return;
}
break;
case BT_BAP_EP_STATE_QOS_CONFIGURED:
if (state != BT_BAP_EP_STATE_IDLE && state != BT_BAP_EP_STATE_STREAMING) {
LOG_DBG("Invalid broadcast sync endpoint state transition");
return;
}
break;
case BT_BAP_EP_STATE_STREAMING:
if (state != BT_BAP_EP_STATE_IDLE) {
LOG_DBG("Invalid broadcast sync endpoint state transition");
return;
}
break;
default:
LOG_ERR("Invalid broadcast sync endpoint state: %s",
bt_bap_ep_state_str(old_state));
return;
}
ep->status.state = state;
if (state == BT_BAP_EP_STATE_IDLE) {
struct bt_bap_stream *stream = ep->stream;
if (stream != NULL) {
bt_bap_iso_unbind_ep(ep->iso, ep);
stream->ep = NULL;
stream->codec = NULL;
ep->stream = NULL;
}
}
}
static void broadcast_sink_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 = iso->rx.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 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");
}
}
static void broadcast_sink_iso_connected(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_broadcast_sink *sink;
struct bt_bap_stream *stream;
struct bt_bap_ep *ep = iso->rx.ep;
bool all_connected;
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;
LOG_DBG("stream %p", stream);
sink = broadcast_sink_lookup_iso_chan(chan);
if (sink == NULL) {
LOG_ERR("Could not lookup sink by iso %p", chan);
return;
}
broadcast_sink_set_ep_state(ep, BT_BAP_EP_STATE_STREAMING);
if (ops != NULL && ops->started != NULL) {
ops->started(stream);
} else {
LOG_WRN("No callback for connected set");
}
all_connected = true;
SYS_SLIST_FOR_EACH_CONTAINER(&sink->streams, stream, _node) {
__ASSERT(stream->ep, "Endpoint is NULL");
if (stream->ep->status.state != BT_BAP_EP_STATE_STREAMING) {
all_connected = false;
break;
}
}
if (all_connected) {
update_recv_state_big_synced(sink);
}
}
static void broadcast_sink_iso_disconnected(struct bt_iso_chan *chan,
uint8_t reason)
{
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 = iso->rx.ep;
struct bt_bap_broadcast_sink *sink;
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;
LOG_DBG("stream %p ep %p reason 0x%02x", stream, ep, reason);
broadcast_sink_set_ep_state(ep, BT_BAP_EP_STATE_IDLE);
if (ops != NULL && ops->stopped != NULL) {
ops->stopped(stream, reason);
} else {
LOG_WRN("No callback for stopped set");
}
sink = broadcast_sink_lookup_iso_chan(chan);
if (sink == NULL) {
LOG_ERR("Could not lookup sink by iso %p", chan);
return;
}
if (!sys_slist_find_and_remove(&sink->streams, &stream->_node)) {
LOG_DBG("Could not find and remove stream %p from sink %p", stream, sink);
}
/* Clear sink->big if not already cleared */
if (sys_slist_is_empty(&sink->streams) && sink->big) {
broadcast_sink_clear_big(sink, reason);
}
}
static struct bt_iso_chan_ops broadcast_sink_iso_ops = {
.recv = broadcast_sink_iso_recv,
.connected = broadcast_sink_iso_connected,
.disconnected = broadcast_sink_iso_disconnected,
};
static struct bt_bap_broadcast_sink *broadcast_sink_syncing_get(void)
{
for (int i = 0; i < ARRAY_SIZE(broadcast_sinks); i++) {
if (atomic_test_bit(broadcast_sinks[i].flags,
BT_BAP_BROADCAST_SINK_FLAG_SYNCING)) {
return &broadcast_sinks[i];
}
}
return NULL;
}
static struct bt_bap_broadcast_sink *broadcast_sink_scanning_get(void)
{
for (int i = 0; i < ARRAY_SIZE(broadcast_sinks); i++) {
if (atomic_test_bit(broadcast_sinks[i].flags,
BT_BAP_BROADCAST_SINK_FLAG_SCANNING)) {
return &broadcast_sinks[i];
}
}
return NULL;
}
static struct bt_bap_broadcast_sink *broadcast_sink_free_get(void)
{
/* Find free entry */
for (int i = 0; i < ARRAY_SIZE(broadcast_sinks); i++) {
if (!atomic_test_bit(broadcast_sinks[i].flags,
BT_BAP_BROADCAST_SINK_FLAG_INITIALIZED)) {
broadcast_sinks[i].index = i;
broadcast_sinks[i].broadcast_id = INVALID_BROADCAST_ID;
return &broadcast_sinks[i];
}
}
return NULL;
}
static struct bt_bap_broadcast_sink *broadcast_sink_get_by_pa(struct bt_le_per_adv_sync *sync)
{
for (int i = 0; i < ARRAY_SIZE(broadcast_sinks); i++) {
if (broadcast_sinks[i].pa_sync == sync) {
return &broadcast_sinks[i];
}
}
return NULL;
}
static struct bt_bap_broadcast_sink *broadcast_sink_get_by_broadcast_id(uint32_t broadcast_id)
{
for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sinks); i++) {
if (broadcast_sinks[i].broadcast_id == broadcast_id) {
return &broadcast_sinks[i];
}
}
return NULL;
}
static void broadcast_sink_add_src(struct bt_bap_broadcast_sink *sink)
{
struct bt_bap_scan_delegator_add_src_param add_src_param;
int err;
add_src_param.pa_sync = sink->pa_sync;
add_src_param.broadcast_id = sink->broadcast_id;
/* Will be updated when we receive the BASE */
add_src_param.encrypt_state = BT_BAP_BIG_ENC_STATE_NO_ENC;
add_src_param.num_subgroups = 0U;
err = bt_bap_scan_delegator_add_src(&add_src_param);
if (err < 0) {
LOG_WRN("Failed to add sync as Receive State for sink %p: %d",
sink, err);
} else {
sink->bass_src_id = (uint8_t)err;
atomic_set_bit(sink->flags,
BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID);
}
}
static void handle_past_sync(struct bt_le_per_adv_sync *sync)
{
const struct bt_bap_scan_delegator_recv_state *recv_state;
recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_pa_sync_cb, (void *)sync);
if (recv_state != NULL) {
/* If we receive a PAST transfer that fits a
* known BASS Receive State, then we create it
* as a Broadcast Sink
*
* The PA state in the Receive State will be
* updated by the Scan Delegator
*/
int err;
err = bt_bap_broadcast_sink_create(sync, recv_state->broadcast_id);
if (err != 0) {
LOG_WRN("Failed to create Broadcast Sink: %d", err);
}
}
}
static void pa_synced(struct bt_le_per_adv_sync *sync,
struct bt_le_per_adv_sync_synced_info *info)
{
const struct bt_bap_scan_delegator_recv_state *recv_state;
struct bt_bap_broadcast_sink_cb *listener;
struct bt_bap_broadcast_sink *sink;
int err;
sink = broadcast_sink_syncing_get();
if (sink == NULL || sync != sink->pa_sync) {
if (info->conn) { /* PAST */
handle_past_sync(sync);
} else {
/* Not ours */
}
return;
}
LOG_DBG("Synced to broadcast source with ID 0x%06X", sink->broadcast_id);
atomic_clear_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_SYNCING);
err = bt_bap_broadcast_sink_scan_stop();
if (err != 0 && err != -EALREADY) {
LOG_WRN("Failed to stop sink scan: %d", err);
/* Even if we cannot stop scanning here, we can still move on */
}
/* Add the PA sync to the scan delegator or modify if it already exists */
recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_pa_sync_cb, (void *)sync);
if (recv_state == NULL) {
broadcast_sink_add_src(sink);
} else {
/* Set PA sync state */
err = bt_bap_scan_delegator_set_pa_state(recv_state->src_id,
BT_BAP_PA_STATE_SYNCED);
if (err != 0) {
LOG_WRN("Failed to set PA state: %d", err);
}
}
SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) {
if (listener->pa_synced != NULL) {
listener->pa_synced(sink, sink->pa_sync, sink->broadcast_id);
}
}
/* TBD: What if sync to a bad broadcast source that does not send
* properly formatted (or any) BASE?
*/
}
static void pa_term(struct bt_le_per_adv_sync *sync,
const struct bt_le_per_adv_sync_term_info *info)
{
struct bt_bap_broadcast_sink_cb *listener;
struct bt_bap_broadcast_sink *sink;
sink = broadcast_sink_get_by_pa(sync);
if (sink == NULL) {
/* Not ours */
return;
}
LOG_DBG("PA sync with broadcast source with ID 0x%06X lost", sink->broadcast_id);
if (sink->big != NULL) {
const int err = bt_iso_big_terminate(sink->big);
if (err != 0) {
LOG_ERR("Failed to disconnect BIG sync: %d", err);
}
}
broadcast_sink_cleanup(sink);
SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) {
if (listener->pa_sync_lost != NULL) {
listener->pa_sync_lost(sink);
}
}
}
static void update_recv_state_base_copy_meta(const struct bt_bap_base *base,
struct bt_bap_scan_delegator_mod_src_param *param)
{
for (uint8_t i = 0U; i < base->subgroup_count; i++) {
struct bt_bap_scan_delegator_subgroup *subgroup_param = &param->subgroups[i];
const struct bt_bap_base_subgroup *subgroup = &base->subgroups[i];
uint8_t *metadata_param = subgroup_param->metadata;
size_t total_len;
/* Copy metadata into subgroup_param, changing it from an array
* of bt_codec_data to a uint8_t buffer
*/
total_len = 0U;
for (size_t j = 0; j < subgroup->codec.meta_count; j++) {
const struct bt_codec_data *meta = &subgroup->codec.meta[j];
const struct bt_data *data = &meta->data;
const uint8_t len = data->data_len;
const uint8_t type = data->type;
const size_t ltv_len = sizeof(len) + sizeof(type) + len;
if (total_len + ltv_len > sizeof(subgroup_param->metadata)) {
LOG_WRN("Could not fit entire metadata for subgroup[%u]", i);
return;
}
metadata_param[total_len++] = len + 1;
metadata_param[total_len++] = type;
(void)memcpy(&metadata_param[total_len], data->data,
len);
total_len += len;
}
subgroup_param->metadata_len = total_len;
}
}
static void update_recv_state_base(const struct bt_bap_broadcast_sink *sink)
{
struct bt_bap_scan_delegator_mod_src_param mod_src_param = { 0 };
const struct bt_bap_scan_delegator_recv_state *recv_state;
const struct bt_bap_base *base;
int err;
recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_sink_cb, (void *)sink);
if (recv_state == NULL) {
LOG_WRN("Failed to find receive state for sink %p", sink);
return;
}
base = &sink->base;
mod_src_param.num_subgroups = base->subgroup_count;
update_recv_state_base_copy_meta(base, &mod_src_param);
/* Copy existing unchanged data */
mod_src_param.src_id = recv_state->src_id;
mod_src_param.encrypt_state = recv_state->encrypt_state;
mod_src_param.broadcast_id = recv_state->broadcast_id;
err = bt_bap_scan_delegator_mod_src(&mod_src_param);
if (err != 0) {
LOG_WRN("Failed to modify Receive State for sink %p: %d", sink, err);
}
}
static bool pa_decode_base(struct bt_data *data, void *user_data)
{
struct bt_bap_broadcast_sink *sink = (struct bt_bap_broadcast_sink *)user_data;
struct bt_bap_broadcast_sink_cb *listener;
struct bt_bap_base base = { 0 };
if (data->type != BT_DATA_SVC_DATA16) {
return true;
}
if (data->data_len < BT_BAP_BASE_MIN_SIZE) {
return true;
}
if (bt_bap_decode_base(data, &base) != 0) {
return false;
}
if (atomic_test_bit(sink->flags,
BT_BAP_BROADCAST_SINK_FLAG_BIGINFO_RECEIVED)) {
uint8_t num_bis = 0;
for (int i = 0; i < base.subgroup_count; i++) {
num_bis += base.subgroups[i].bis_count;
}
if (num_bis > sink->biginfo_num_bis) {
LOG_WRN("BASE contains more BIS than reported by BIGInfo");
return false;
}
}
sink->codec_qos.pd = base.pd;
if (memcmp(&sink->base, &base, sizeof(base)) != 0) {
/* We only overwrite the sink->base data once the base has
* successfully been decoded to avoid overwriting it with
* invalid data
*/
(void)memcpy(&sink->base, &base, sizeof(base));
if (atomic_test_bit(sink->flags,
BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID)) {
update_recv_state_base(sink);
}
}
SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) {
if (listener->base_recv != NULL) {
listener->base_recv(sink, &base);
}
}
return false;
}
static void pa_recv(struct bt_le_per_adv_sync *sync,
const struct bt_le_per_adv_sync_recv_info *info,
struct net_buf_simple *buf)
{
struct bt_bap_broadcast_sink *sink = broadcast_sink_get_by_pa(sync);
if (sink == NULL) {
/* Not a PA sync that we control */
return;
}
if (sys_slist_is_empty(&sink_cbs)) {
/* Terminate early if we do not have any broadcast sink listeners */
return;
}
bt_data_parse(buf, pa_decode_base, (void *)sink);
}
static void update_recv_state_encryption(const struct bt_bap_broadcast_sink *sink)
{
struct bt_bap_scan_delegator_mod_src_param mod_src_param = { 0 };
const struct bt_bap_scan_delegator_recv_state *recv_state;
int err;
__ASSERT(sink->big == NULL, "Encryption state shall not be updated while synced");
recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_sink_cb, (void *)sink);
if (recv_state == NULL) {
LOG_WRN("Failed to find receive state for sink %p", sink);
return;
}
/* Only change the encrypt state, and leave the rest as is */
if (atomic_test_bit(sink->flags,
BT_BAP_BROADCAST_SINK_FLAG_BIG_ENCRYPTED)) {
mod_src_param.encrypt_state = BT_BAP_BIG_ENC_STATE_BCODE_REQ;
} else {
mod_src_param.encrypt_state = BT_BAP_BIG_ENC_STATE_NO_ENC;
}
if (mod_src_param.encrypt_state == recv_state->encrypt_state) {
/* No change, abort*/
return;
}
/* Copy existing data */
/* TODO: Maybe we need more refined functions to set only specific fields? */
mod_src_param.src_id = recv_state->src_id;
mod_src_param.broadcast_id = recv_state->broadcast_id;
mod_src_param.num_subgroups = recv_state->num_subgroups;
(void)memcpy(mod_src_param.subgroups,
recv_state->subgroups,
sizeof(recv_state->num_subgroups));
err = bt_bap_scan_delegator_mod_src(&mod_src_param);
if (err != 0) {
LOG_WRN("Failed to modify Receive State for sink %p: %d", sink, err);
}
}
static void biginfo_recv(struct bt_le_per_adv_sync *sync,
const struct bt_iso_biginfo *biginfo)
{
struct bt_bap_broadcast_sink_cb *listener;
struct bt_bap_broadcast_sink *sink;
sink = broadcast_sink_get_by_pa(sync);
if (sink == NULL) {
/* Not ours */
return;
}
if (sink->big != NULL) {
/* Already synced - ignore */
return;
}
atomic_set_bit(sink->flags,
BT_BAP_BROADCAST_SINK_FLAG_BIGINFO_RECEIVED);
sink->iso_interval = biginfo->iso_interval;
sink->biginfo_num_bis = biginfo->num_bis;
if (biginfo->encryption != atomic_test_bit(sink->flags,
BT_BAP_BROADCAST_SINK_FLAG_BIG_ENCRYPTED)) {
atomic_set_bit_to(sink->flags,
BT_BAP_BROADCAST_SINK_FLAG_BIG_ENCRYPTED,
biginfo->encryption);
if (atomic_test_bit(sink->flags,
BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID)) {
update_recv_state_encryption(sink);
}
}
sink->codec_qos.framing = biginfo->framing;
sink->codec_qos.phy = biginfo->phy;
sink->codec_qos.sdu = biginfo->max_sdu;
sink->codec_qos.interval = biginfo->sdu_interval;
SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) {
if (listener->syncable != NULL) {
listener->syncable(sink, biginfo->encryption);
}
}
}
static uint16_t interval_to_sync_timeout(uint16_t interval)
{
uint32_t interval_ms;
uint16_t timeout;
/* Ensure that the following calculation does not overflow silently */
__ASSERT(SYNC_RETRY_COUNT < 10, "SYNC_RETRY_COUNT shall be less than 10");
/* Add retries and convert to unit in 10's of ms */
interval_ms = BT_GAP_PER_ADV_INTERVAL_TO_MS(interval);
timeout = (interval_ms * SYNC_RETRY_COUNT) / 10;
/* Enforce restraints */
timeout = CLAMP(timeout, BT_GAP_PER_ADV_MIN_TIMEOUT,
BT_GAP_PER_ADV_MAX_TIMEOUT);
return timeout;
}
static void sync_broadcast_pa(const struct bt_le_scan_recv_info *info,
uint32_t broadcast_id)
{
struct bt_bap_broadcast_sink_cb *listener;
struct bt_le_per_adv_sync_param param;
struct bt_bap_broadcast_sink *sink;
int err;
sink = broadcast_sink_scanning_get();
/* Should never happen as we set the scanning flag before registering
* the scanning callbacks
*/
__ASSERT(sink != NULL, "sink is NULL");
/* Unregister the callbacks to prevent broadcast_scan_recv to be called again */
bt_le_scan_cb_unregister(&broadcast_scan_cb);
err = bt_le_scan_stop();
if (err != 0) {
LOG_ERR("Could not stop scan: %d", err);
} else {
atomic_clear_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_SCANNING);
}
bt_addr_le_copy(&param.addr, info->addr);
param.options = 0;
param.sid = info->sid;
param.skip = PA_SYNC_SKIP;
param.timeout = interval_to_sync_timeout(info->interval);
err = bt_le_per_adv_sync_create(&param, &sink->pa_sync);
if (err != 0) {
LOG_ERR("Could not sync to PA: %d", err);
broadcast_sink_cleanup(sink);
SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) {
if (listener->scan_term != NULL) {
listener->scan_term(err);
}
}
} else {
atomic_set_bit(sink->flags,
BT_BAP_BROADCAST_SINK_FLAG_SYNCING);
sink->broadcast_id = broadcast_id;
}
}
static bool scan_check_and_sync_broadcast(struct bt_data *data, void *user_data)
{
uint32_t *broadcast_id = user_data;
struct bt_uuid_16 adv_uuid;
if (sys_slist_is_empty(&sink_cbs)) {
/* Terminate early if we do not have any broadcast sink listeners */
return false;
}
if (data->type != BT_DATA_SVC_DATA16) {
return true;
}
if (data->data_len < BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE) {
return true;
}
if (!bt_uuid_create(&adv_uuid.uuid, data->data, BT_UUID_SIZE_16)) {
return true;
}
if (bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_BROADCAST_AUDIO)) {
return true;
}
if (broadcast_sink_syncing_get() != NULL) {
/* Already syncing, can maximum sync one */
return true;
}
*broadcast_id = sys_get_le24(data->data + BT_UUID_SIZE_16);
/* Stop parsing */
return false;
}
static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info,
struct net_buf_simple *ad)
{
struct bt_bap_broadcast_sink_cb *listener;
struct net_buf_simple_state state;
uint32_t broadcast_id;
/* We are only interested in non-connectable periodic advertisers */
if ((info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) ||
info->interval == 0) {
return;
}
/* As scan_check_and_sync_broadcast modifies the AD data,
* we store the state before parsing it
*/
net_buf_simple_save(ad, &state);
broadcast_id = INVALID_BROADCAST_ID;
bt_data_parse(ad, scan_check_and_sync_broadcast, (void *)&broadcast_id);
net_buf_simple_restore(ad, &state);
/* We check if `broadcast_id` was modified by `scan_check_and_sync_broadcast`.
* If it was then that means that we found a broadcast source
*/
if (broadcast_id != INVALID_BROADCAST_ID) {
LOG_DBG("Found broadcast source with address %s and id 0x%06X",
bt_addr_le_str(info->addr), broadcast_id);
if (broadcast_sink_get_by_broadcast_id(broadcast_id) != NULL) {
LOG_DBG("Broadcast sink with broadcast_id 0x%X already exists",
broadcast_id);
return;
}
SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) {
if (listener->scan_recv != NULL) {
bool sync_pa;
/* As the callback receiver may modify the AD
* data, we store the state so that we can
* restore it for each callback
*/
net_buf_simple_save(ad, &state);
sync_pa = listener->scan_recv(info, ad, broadcast_id);
if (sync_pa) {
sync_broadcast_pa(info, broadcast_id);
break;
}
net_buf_simple_restore(ad, &state);
}
}
}
}
static void broadcast_scan_timeout(void)
{
struct bt_bap_broadcast_sink_cb *listener;
struct bt_bap_broadcast_sink *sink;
bt_le_scan_cb_unregister(&broadcast_scan_cb);
sink = broadcast_sink_scanning_get();
/* Should never happen as we set the scanning flag before registering
* the scanning callbacks
*/
__ASSERT(sink != NULL, "sink is NULL");
broadcast_sink_cleanup(sink);
SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) {
if (listener->scan_term != NULL) {
listener->scan_term(-ETIME);
}
}
}
int bt_bap_broadcast_sink_register_cb(struct bt_bap_broadcast_sink_cb *cb)
{
CHECKIF(cb == NULL) {
LOG_DBG("cb is NULL");
return -EINVAL;
}
sys_slist_append(&sink_cbs, &cb->_node);
return 0;
}
int bt_bap_broadcast_sink_scan_start(const struct bt_le_scan_param *param)
{
struct bt_bap_broadcast_sink *sink;
int err;
CHECKIF(param == NULL) {
LOG_DBG("param is NULL");
return -EINVAL;
}
CHECKIF(param->timeout != 0) {
/* This is to avoid having to re-implement the scan timeout
* callback as well, and can be modified later if requested
*/
LOG_DBG("Scan param shall not have a timeout");
return -EINVAL;
}
if (sys_slist_is_empty(&sink_cbs)) {
LOG_WRN("No broadcast sink callbacks registered");
return -EINVAL;
}
if (broadcast_sink_scanning_get() != NULL) {
LOG_DBG("Already scanning");
return -EALREADY;
}
sink = broadcast_sink_free_get();
if (sink == NULL) {
LOG_DBG("No more free broadcast sinks");
return -ENOMEM;
}
/* TODO: check for scan callback */
err = bt_le_scan_start(param, NULL);
if (err == 0) {
atomic_set_bit(sink->flags,
BT_BAP_BROADCAST_SINK_FLAG_INITIALIZED);
atomic_set_bit(sink->flags,
BT_BAP_BROADCAST_SINK_FLAG_SCANNING);
broadcast_scan_cb.recv = broadcast_scan_recv;
broadcast_scan_cb.timeout = broadcast_scan_timeout;
bt_le_scan_cb_register(&broadcast_scan_cb);
}
return err;
}
int bt_bap_broadcast_sink_scan_stop(void)
{
struct bt_bap_broadcast_sink_cb *listener;
struct bt_bap_broadcast_sink *sink;
int err;
sink = broadcast_sink_scanning_get();
if (sink == NULL) {
LOG_DBG("Not scanning");
return -EALREADY;
}
if (sink->pa_sync != NULL) {
err = bt_le_per_adv_sync_delete(sink->pa_sync);
if (err != 0) {
LOG_DBG("Could not delete PA sync: %d", err);
return err;
}
}
broadcast_sink_cleanup(sink);
err = bt_le_scan_stop();
if (err == 0) {
bt_le_scan_cb_unregister(&broadcast_scan_cb);
}
SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) {
if (listener->scan_term != NULL) {
listener->scan_term(err);
}
}
return err;
}
bool bt_bap_ep_is_broadcast_snk(const struct bt_bap_ep *ep)
{
for (int i = 0; i < ARRAY_SIZE(broadcast_sink_eps); i++) {
if (PART_OF_ARRAY(broadcast_sink_eps[i], ep)) {
return true;
}
}
return false;
}
static void broadcast_sink_ep_init(struct bt_bap_ep *ep)
{
LOG_DBG("ep %p", ep);
(void)memset(ep, 0, sizeof(*ep));
ep->dir = BT_AUDIO_DIR_SINK;
ep->iso = NULL;
}
static struct bt_bap_ep *broadcast_sink_new_ep(uint8_t index)
{
for (size_t i = 0; i < ARRAY_SIZE(broadcast_sink_eps[index]); i++) {
struct bt_bap_ep *ep = &broadcast_sink_eps[index][i];
/* If ep->stream is NULL the endpoint is unallocated */
if (ep->stream == NULL) {
broadcast_sink_ep_init(ep);
return ep;
}
}
return NULL;
}
static int bt_bap_broadcast_sink_setup_stream(struct bt_bap_broadcast_sink *sink,
struct bt_bap_stream *stream, struct bt_codec *codec)
{
struct bt_bap_iso *iso;
struct bt_bap_ep *ep;
if (stream->group != NULL) {
LOG_DBG("Stream %p already in group %p", stream, stream->group);
return -EALREADY;
}
ep = broadcast_sink_new_ep(sink->index);
if (ep == NULL) {
LOG_DBG("Could not allocate new broadcast endpoint");
return -ENOMEM;
}
iso = bt_bap_iso_new();
if (iso == NULL) {
LOG_DBG("Could not allocate iso");
return -ENOMEM;
}
bt_bap_iso_init(iso, &broadcast_sink_iso_ops);
bt_bap_iso_bind_ep(iso, ep);
bt_audio_codec_qos_to_iso_qos(iso->chan.qos->rx, &sink->codec_qos);
bt_audio_codec_to_iso_path(iso->chan.qos->rx->path, codec);
bt_bap_iso_unref(iso);
bt_bap_stream_attach(NULL, stream, ep, codec);
stream->qos = &sink->codec_qos;
return 0;
}
static void broadcast_sink_cleanup_streams(struct bt_bap_broadcast_sink *sink)
{
struct bt_bap_stream *stream, *next;
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&sink->streams, stream, next, _node) {
if (stream->ep != NULL) {
bt_bap_iso_unbind_ep(stream->ep->iso, stream->ep);
stream->ep->stream = NULL;
stream->ep = NULL;
}
stream->qos = NULL;
stream->codec = NULL;
stream->group = NULL;
sys_slist_remove(&sink->streams, NULL, &stream->_node);
}
sink->stream_count = 0;
}
static void broadcast_sink_cleanup(struct bt_bap_broadcast_sink *sink)
{
if (atomic_test_bit(sink->flags,
BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID)) {
int err;
err = bt_bap_scan_delegator_rem_src(sink->bass_src_id);
if (err != 0) {
LOG_WRN("Failed to remove Receive State for sink %p: %d",
sink, err);
}
}
if (sink->stream_count > 0U) {
broadcast_sink_cleanup_streams(sink);
}
(void)memset(sink, 0, sizeof(*sink)); /* also clears flags */
}
static struct bt_codec *codec_from_base_by_index(struct bt_bap_base *base, uint8_t index)
{
for (size_t i = 0U; i < base->subgroup_count; i++) {
struct bt_bap_base_subgroup *subgroup = &base->subgroups[i];
for (size_t j = 0U; j < subgroup->bis_count; j++) {
if (subgroup->bis_data[j].index == index) {
return &subgroup->codec;
}
}
}
return NULL;
}
static bool codec_lookup_id(const struct bt_pacs_cap *cap, void *user_data)
{
struct codec_lookup_id_data *data = user_data;
if (cap->codec->id == data->id) {
data->codec = cap->codec;
return false;
}
return true;
}
int bt_bap_broadcast_sink_create(struct bt_le_per_adv_sync *pa_sync, uint32_t broadcast_id)
{
const struct bt_bap_scan_delegator_recv_state *recv_state;
struct bt_bap_broadcast_sink_cb *listener;
struct bt_bap_broadcast_sink *sink;
CHECKIF(pa_sync == NULL) {
LOG_DBG("pa_sync is NULL");
return -EINVAL;
}
CHECKIF(broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) {
LOG_DBG("Invalid broadcast_id: 0x%X", broadcast_id);
return -EINVAL;
}
if (broadcast_sink_get_by_broadcast_id(broadcast_id) != NULL) {
LOG_DBG("Broadcast sink with broadcast_id 0x%X already exists",
broadcast_id);
return -EALREADY;
}
sink = broadcast_sink_free_get();
if (sink == NULL) {
LOG_DBG("No more free broadcast sinks");
return -ENOMEM;
}
recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_pa_sync_cb,
(void *)pa_sync);
if (recv_state == NULL) {
broadcast_sink_add_src(sink);
} else {
/* The PA sync is known by the Scan Delegator */
if (recv_state->broadcast_id != broadcast_id) {
LOG_DBG("Broadcast ID mismatch: 0x%X != 0x%X",
recv_state->broadcast_id, broadcast_id);
return -EINVAL;
}
sink->bass_src_id = recv_state->src_id;
}
sink->broadcast_id = broadcast_id;
sink->pa_sync = pa_sync;
atomic_set_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_INITIALIZED);
SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) {
if (listener->pa_synced != NULL) {
listener->pa_synced(sink, sink->pa_sync,
sink->broadcast_id);
}
}
return 0;
}
int bt_bap_broadcast_sink_sync(struct bt_bap_broadcast_sink *sink, uint32_t indexes_bitfield,
struct bt_bap_stream *streams[], const uint8_t broadcast_code[16])
{
struct bt_iso_big_sync_param param;
struct bt_codec *codecs[BROADCAST_SNK_STREAM_CNT] = { NULL };
uint8_t stream_count;
int err;
CHECKIF(sink == NULL) {
LOG_DBG("sink is NULL");
return -EINVAL;
}
CHECKIF(indexes_bitfield == 0) {
LOG_DBG("indexes_bitfield is 0");
return -EINVAL;
}
CHECKIF(indexes_bitfield & BIT(0)) {
LOG_DBG("BIT(0) is not a valid BIS index");
return -EINVAL;
}
CHECKIF(streams == NULL) {
LOG_DBG("streams is NULL");
return -EINVAL;
}
if (sink->pa_sync == NULL) {
LOG_DBG("Sink is not PA synced");
return -EINVAL;
}
if (!atomic_test_bit(sink->flags,
BT_BAP_BROADCAST_SINK_FLAG_BIGINFO_RECEIVED)) {
/* TODO: We could store the request to sync and start the sync
* once the BIGInfo has been received, and then do the sync
* then. This would be similar how LE Create Connection works.
*/
LOG_DBG("BIGInfo not received, cannot sync yet");
return -EAGAIN;
}
if (atomic_test_bit(sink->flags,
BT_BAP_BROADCAST_SINK_FLAG_BIG_ENCRYPTED) &&
broadcast_code == NULL) {
LOG_DBG("Broadcast code required");
return -EINVAL;
}
/* Validate that number of bits set is less than number of streams */
stream_count = 0;
for (int i = 1; i < BT_ISO_MAX_GROUP_ISO_COUNT; i++) {
if ((indexes_bitfield & BIT(i)) != 0) {
struct bt_codec *codec = codec_from_base_by_index(&sink->base, i);
struct codec_lookup_id_data lookup_data = { };
if (codec == NULL) {
LOG_DBG("Index %d not found in BASE", i);
return -EINVAL;
}
/* Lookup and assign path_id based on capabilities */
lookup_data.id = codec->id;
bt_pacs_cap_foreach(BT_AUDIO_DIR_SINK, codec_lookup_id,
&lookup_data);
if (lookup_data.codec == NULL) {
LOG_DBG("Codec with id %u is not supported by our capabilities",
codec->id);
return -ENOENT;
}
codec->path_id = lookup_data.codec->path_id;
codecs[stream_count++] = codec;
if (stream_count > BROADCAST_SNK_STREAM_CNT) {
LOG_DBG("Cannot sync to more than %d streams",
BROADCAST_SNK_STREAM_CNT);
return -EINVAL;
}
}
}
for (size_t i = 0; i < stream_count; i++) {
CHECKIF(streams[i] == NULL) {
LOG_DBG("streams[%zu] is NULL", i);
return -EINVAL;
}
}
sink->stream_count = 0U;
for (size_t i = 0; i < stream_count; i++) {
struct bt_bap_stream *stream;
struct bt_codec *codec;
stream = streams[i];
codec = codecs[i];
err = bt_bap_broadcast_sink_setup_stream(sink, stream, codec);
if (err != 0) {
LOG_DBG("Failed to setup streams[%zu]: %d", i, err);
broadcast_sink_cleanup_streams(sink);
return err;
}
sink->bis[i] = bt_bap_stream_iso_chan_get(stream);
sys_slist_append(&sink->streams, &stream->_node);
sink->stream_count++;
}
param.bis_channels = sink->bis;
param.num_bis = sink->stream_count;
param.bis_bitfield = indexes_bitfield;
param.mse = 0; /* Let controller decide */
param.sync_timeout = interval_to_sync_timeout(sink->iso_interval);
param.encryption = atomic_test_bit(sink->flags,
BT_BAP_BROADCAST_SINK_FLAG_BIG_ENCRYPTED);
if (param.encryption) {
memcpy(param.bcode, broadcast_code, sizeof(param.bcode));
} else {
memset(param.bcode, 0, sizeof(param.bcode));
}
err = bt_iso_big_sync(sink->pa_sync, &param, &sink->big);
if (err != 0) {
broadcast_sink_cleanup_streams(sink);
return err;
}
for (size_t i = 0; i < stream_count; i++) {
struct bt_bap_ep *ep = streams[i]->ep;
ep->broadcast_sink = sink;
broadcast_sink_set_ep_state(ep, BT_BAP_EP_STATE_QOS_CONFIGURED);
}
return 0;
}
int bt_bap_broadcast_sink_stop(struct bt_bap_broadcast_sink *sink)
{
struct bt_bap_stream *stream;
sys_snode_t *head_node;
int err;
CHECKIF(sink == NULL) {
LOG_DBG("sink is NULL");
return -EINVAL;
}
if (sys_slist_is_empty(&sink->streams)) {
LOG_DBG("Source does not have any streams (already stopped)");
return -EALREADY;
}
head_node = sys_slist_peek_head(&sink->streams);
stream = CONTAINER_OF(head_node, struct bt_bap_stream, _node);
/* All streams in a broadcast source is in the same state,
* so we can just check the first stream
*/
if (stream->ep == NULL) {
LOG_DBG("stream->ep is NULL");
return -EINVAL;
}
if (stream->ep->status.state != BT_BAP_EP_STATE_STREAMING &&
stream->ep->status.state != BT_BAP_EP_STATE_QOS_CONFIGURED) {
LOG_DBG("Broadcast sink stream %p invalid state: %u", stream,
stream->ep->status.state);
return -EBADMSG;
}
err = bt_iso_big_terminate(sink->big);
if (err) {
LOG_DBG("Failed to terminate BIG (err %d)", err);
return err;
}
broadcast_sink_clear_big(sink, BT_HCI_ERR_LOCALHOST_TERM_CONN);
/* Channel states will be updated in the broadcast_sink_iso_disconnected function */
return 0;
}
int bt_bap_broadcast_sink_delete(struct bt_bap_broadcast_sink *sink)
{
int err;
CHECKIF(sink == NULL) {
LOG_DBG("sink is NULL");
return -EINVAL;
}
if (!sys_slist_is_empty(&sink->streams)) {
struct bt_bap_stream *stream;
sys_snode_t *head_node;
head_node = sys_slist_peek_head(&sink->streams);
stream = CONTAINER_OF(head_node, struct bt_bap_stream, _node);
/* All streams in a broadcast source is in the same state,
* so we can just check the first stream
*/
if (stream->ep != NULL) {
LOG_DBG("Sink is not stopped");
return -EBADMSG;
}
}
if (sink->pa_sync == NULL) {
LOG_DBG("Broadcast sink is already deleted");
return -EALREADY;
}
err = bt_le_per_adv_sync_delete(sink->pa_sync);
if (err != 0) {
LOG_DBG("Failed to delete periodic advertising sync (err %d)", err);
return err;
}
/* Reset the broadcast sink */
broadcast_sink_cleanup(sink);
return 0;
}
static int broadcast_sink_init(void)
{
static struct bt_le_per_adv_sync_cb cb = {
.synced = pa_synced,
.recv = pa_recv,
.term = pa_term,
.biginfo = biginfo_recv
};
bt_le_per_adv_sync_cb_register(&cb);
return 0;
}
SYS_INIT(broadcast_sink_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);