@@ -234,6 +234,7 @@ shared_sources = src/shared/io.h src/shared/timeout.h \
src/shared/mcs.h src/shared/mcp.h src/shared/mcp.c \
src/shared/vcp.c src/shared/vcp.h \
src/shared/csip.c src/shared/csip.h \
+ src/shared/bass.h src/shared/bass.c \
src/shared/lc3.h src/shared/tty.h
if READLINE
new file mode 100644
@@ -0,0 +1,1012 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright 2023 NXP
+ *
+ */
+
+#define _GNU_SOURCE
+#include <inttypes.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "lib/bluetooth.h"
+#include "lib/uuid.h"
+
+#include "src/shared/queue.h"
+#include "src/shared/util.h"
+#include "src/shared/att.h"
+#include "src/shared/gatt-db.h"
+#include "src/shared/gatt-client.h"
+#include "src/shared/bass.h"
+
+#define DBG(_bass, fmt, arg...) \
+ bass_debug(_bass, "%s:%s() " fmt, __FILE__, __func__, ## arg)
+
+struct bt_bass_db;
+
+struct bt_bass_cb {
+ unsigned int id;
+ bt_bass_func_t attached;
+ bt_bass_func_t detached;
+ void *user_data;
+};
+
+struct bt_bcast_recv_state {
+ struct bt_bass_db *bdb;
+ struct gatt_db_attribute *attr;
+ struct gatt_db_attribute *ccc;
+};
+
+struct bt_bass_db {
+ struct gatt_db *db;
+ struct queue *bcast_srcs;
+ struct gatt_db_attribute *service;
+ struct gatt_db_attribute *bcast_audio_scan_cp;
+ struct bt_bcast_recv_state *bcast_recv_states[NUM_BCAST_RECV_STATES];
+};
+
+struct bt_bass {
+ int ref_count;
+ struct bt_bass_db *ldb;
+ struct bt_bass_db *rdb;
+ struct bt_gatt_client *client;
+ struct bt_att *att;
+
+ struct queue *notify;
+
+ bt_bass_debug_func_t debug_func;
+ bt_bass_destroy_func_t debug_destroy;
+ void *debug_data;
+
+ void *user_data;
+};
+
+typedef void (*bass_notify_t)(struct bt_bass *bass, uint16_t value_handle,
+ const uint8_t *value, uint16_t length,
+ void *user_data);
+
+struct bt_bass_notify {
+ unsigned int id;
+ struct bt_bass *bass;
+ bass_notify_t func;
+ void *user_data;
+};
+
+static struct queue *bass_db;
+static struct queue *bass_cbs;
+static struct queue *sessions;
+
+static void bass_debug(struct bt_bass *bass, const char *format, ...)
+{
+ va_list ap;
+
+ if (!bass || !format || !bass->debug_func)
+ return;
+
+ va_start(ap, format);
+ util_debug_va(bass->debug_func, bass->debug_data, format, ap);
+ va_end(ap);
+}
+
+static int
+bass_build_bcast_src_from_notif(struct bt_bcast_src *bcast_src,
+ const uint8_t *value, uint16_t length)
+{
+ struct bt_bass_subgroup_data *subgroup_data = NULL;
+ uint8_t *id;
+ uint8_t *addr_type;
+ uint8_t *addr;
+ uint8_t *sid;
+ uint32_t bid;
+ uint8_t *pa_sync_state;
+ uint8_t *enc;
+ uint8_t *bad_code = NULL;
+ uint8_t *num_subgroups;
+ uint32_t bis_sync_state;
+ uint8_t *meta_len;
+ uint8_t *meta;
+
+ struct iovec iov = {
+ .iov_base = (void *) value,
+ .iov_len = length,
+ };
+
+ /* Extract all fields from notification */
+ id = util_iov_pull_mem(&iov, sizeof(*id));
+ if (!id) {
+ DBG(bcast_src->bass, "Unable to parse Broadcast Receive State");
+ return -1;
+ }
+
+ addr_type = util_iov_pull_mem(&iov, sizeof(*addr_type));
+ if (!addr_type) {
+ DBG(bcast_src->bass, "Unable to parse Broadcast Receive State");
+ return -1;
+ }
+
+ addr = util_iov_pull_mem(&iov, sizeof(bdaddr_t));
+ if (!addr) {
+ DBG(bcast_src->bass, "Unable to parse Broadcast Receive State");
+ return -1;
+ }
+
+ sid = util_iov_pull_mem(&iov, sizeof(*sid));
+ if (!sid) {
+ DBG(bcast_src->bass, "Unable to parse Broadcast Receive State");
+ return -1;
+ }
+
+ if (!util_iov_pull_le24(&iov, &bid)) {
+ DBG(bcast_src->bass, "Unable to parse Broadcast Receive State");
+ return -1;
+ }
+
+ pa_sync_state = util_iov_pull_mem(&iov, sizeof(*pa_sync_state));
+ if (!pa_sync_state) {
+ DBG(bcast_src->bass, "Unable to parse Broadcast Receive State");
+ return -1;
+ }
+
+ enc = util_iov_pull_mem(&iov, sizeof(*enc));
+ if (!enc) {
+ DBG(bcast_src->bass, "Unable to parse Broadcast Receive State");
+ return -1;
+ }
+
+ if (*enc == BT_BASS_BIG_ENC_STATE_BAD_CODE) {
+ bad_code = util_iov_pull_mem(&iov, BT_BASS_BCAST_CODE_SIZE);
+ if (!bad_code) {
+ DBG(bcast_src->bass, "Unable to parse "
+ "Broadcast Receive State");
+ return -1;
+ }
+ }
+
+ num_subgroups = util_iov_pull_mem(&iov, sizeof(*num_subgroups));
+ if (!num_subgroups) {
+ DBG(bcast_src->bass, "Unable to parse Broadcast Receive State");
+ return -1;
+ }
+
+ if (*num_subgroups == 0)
+ goto done;
+
+ subgroup_data = malloc((*num_subgroups) * sizeof(*subgroup_data));
+ if (!subgroup_data) {
+ DBG(bcast_src->bass, "Unable to allocate memory");
+ return -1;
+ }
+
+ memset(subgroup_data, 0, (*num_subgroups) * sizeof(*subgroup_data));
+
+ for (int i = 0; i < *num_subgroups; i++) {
+ if (!util_iov_pull_le32(&iov, &bis_sync_state)) {
+ DBG(bcast_src->bass, "Unable to parse "
+ "Broadcast Receive State");
+
+ for (int j = 0; j < i; j++)
+ free(subgroup_data[j].meta);
+
+ free(subgroup_data);
+ return -1;
+ }
+
+ subgroup_data[i].bis_sync = bis_sync_state;
+
+ meta_len = util_iov_pull_mem(&iov, sizeof(*meta_len));
+ if (!meta_len) {
+ DBG(bcast_src->bass, "Unable to parse "
+ "Broadcast Receive State");
+
+ for (int j = 0; j < i; j++)
+ free(subgroup_data[j].meta);
+
+ free(subgroup_data);
+ return -1;
+ }
+
+ subgroup_data[i].meta_len = *meta_len;
+
+ if (*meta_len == 0)
+ continue;
+
+ subgroup_data[i].meta = malloc(*meta_len);
+ if (!subgroup_data[i].meta) {
+ DBG(bcast_src->bass, "Unable to allocate memory");
+
+ for (int j = 0; j < i; j++)
+ free(subgroup_data[j].meta);
+
+ free(subgroup_data);
+ return -1;
+ }
+
+ meta = util_iov_pull_mem(&iov, *meta_len);
+ if (!meta) {
+ DBG(bcast_src->bass, "Unable to parse "
+ "Broadcast Receive State");
+
+ for (int j = 0; j < i; j++)
+ free(subgroup_data[j].meta);
+
+ free(subgroup_data);
+ return -1;
+ }
+
+ memcpy(subgroup_data[i].meta, meta, *meta_len);
+ }
+
+done:
+ /*
+ * If no errors occurred, copy extracted fields into
+ * the broadcast source structure
+ */
+ if (bcast_src->subgroup_data) {
+ for (int i = 0; i < bcast_src->num_subgroups; i++)
+ free(bcast_src->subgroup_data[i].meta);
+
+ free(bcast_src->subgroup_data);
+ }
+
+ bcast_src->id = *id;
+ bcast_src->addr_type = *addr_type;
+ memcpy(&bcast_src->addr, addr, sizeof(bdaddr_t));
+ bcast_src->sid = *sid;
+ bcast_src->bid = bid;
+ bcast_src->sync_state = *pa_sync_state;
+ bcast_src->enc = *enc;
+
+ if (*enc == BT_BASS_BIG_ENC_STATE_BAD_CODE)
+ memcpy(bcast_src->bad_code, bad_code, BT_BASS_BCAST_CODE_SIZE);
+ else
+ memset(bcast_src->bad_code, 0, BT_BASS_BCAST_CODE_SIZE);
+
+ bcast_src->num_subgroups = *num_subgroups;
+
+ bcast_src->subgroup_data = subgroup_data;
+
+ return 0;
+}
+
+static int
+bass_build_bcast_src_from_read_rsp(struct bt_bcast_src *bcast_src,
+ const uint8_t *value, uint16_t length)
+{
+ return bass_build_bcast_src_from_notif(bcast_src, value, length);
+}
+
+static uint8_t *bass_build_notif_from_bcast_src(struct bt_bcast_src *bcast_src,
+ size_t *notif_len)
+{
+ size_t len = 0;
+ uint8_t *notif = NULL;
+ struct iovec iov;
+
+ *notif_len = 0;
+
+ if (!bcast_src)
+ return NULL;
+
+ len = BT_BASS_BCAST_SRC_LEN + bcast_src->num_subgroups *
+ BT_BASS_BCAST_SRC_SUBGROUP_LEN;
+
+ if (bcast_src->enc == BT_BASS_BIG_ENC_STATE_BAD_CODE)
+ len += BT_BASS_BCAST_CODE_SIZE;
+
+ for (size_t i = 0; i < bcast_src->num_subgroups; i++) {
+ /* Add length for subgroup metadata */
+ len += bcast_src->subgroup_data[i].meta_len;
+ }
+
+ notif = malloc(len);
+ if (!notif)
+ return NULL;
+
+ memset(notif, 0, len);
+
+ iov.iov_base = notif;
+ iov.iov_len = 0;
+
+ util_iov_push_mem(&iov, sizeof(bcast_src->id),
+ &bcast_src->id);
+ util_iov_push_mem(&iov, sizeof(bcast_src->addr_type),
+ &bcast_src->addr_type);
+ util_iov_push_mem(&iov, sizeof(bcast_src->addr),
+ &bcast_src->addr);
+ util_iov_push_mem(&iov, sizeof(bcast_src->sid),
+ &bcast_src->sid);
+ util_iov_push_le24(&iov, bcast_src->bid);
+ util_iov_push_mem(&iov, sizeof(bcast_src->sync_state),
+ &bcast_src->sync_state);
+ util_iov_push_mem(&iov, sizeof(bcast_src->enc),
+ &bcast_src->enc);
+
+ if (bcast_src->enc == BT_BASS_BIG_ENC_STATE_BAD_CODE)
+ util_iov_push_mem(&iov, sizeof(bcast_src->bad_code),
+ bcast_src->bad_code);
+
+ util_iov_push_mem(&iov, sizeof(bcast_src->num_subgroups),
+ &bcast_src->num_subgroups);
+
+ for (size_t i = 0; i < bcast_src->num_subgroups; i++) {
+ /* Add subgroup bis_sync */
+ util_iov_push_le32(&iov, bcast_src->subgroup_data[i].bis_sync);
+
+ /* Add subgroup meta_len */
+ util_iov_push_mem(&iov,
+ sizeof(bcast_src->subgroup_data[i].meta_len),
+ &bcast_src->subgroup_data[i].meta_len);
+
+ /* Add subgroup metadata */
+ if (bcast_src->subgroup_data[i].meta_len > 0)
+ util_iov_push_mem(&iov,
+ bcast_src->subgroup_data[i].meta_len,
+ bcast_src->subgroup_data[i].meta);
+ }
+
+ *notif_len = len;
+ return notif;
+}
+
+static uint8_t *
+bass_build_read_rsp_from_bcast_src(struct bt_bcast_src *bcast_src,
+ size_t *rsp_len)
+{
+ return bass_build_notif_from_bcast_src(bcast_src, rsp_len);
+}
+
+static bool bass_check_cp_command_subgroup_data_len(uint8_t num_subgroups,
+ struct iovec *iov)
+{
+ uint32_t bis_sync_state;
+ uint8_t *meta_len;
+ uint8_t *meta;
+
+ for (int i = 0; i < num_subgroups; i++) {
+ if (!util_iov_pull_le32(iov, &bis_sync_state))
+ return false;
+
+ meta_len = util_iov_pull_mem(iov,
+ sizeof(*meta_len));
+ if (!meta_len)
+ return false;
+
+ meta = util_iov_pull_mem(iov, *meta_len);
+ if (!meta)
+ return false;
+ }
+
+ return true;
+}
+
+static bool bass_check_cp_command_len(struct iovec *iov)
+{
+ struct bt_bass_bcast_audio_scan_cp_hdr *hdr;
+ union {
+ struct bt_bass_add_src_params *add_src_params;
+ struct bt_bass_mod_src_params *mod_src_params;
+ struct bt_bass_set_bcast_code_params *set_bcast_code_params;
+ struct bt_bass_remove_src_params *remove_src_params;
+ } params;
+
+ /* Get command header */
+ hdr = util_iov_pull_mem(iov, sizeof(*hdr));
+
+ if (!hdr)
+ return false;
+
+ /* Check command parameters */
+ switch (hdr->op) {
+ case BT_BASS_ADD_SRC:
+ params.add_src_params = util_iov_pull_mem(iov,
+ sizeof(*params.add_src_params));
+ if (!params.add_src_params)
+ return false;
+
+ if (!bass_check_cp_command_subgroup_data_len(
+ params.add_src_params->num_subgroups,
+ iov))
+ return false;
+
+ break;
+ case BT_BASS_MOD_SRC:
+ params.mod_src_params = util_iov_pull_mem(iov,
+ sizeof(*params.mod_src_params));
+ if (!params.mod_src_params)
+ return false;
+
+ if (!bass_check_cp_command_subgroup_data_len(
+ params.mod_src_params->num_subgroups,
+ iov))
+ return false;
+
+ break;
+ case BT_BASS_SET_BCAST_CODE:
+ params.set_bcast_code_params = util_iov_pull_mem(iov,
+ sizeof(*params.set_bcast_code_params));
+ if (!params.set_bcast_code_params)
+ return false;
+
+ break;
+ case BT_BASS_REMOVE_SRC:
+ params.remove_src_params = util_iov_pull_mem(iov,
+ sizeof(*params.remove_src_params));
+ if (!params.remove_src_params)
+ return false;
+
+ break;
+ case BT_BASS_REMOTE_SCAN_STOPPED:
+ case BT_BASS_REMOTE_SCAN_STARTED:
+ break;
+ default:
+ return true;
+ }
+
+ if (iov->iov_len > 0)
+ return false;
+
+ return true;
+}
+
+static void bass_bcast_audio_scan_cp_write(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ const uint8_t *value, size_t len,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct iovec iov = {
+ .iov_base = (void *)value,
+ .iov_len = len,
+ };
+
+ /* Validate written command length */
+ if (!bass_check_cp_command_len(&iov)) {
+ if (opcode == BT_ATT_OP_WRITE_REQ) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ERROR_WRITE_REQUEST_REJECTED);
+ }
+ return;
+ }
+
+ /* TODO: Implement handlers for the written opcodes */
+ gatt_db_attribute_write_result(attrib, id,
+ BT_BASS_ERROR_OPCODE_NOT_SUPPORTED);
+}
+
+static bool bass_src_match_attrib(const void *data, const void *match_data)
+{
+ const struct bt_bcast_src *bcast_src = data;
+ const struct gatt_db_attribute *attr = match_data;
+
+ return (bcast_src->attr == attr);
+}
+
+static void bass_bcast_recv_state_read(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct bt_bass_db *bdb = user_data;
+ uint8_t *rsp;
+ size_t rsp_len;
+ struct bt_bcast_src *bcast_src;
+
+ bcast_src = queue_find(bdb->bcast_srcs,
+ bass_src_match_attrib,
+ attrib);
+
+ if (!bcast_src) {
+ gatt_db_attribute_read_result(attrib, id, 0, NULL,
+ 0);
+ return;
+ }
+
+ /* Build read response */
+ rsp = bass_build_read_rsp_from_bcast_src(bcast_src, &rsp_len);
+
+ if (!rsp) {
+ gatt_db_attribute_read_result(attrib, id,
+ BT_ATT_ERROR_UNLIKELY,
+ NULL, 0);
+ return;
+ }
+
+ gatt_db_attribute_read_result(attrib, id, 0, (void *)rsp,
+ rsp_len);
+
+ free(rsp);
+}
+
+static void bcast_recv_new(struct bt_bass_db *bdb, int i)
+{
+ struct bt_bcast_recv_state *bcast_recv_state;
+ bt_uuid_t uuid;
+
+ if (!bdb)
+ return;
+
+ bcast_recv_state = new0(struct bt_bcast_recv_state, 1);
+ bcast_recv_state->bdb = bdb;
+
+ bt_uuid16_create(&uuid, BCAST_RECV_STATE_UUID);
+ bcast_recv_state->attr =
+ gatt_db_service_add_characteristic(bdb->service, &uuid,
+ BT_ATT_PERM_READ,
+ BT_GATT_CHRC_PROP_READ |
+ BT_GATT_CHRC_PROP_NOTIFY,
+ bass_bcast_recv_state_read, NULL,
+ bdb);
+
+ bcast_recv_state->ccc = gatt_db_service_add_ccc(bdb->service,
+ BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+
+ bdb->bcast_recv_states[i] = bcast_recv_state;
+}
+
+static void bass_new(struct bt_bass_db *bdb)
+{
+ bt_uuid_t uuid;
+ int i;
+
+ /* Populate DB with BASS attributes */
+ bt_uuid16_create(&uuid, BASS_UUID);
+ bdb->service = gatt_db_add_service(bdb->db, &uuid, true,
+ 3 + (NUM_BCAST_RECV_STATES * 3));
+
+ for (i = 0; i < NUM_BCAST_RECV_STATES; i++)
+ bcast_recv_new(bdb, i);
+
+ bt_uuid16_create(&uuid, BCAST_AUDIO_SCAN_CP_UUID);
+ bdb->bcast_audio_scan_cp =
+ gatt_db_service_add_characteristic(bdb->service,
+ &uuid,
+ BT_ATT_PERM_WRITE,
+ BT_GATT_CHRC_PROP_WRITE,
+ NULL, bass_bcast_audio_scan_cp_write,
+ bdb);
+
+ gatt_db_service_set_active(bdb->service, true);
+}
+
+static void bass_bcast_src_free(void *data)
+{
+ struct bt_bcast_src *bcast_src = data;
+
+ for (int i = 0; i < bcast_src->num_subgroups; i++)
+ free(bcast_src->subgroup_data[i].meta);
+
+ free(bcast_src->subgroup_data);
+ free(bcast_src);
+}
+
+static void read_bcast_recv_state(bool success, uint8_t att_ecode,
+ const uint8_t *value, uint16_t length,
+ void *user_data)
+{
+ struct bt_bcast_src *bcast_src = user_data;
+
+ if (!success) {
+ DBG(bcast_src->bass, "Unable to read "
+ "Broadcast Receive State: error 0x%02x",
+ att_ecode);
+ return;
+ }
+
+ if (length == 0) {
+ queue_remove(bcast_src->bass->rdb->bcast_srcs, bcast_src);
+ bass_bcast_src_free(bcast_src);
+ return;
+ }
+
+ if (bass_build_bcast_src_from_read_rsp(bcast_src, value, length)) {
+ queue_remove(bcast_src->bass->rdb->bcast_srcs, bcast_src);
+ bass_bcast_src_free(bcast_src);
+ return;
+ }
+}
+
+static void bcast_recv_state_notify(struct bt_bass *bass, uint16_t value_handle,
+ const uint8_t *value, uint16_t length,
+ void *user_data)
+{
+ struct gatt_db_attribute *attr = user_data;
+ struct bt_bcast_src *bcast_src;
+ bool new_src = false;
+
+ bcast_src = queue_find(bass->rdb->bcast_srcs,
+ bass_src_match_attrib, attr);
+ if (!bcast_src) {
+ new_src = true;
+ bcast_src = malloc(sizeof(*bcast_src));
+
+ if (!bcast_src) {
+ DBG(bass, "Failed to allocate "
+ "memory for broadcast source");
+ return;
+ }
+
+ memset(bcast_src, 0, sizeof(struct bt_bcast_src));
+ bcast_src->bass = bass;
+ bcast_src->attr = attr;
+ }
+
+ if (bass_build_bcast_src_from_notif(bcast_src, value, length)
+ && new_src) {
+ bass_bcast_src_free(bcast_src);
+ return;
+ }
+
+ if (new_src)
+ queue_push_tail(bass->rdb->bcast_srcs, bcast_src);
+}
+
+static void bass_register(uint16_t att_ecode, void *user_data)
+{
+ struct bt_bass_notify *notify = user_data;
+
+ if (att_ecode)
+ DBG(notify->bass, "BASS register notify failed: 0x%04x",
+ att_ecode);
+}
+
+static void bass_notify(uint16_t value_handle, const uint8_t *value,
+ uint16_t length, void *user_data)
+{
+ struct bt_bass_notify *notify = user_data;
+
+ if (notify->func)
+ notify->func(notify->bass, value_handle, value, length,
+ notify->user_data);
+}
+
+static void bass_notify_destroy(void *data)
+{
+ struct bt_bass_notify *notify = data;
+ struct bt_bass *bass = notify->bass;
+
+ if (queue_remove_if(bass->notify, NULL, notify))
+ free(notify);
+}
+
+static unsigned int bass_register_notify(struct bt_bass *bass,
+ uint16_t value_handle,
+ bass_notify_t func,
+ void *user_data)
+{
+ struct bt_bass_notify *notify;
+
+ notify = new0(struct bt_bass_notify, 1);
+ notify->bass = bass;
+ notify->func = func;
+ notify->user_data = user_data;
+
+ notify->id = bt_gatt_client_register_notify(bass->client,
+ value_handle, bass_register,
+ bass_notify, notify,
+ bass_notify_destroy);
+ if (!notify->id) {
+ DBG(bass, "Unable to register for notifications");
+ free(notify);
+ return 0;
+ }
+
+ queue_push_tail(bass->notify, notify);
+
+ return notify->id;
+}
+
+static void foreach_bass_char(struct gatt_db_attribute *attr, void *user_data)
+{
+ struct bt_bass *bass = user_data;
+ uint16_t value_handle;
+ bt_uuid_t uuid, uuid_bcast_audio_scan_cp, uuid_bcast_recv_state;
+
+ /* Get attribute value handle and uuid */
+ if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle,
+ NULL, NULL, &uuid))
+ return;
+
+ bt_uuid16_create(&uuid_bcast_audio_scan_cp, BCAST_AUDIO_SCAN_CP_UUID);
+ bt_uuid16_create(&uuid_bcast_recv_state, BCAST_RECV_STATE_UUID);
+
+ if (!bt_uuid_cmp(&uuid, &uuid_bcast_audio_scan_cp)) {
+ /* Found Broadcast Audio Scan Control Point characteristic */
+ bass->rdb->bcast_audio_scan_cp = attr;
+
+ DBG(bass, "Broadcast Audio Scan Control Point "
+ "found: handle 0x%04x", value_handle);
+ }
+
+ if (!bt_uuid_cmp(&uuid, &uuid_bcast_recv_state)) {
+ /* Found Broadcast Receive State characteristic */
+ struct bt_bcast_src *bcast_src =
+ queue_find(bass->rdb->bcast_srcs,
+ bass_src_match_attrib, attr);
+
+ if (!bcast_src) {
+ bcast_src = malloc(sizeof(struct bt_bcast_src));
+
+ if (bcast_src == NULL) {
+ DBG(bass, "Failed to allocate "
+ "memory for broadcast source");
+ return;
+ }
+
+ memset(bcast_src, 0, sizeof(struct bt_bcast_src));
+ bcast_src->bass = bass;
+ bcast_src->attr = attr;
+
+ queue_push_tail(bass->rdb->bcast_srcs, bcast_src);
+ }
+
+ bt_gatt_client_read_value(bass->client, value_handle,
+ read_bcast_recv_state,
+ bcast_src, NULL);
+
+ (void)bass_register_notify(bass, value_handle,
+ bcast_recv_state_notify,
+ attr);
+
+ DBG(bass, "Broadcast Receive State found: handle 0x%04x",
+ value_handle);
+ }
+}
+
+static void foreach_bass_service(struct gatt_db_attribute *attr,
+ void *user_data)
+{
+ struct bt_bass *bass = user_data;
+
+ /* Store BASS service reference */
+ bass->rdb->service = attr;
+
+ /* Handle BASS characteristics */
+ gatt_db_service_foreach_char(attr, foreach_bass_char, bass);
+}
+
+bool bt_bass_attach(struct bt_bass *bass, struct bt_gatt_client *client)
+{
+ bt_uuid_t uuid;
+
+ if (!sessions)
+ sessions = queue_new();
+
+ queue_push_tail(sessions, bass);
+
+ if (!client)
+ return true;
+
+ if (bass->client)
+ return false;
+
+ bass->client = bt_gatt_client_clone(client);
+ if (!bass->client)
+ return false;
+
+ bt_uuid16_create(&uuid, BASS_UUID);
+ gatt_db_foreach_service(bass->rdb->db, &uuid, foreach_bass_service,
+ bass);
+
+ return true;
+}
+
+static void bass_detached(void *data, void *user_data)
+{
+ struct bt_bass_cb *cb = data;
+ struct bt_bass *bass = user_data;
+
+ cb->detached(bass, cb->user_data);
+}
+
+void bt_bass_detach(struct bt_bass *bass)
+{
+ if (!queue_remove(sessions, bass))
+ return;
+
+ bt_gatt_client_unref(bass->client);
+ bass->client = NULL;
+
+ queue_foreach(bass_cbs, bass_detached, bass);
+}
+
+static void bass_db_free(void *data)
+{
+ struct bt_bass_db *bdb = data;
+
+ if (!bdb)
+ return;
+
+ gatt_db_unref(bdb->db);
+ queue_destroy(bdb->bcast_srcs, bass_bcast_src_free);
+
+ free(bdb);
+}
+
+static void bass_free(void *data)
+{
+ struct bt_bass *bass = data;
+
+ bt_bass_detach(bass);
+ bass_db_free(bass->rdb);
+ queue_destroy(bass->notify, NULL);
+
+ free(bass);
+}
+
+void bt_bass_unref(struct bt_bass *bass)
+{
+ if (!bass)
+ return;
+
+ if (__sync_sub_and_fetch(&bass->ref_count, 1))
+ return;
+
+ bass_free(bass);
+}
+
+bool bt_bass_set_user_data(struct bt_bass *bass, void *user_data)
+{
+ if (!bass)
+ return false;
+
+ bass->user_data = user_data;
+
+ return true;
+}
+
+static struct bt_bass_db *bass_db_new(struct gatt_db *db)
+{
+ struct bt_bass_db *bdb;
+
+ if (!db)
+ return NULL;
+
+ bdb = new0(struct bt_bass_db, 1);
+ bdb->db = gatt_db_ref(db);
+ bdb->bcast_srcs = queue_new();
+
+ if (!bass_db)
+ bass_db = queue_new();
+
+ bass_new(bdb);
+
+ queue_push_tail(bass_db, bdb);
+
+ return bdb;
+}
+
+static bool bass_db_match(const void *data, const void *match_data)
+{
+ const struct bt_bass_db *bdb = data;
+ const struct gatt_db *db = match_data;
+
+ return (bdb->db == db);
+}
+
+static struct bt_bass_db *bass_get_db(struct gatt_db *db)
+{
+ struct bt_bass_db *bdb;
+
+ bdb = queue_find(bass_db, bass_db_match, db);
+ if (bdb)
+ return bdb;
+
+ return bass_db_new(db);
+}
+
+static struct bt_bass *bt_bass_ref(struct bt_bass *bass)
+{
+ if (!bass)
+ return NULL;
+
+ __sync_fetch_and_add(&bass->ref_count, 1);
+
+ return bass;
+}
+
+struct bt_bass *bt_bass_new(struct gatt_db *ldb, struct gatt_db *rdb)
+{
+ struct bt_bass *bass;
+ struct bt_bass_db *db;
+
+ if (!ldb)
+ return NULL;
+
+ db = bass_get_db(ldb);
+ if (!db)
+ return NULL;
+
+ bass = new0(struct bt_bass, 1);
+ bass->ldb = db;
+ bass->notify = queue_new();
+
+ if (!rdb)
+ goto done;
+
+ db = new0(struct bt_bass_db, 1);
+ db->db = gatt_db_ref(rdb);
+ db->bcast_srcs = queue_new();
+
+ bass->rdb = db;
+
+done:
+ bt_bass_ref(bass);
+
+ return bass;
+}
+
+struct bt_att *bt_bass_get_att(struct bt_bass *bass)
+{
+ if (!bass)
+ return NULL;
+
+ if (bass->att)
+ return bass->att;
+
+ return bt_gatt_client_get_att(bass->client);
+}
+
+bool bt_bass_set_debug(struct bt_bass *bass, bt_bass_debug_func_t func,
+ void *user_data, bt_bass_destroy_func_t destroy)
+{
+ if (!bass)
+ return false;
+
+ if (bass->debug_destroy)
+ bass->debug_destroy(bass->debug_data);
+
+ bass->debug_func = func;
+ bass->debug_destroy = destroy;
+ bass->debug_data = user_data;
+
+ return true;
+}
+
+unsigned int bt_bass_register(bt_bass_func_t attached, bt_bass_func_t detached,
+ void *user_data)
+{
+ struct bt_bass_cb *cb;
+ static unsigned int id;
+
+ if (!attached && !detached)
+ return 0;
+
+ if (!bass_cbs)
+ bass_cbs = queue_new();
+
+ cb = new0(struct bt_bass_cb, 1);
+ cb->id = ++id ? id : ++id;
+ cb->attached = attached;
+ cb->detached = detached;
+ cb->user_data = user_data;
+
+ queue_push_tail(bass_cbs, cb);
+
+ return cb->id;
+}
+static bool match_id(const void *data, const void *match_data)
+{
+ const struct bt_bass_cb *cb = data;
+ unsigned int id = PTR_TO_UINT(match_data);
+
+ return (cb->id == id);
+}
+
+bool bt_bass_unregister(unsigned int id)
+{
+ struct bt_bass_cb *cb;
+
+ cb = queue_remove_if(bass_cbs, match_id, UINT_TO_PTR(id));
+ if (!cb)
+ return false;
+
+ free(cb);
+
+ return true;
+}
new file mode 100644
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright 2023 NXP
+ *
+ */
+
+struct bt_bass;
+
+#define NUM_BCAST_RECV_STATES 2
+#define BT_BASS_BCAST_CODE_SIZE 16
+#define BT_BASS_BIG_SYNC_FAILED_BITMASK 0xFFFFFFFF
+#define BT_BASS_BCAST_SRC_LEN 15
+#define BT_BASS_BCAST_SRC_SUBGROUP_LEN 5
+
+/* Application error codes */
+#define BT_BASS_ERROR_OPCODE_NOT_SUPPORTED 0x80
+#define BT_BASS_ERROR_INVALID_SOURCE_ID 0x81
+
+/* PA_Sync_State values */
+#define BT_BASS_NOT_SYNCHRONIZED_TO_PA 0x00
+#define BT_BASS_SYNC_INFO_RE 0x01
+#define BT_BASS_SYNCHRONIZED_TO_PA 0x02
+#define BT_BASS_FAILED_TO_SYNCHRONIZE_TO_PA 0x03
+#define BT_BASS_NO_PAST 0x04
+
+/* BIG_Encryption values */
+#define BT_BASS_BIG_ENC_STATE_NO_ENC 0x00
+#define BT_BASS_BIG_ENC_STATE_BCODE_REQ 0x01
+#define BT_BASS_BIG_ENC_STATE_DEC 0x02
+#define BT_BASS_BIG_ENC_STATE_BAD_CODE 0x03
+
+/* BASS subgroup field of the Broadcast
+ * Receive State characteristic
+ */
+struct bt_bass_subgroup_data {
+ uint32_t bis_sync;
+ uint32_t pending_bis_sync;
+ uint8_t meta_len;
+ uint8_t *meta;
+};
+
+/* BASS Broadcast Source structure */
+struct bt_bcast_src {
+ struct bt_bass *bass;
+ struct gatt_db_attribute *attr;
+ uint8_t id;
+ uint8_t addr_type;
+ bdaddr_t addr;
+ uint8_t sid;
+ uint32_t bid;
+ uint8_t sync_state;
+ uint8_t enc;
+ uint8_t bad_code[BT_BASS_BCAST_CODE_SIZE];
+ uint8_t num_subgroups;
+ struct bt_bass_subgroup_data *subgroup_data;
+};
+
+/* Broadcast Audio Scan Control Point
+ * header structure
+ */
+struct bt_bass_bcast_audio_scan_cp_hdr {
+ uint8_t op;
+} __packed;
+
+#define BT_BASS_REMOTE_SCAN_STOPPED 0x00
+
+#define BT_BASS_REMOTE_SCAN_STARTED 0x01
+
+#define BT_BASS_ADD_SRC 0x02
+
+struct bt_bass_add_src_params {
+ uint8_t addr_type;
+ bdaddr_t addr;
+ uint8_t sid;
+ uint8_t bid[3];
+ uint8_t pa_sync;
+ uint16_t pa_interval;
+ uint8_t num_subgroups;
+ uint8_t subgroup_data[];
+} __packed;
+
+#define BT_BASS_MOD_SRC 0x03
+
+struct bt_bass_mod_src_params {
+ uint8_t id;
+ uint8_t pa_sync;
+ uint16_t pa_interval;
+ uint8_t num_subgroups;
+ uint8_t subgroup_data[];
+} __packed;
+
+#define BT_BASS_SET_BCAST_CODE 0x04
+
+struct bt_bass_set_bcast_code_params {
+ uint8_t id;
+ uint8_t bcast_code[BT_BASS_BCAST_CODE_SIZE];
+} __packed;
+
+#define BT_BASS_REMOVE_SRC 0x05
+
+struct bt_bass_remove_src_params {
+ uint8_t id;
+} __packed;
+
+typedef void (*bt_bass_func_t)(struct bt_bass *bass, void *user_data);
+typedef void (*bt_bass_destroy_func_t)(void *user_data);
+typedef void (*bt_bass_debug_func_t)(const char *str, void *user_data);
+
+struct bt_att *bt_bass_get_att(struct bt_bass *bass);
+unsigned int bt_bass_register(bt_bass_func_t attached, bt_bass_func_t detached,
+ void *user_data);
+bool bt_bass_unregister(unsigned int id);
+bool bt_bass_set_debug(struct bt_bass *bass, bt_bass_debug_func_t func,
+ void *user_data, bt_bass_destroy_func_t destroy);
+struct bt_bass *bt_bass_new(struct gatt_db *ldb, struct gatt_db *rdb);
+bool bt_bass_set_user_data(struct bt_bass *bass, void *user_data);
+void bt_bass_unref(struct bt_bass *bass);
+bool bt_bass_attach(struct bt_bass *bass, struct bt_gatt_client *client);
+void bt_bass_detach(struct bt_bass *bass);