This implements the Control Point handler for the Add Source operation. --- Makefile.am | 2 +- profiles/audio/bass.c | 26 ++- src/shared/bass.c | 427 ++++++++++++++++++++++++++++++++++++++++-- src/shared/bass.h | 14 +- unit/test-bass.c | 2 +- 5 files changed, 453 insertions(+), 18 deletions(-) diff --git a/Makefile.am b/Makefile.am index 2bd547564..98eb185fc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -580,7 +580,7 @@ unit_test_bap_LDADD = src/libshared-glib.la \ unit_tests += unit/test-bass -unit_test_bass_SOURCES = unit/test-bass.c +unit_test_bass_SOURCES = unit/test-bass.c $(btio_sources) unit_test_bass_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) diff --git a/profiles/audio/bass.c b/profiles/audio/bass.c index fae7fe004..7952105c5 100644 --- a/profiles/audio/bass.c +++ b/profiles/audio/bass.c @@ -37,10 +37,10 @@ #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "src/shared/gatt-server.h" +#include "src/adapter.h" #include "src/shared/bass.h" #include "src/plugin.h" -#include "src/adapter.h" #include "src/gatt-database.h" #include "src/device.h" #include "src/profile.h" @@ -197,7 +197,8 @@ static int bass_probe(struct btd_service *service) data->service = service; data->bass = bt_bass_new(btd_gatt_database_get_db(database), - btd_device_get_gatt_db(device)); + btd_device_get_gatt_db(device), + btd_adapter_get_address(adapter)); if (!data->bass) { error("Unable to create BASS instance"); free(data); @@ -268,6 +269,25 @@ static int bass_disconnect(struct btd_service *service) return 0; } +static int bass_server_probe(struct btd_profile *p, + struct btd_adapter *adapter) +{ + struct btd_gatt_database *database = btd_adapter_get_database(adapter); + + DBG("BASS path %s", adapter_get_path(adapter)); + + bt_bass_add_db(btd_gatt_database_get_db(database), + btd_adapter_get_address(adapter)); + + return 0; +} + +static void bass_server_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + DBG("BASS remove Adapter"); +} + static struct btd_profile bass_service = { .name = "bass", .priority = BTD_PROFILE_PRIORITY_MEDIUM, @@ -276,6 +296,8 @@ static struct btd_profile bass_service = { .device_remove = bass_remove, .accept = bass_accept, .disconnect = bass_disconnect, + .adapter_probe = bass_server_probe, + .adapter_remove = bass_server_remove, .experimental = true, }; diff --git a/src/shared/bass.c b/src/shared/bass.c index 423ab5bf7..1bb40877f 100644 --- a/src/shared/bass.c +++ b/src/shared/bass.c @@ -14,9 +14,13 @@ #include <stdbool.h> #include <unistd.h> #include <errno.h> +#include <poll.h> #include "lib/bluetooth.h" #include "lib/uuid.h" +#include "lib/iso.h" + +#include "btio/btio.h" #include "src/shared/queue.h" #include "src/shared/util.h" @@ -25,6 +29,8 @@ #include "src/shared/gatt-client.h" #include "src/shared/bass.h" +#define MAX_BIS_BITMASK_IDX 31 + #define DBG(_bass, fmt, arg...) \ bass_debug(_bass, "%s:%s() " fmt, __FILE__, __func__, ## arg) @@ -45,6 +51,7 @@ struct bt_bcast_recv_state { struct bt_bass_db { struct gatt_db *db; + bdaddr_t adapter_bdaddr; struct queue *bcast_srcs; struct gatt_db_attribute *service; struct gatt_db_attribute *bcast_audio_scan_cp; @@ -82,6 +89,35 @@ static struct queue *bass_db; static struct queue *bass_cbs; static struct queue *sessions; +#define DEFAULT_IO_QOS \ +{ \ + .interval = 10000, \ + .latency = 10, \ + .sdu = 40, \ + .phy = 0x02, \ + .rtn = 2, \ +} + +static struct bt_iso_qos default_qos = { + .bcast = { + .big = BT_ISO_QOS_BIG_UNSET, + .bis = BT_ISO_QOS_BIS_UNSET, + .sync_interval = 0x07, + .packing = 0x00, + .framing = 0x00, + .in = DEFAULT_IO_QOS, + .out = DEFAULT_IO_QOS, + .encryption = 0x00, + .bcode = {0x00}, + .options = 0x00, + .skip = 0x0000, + .sync_timeout = 0x4000, + .sync_cte_type = 0x00, + .mse = 0x00, + .timeout = 0x4000, + } +}; + static void bass_bcast_src_free(void *data); static void bass_debug(struct bt_bass *bass, const char *format, ...) @@ -461,7 +497,7 @@ static bool bass_check_cp_command_len(const uint8_t *value, size_t len) return true; } -static void bass_handle_remote_scan_stopped_op(struct bt_bass_db *bdb, +static void bass_handle_remote_scan_stopped_op(struct bt_bass *bass, struct gatt_db_attribute *attrib, uint8_t opcode, unsigned int id, @@ -472,7 +508,7 @@ static void bass_handle_remote_scan_stopped_op(struct bt_bass_db *bdb, gatt_db_attribute_write_result(attrib, id, 0x00); } -static void bass_handle_remote_scan_started_op(struct bt_bass_db *bdb, +static void bass_handle_remote_scan_started_op(struct bt_bass *bass, struct gatt_db_attribute *attrib, uint8_t opcode, unsigned int id, @@ -491,7 +527,7 @@ static bool bass_src_id_match(const void *data, const void *match_data) return (bcast_src->id == *id); } -static void bass_handle_remove_src_op(struct bt_bass_db *bdb, +static void bass_handle_remove_src_op(struct bt_bass *bass, struct gatt_db_attribute *attrib, uint8_t opcode, unsigned int id, @@ -504,7 +540,7 @@ static void bass_handle_remove_src_op(struct bt_bass_db *bdb, /* Get Remove Source command parameters */ params = util_iov_pull_mem(iov, sizeof(*params)); - bcast_src = queue_find(bdb->bcast_srcs, + bcast_src = queue_find(bass->ldb->bcast_srcs, bass_src_id_match, ¶ms->id); @@ -531,7 +567,7 @@ static void bass_handle_remove_src_op(struct bt_bass_db *bdb, return; /* Accept the operation and remove source */ - queue_remove(bdb->bcast_srcs, bcast_src); + queue_remove(bass->ldb->bcast_srcs, bcast_src); gatt_db_attribute_notify(bcast_src->attr, NULL, 0, att); bass_bcast_src_free(bcast_src); @@ -539,6 +575,338 @@ static void bass_handle_remove_src_op(struct bt_bass_db *bdb, gatt_db_attribute_write_result(attrib, id, 0x00); } +static bool bass_src_attr_match(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 gboolean check_io_err(GIOChannel *io) +{ + struct pollfd fds; + + memset(&fds, 0, sizeof(fds)); + fds.fd = g_io_channel_unix_get_fd(io); + fds.events = POLLERR; + + if (poll(&fds, 1, 0) > 0 && (fds.revents & POLLERR)) + return TRUE; + + return FALSE; +} + +static void bass_bis_unref(void *data) +{ + GIOChannel *io = data; + + g_io_channel_unref(io); +} + +static void connect_cb(GIOChannel *io, GError *gerr, + gpointer user_data) +{ + struct bt_bcast_src *bcast_src = user_data; + uint8_t *notify_data; + size_t notify_data_len; + int bis_idx; + int i; + + if (bcast_src->sync_state == BT_BASS_NOT_SYNCHRONIZED_TO_PA) + bcast_src->sync_state = BT_BASS_SYNCHRONIZED_TO_PA; + + /* Keep io reference */ + g_io_channel_ref(io); + queue_push_tail(bcast_src->bises, io); + + for (i = 0; i < bcast_src->num_subgroups; i++) { + struct bt_bass_subgroup_data *data = + &bcast_src->subgroup_data[i]; + + for (bis_idx = 0; bis_idx < MAX_BIS_BITMASK_IDX; bis_idx++) { + if (data->pending_bis_sync & (1 << bis_idx)) { + data->bis_sync |= (1 << bis_idx); + data->pending_bis_sync &= ~(1 << bis_idx); + break; + } + } + + if (bis_idx < MAX_BIS_BITMASK_IDX) + break; + } + + for (i = 0; i < bcast_src->num_subgroups; i++) { + if (bcast_src->subgroup_data[i].pending_bis_sync) + break; + } + + /* If there are still pending bises, wait for their + * notifications also before sending notification to + * client + */ + if (i != bcast_src->num_subgroups) + return; + + /* All connections have been notified */ + if (check_io_err(io)) { + DBG(bcast_src->bass, "BIG sync failed"); + + /* Close all connected bises */ + queue_destroy(bcast_src->bises, bass_bis_unref); + bcast_src->bises = NULL; + + /* Close listen io */ + g_io_channel_shutdown(bcast_src->listen_io, TRUE, NULL); + g_io_channel_unref(bcast_src->listen_io); + bcast_src->listen_io = NULL; + + for (i = 0; i < bcast_src->num_subgroups; i++) + bcast_src->subgroup_data[i].bis_sync = + BT_BASS_BIG_SYNC_FAILED_BITMASK; + } + + /* Send notification to client */ + notify_data = bass_build_notif_from_bcast_src(bcast_src, + ¬ify_data_len); + + gatt_db_attribute_notify(bcast_src->attr, + (void *)notify_data, + notify_data_len, + bt_bass_get_att(bcast_src->bass)); + + free(notify_data); +} + +static struct bt_bass *bass_get_session(struct bt_att *att, struct gatt_db *db, + const bdaddr_t *adapter_bdaddr) +{ + const struct queue_entry *entry; + struct bt_bass *bass; + + for (entry = queue_get_entries(sessions); entry; entry = entry->next) { + struct bt_bass *bass = entry->data; + + if (att == bt_bass_get_att(bass)) + return bass; + } + + bass = bt_bass_new(db, NULL, adapter_bdaddr); + bass->att = att; + + bt_bass_attach(bass, NULL); + + return bass; +} + +static void bass_handle_add_src_op(struct bt_bass *bass, + struct gatt_db_attribute *attrib, + uint8_t opcode, + unsigned int id, + struct iovec *iov, + struct bt_att *att) +{ + struct bt_bcast_src *bcast_src, *src; + uint8_t src_id = 0; + struct gatt_db_attribute *attr; + uint8_t *pa_sync; + GIOChannel *io; + GError *err = NULL; + struct bt_iso_qos iso_qos = default_qos; + uint8_t num_bis = 0; + uint8_t bis[ISO_MAX_NUM_BIS]; + uint8_t *notify_data; + size_t notify_data_len; + + if (opcode == BT_ATT_OP_WRITE_REQ) + gatt_db_attribute_write_result(attrib, id, 0x00); + + /* Allocate a new broadcast source */ + bcast_src = malloc(sizeof(*bcast_src)); + if (!bcast_src) { + DBG(bass, "Unable to allocate broadcast source"); + return; + } + + queue_push_tail(bass->ldb->bcast_srcs, bcast_src); + + memset(bcast_src, 0, sizeof(*bcast_src)); + memset(bis, 0, ISO_MAX_NUM_BIS); + + bcast_src->bass = bass; + + /* Map the source to a Broadcast Receive State characteristic */ + for (int i = 0; i < NUM_BCAST_RECV_STATES; i++) { + src = queue_find(bass->ldb->bcast_srcs, + bass_src_attr_match, + bass->ldb->bcast_recv_states[i]->attr); + if (!src) { + /* Found and empty characteristic */ + bcast_src->attr = + bass->ldb->bcast_recv_states[i]->attr; + break; + } + } + + if (!bcast_src->attr) { + /* If no empty characteristic has been found, + * overwrite an existing one + */ + attr = bass->ldb->bcast_recv_states[0]->attr; + + src = queue_find(bass->ldb->bcast_srcs, + bass_src_attr_match, + attr); + + queue_remove(bass->ldb->bcast_srcs, src); + bass_bcast_src_free(src); + bcast_src->attr = attr; + } + + /* Allocate source id */ + while (true) { + src = queue_find(bass->ldb->bcast_srcs, + bass_src_id_match, + &src_id); + if (!src) + break; + + if (src_id == 0xFF) { + DBG(bass, "Unable to allocate broadcast source id"); + return; + } + + src_id++; + } + + bcast_src->id = src_id; + + /* Populate broadcast source fields from command parameters */ + if (*(uint8_t *)util_iov_pull_mem(iov, sizeof(bcast_src->addr_type))) + bcast_src->addr_type = BDADDR_LE_RANDOM; + else + bcast_src->addr_type = BDADDR_LE_PUBLIC; + + bacpy(&bcast_src->addr, (bdaddr_t *)util_iov_pull_mem(iov, + sizeof(bdaddr_t))); + bcast_src->sid = *(uint8_t *)util_iov_pull_mem(iov, + sizeof(bcast_src->sid)); + util_iov_pull_le24(iov, &bcast_src->bid); + + pa_sync = util_iov_pull_mem(iov, sizeof(*pa_sync)); + bcast_src->sync_state = BT_BASS_NOT_SYNCHRONIZED_TO_PA; + + /* TODO: Set the encryption field based on observed BIGInfo reports, + * after PA sync establishment + */ + bcast_src->enc = BT_BASS_BIG_ENC_STATE_NO_ENC; + + /* TODO: Use the pa_interval field for the sync transfer procedure */ + util_iov_pull_mem(iov, sizeof(uint16_t)); + + bcast_src->num_subgroups = *(uint8_t *)util_iov_pull_mem(iov, + sizeof(bcast_src->num_subgroups)); + + if (!bcast_src->num_subgroups) + return; + + bcast_src->subgroup_data = malloc(bcast_src->num_subgroups * + sizeof(*bcast_src->subgroup_data)); + if (!bcast_src->subgroup_data) { + DBG(bass, "Unable to allocate subgroup data"); + goto err; + } + + memset(bcast_src->subgroup_data, 0, sizeof(*bcast_src->subgroup_data)); + + for (int i = 0; i < bcast_src->num_subgroups; i++) { + struct bt_bass_subgroup_data *data = + &bcast_src->subgroup_data[i]; + + util_iov_pull_le32(iov, &data->pending_bis_sync); + + if (data->pending_bis_sync != BIS_SYNC_NO_PREF) + /* Iterate through the bis sync bitmask written + * by the client and store the bis indexes that + * the BASS server will try to synchronize to + */ + for (int bis_idx = 0; bis_idx < 31; bis_idx++) { + if (data->pending_bis_sync & (1 << bis_idx)) { + bis[num_bis] = bis_idx + 1; + num_bis++; + } + } + + data->meta_len = *(uint8_t *)util_iov_pull_mem(iov, + sizeof(data->meta_len)); + if (!data->meta_len) + continue; + + data->meta = malloc(data->meta_len); + if (!data->meta) + goto err; + + memcpy(data->meta, (uint8_t *)util_iov_pull_mem(iov, + data->meta_len), data->meta_len); + } + + if (pa_sync != PA_SYNC_NO_SYNC && num_bis > 0) { + /* If requested by client, try to synchronize to the source */ + io = bt_io_listen(connect_cb, NULL, bcast_src, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, + &bass->ldb->adapter_bdaddr, + BT_IO_OPT_DEST_BDADDR, + &bcast_src->addr, + BT_IO_OPT_DEST_TYPE, + bcast_src->addr_type, + BT_IO_OPT_MODE, BT_IO_MODE_ISO, + BT_IO_OPT_QOS, &iso_qos, + BT_IO_OPT_ISO_BC_SID, bcast_src->sid, + BT_IO_OPT_ISO_BC_NUM_BIS, num_bis, + BT_IO_OPT_ISO_BC_BIS, bis, + BT_IO_OPT_INVALID); + + if (!io) { + DBG(bass, "%s", err->message); + g_error_free(err); + goto err; + } + + bcast_src->listen_io = io; + g_io_channel_ref(bcast_src->listen_io); + + if (!bcast_src->bises) + bcast_src->bises = queue_new(); + } else { + for (int i = 0; i < bcast_src->num_subgroups; i++) + bcast_src->subgroup_data[i].bis_sync = + bcast_src->subgroup_data[i].pending_bis_sync; + + notify_data = bass_build_notif_from_bcast_src(bcast_src, + ¬ify_data_len); + + gatt_db_attribute_notify(bcast_src->attr, + (void *)notify_data, + notify_data_len, + bt_bass_get_att(bcast_src->bass)); + + free(notify_data); + } + + return; + +err: + 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); + } + + free(bcast_src); +} + + #define BASS_OP(_str, _op, _size, _func) \ { \ .str = _str, \ @@ -551,7 +919,7 @@ struct bass_op_handler { const char *str; uint8_t op; size_t size; - void (*func)(struct bt_bass_db *bdb, + void (*func)(struct bt_bass *bass, struct gatt_db_attribute *attrib, uint8_t opcode, unsigned int id, @@ -564,6 +932,8 @@ struct bass_op_handler { 0, bass_handle_remote_scan_started_op), BASS_OP("Remove Source", BT_BASS_REMOVE_SRC, 0, bass_handle_remove_src_op), + BASS_OP("Add Source", BT_BASS_ADD_SRC, + 0, bass_handle_add_src_op), {} }; @@ -576,6 +946,8 @@ static void bass_bcast_audio_scan_cp_write(struct gatt_db_attribute *attrib, struct bt_bass_db *bdb = user_data; struct bt_bass_bcast_audio_scan_cp_hdr *hdr; struct bass_op_handler *handler; + struct bt_bass *bass = bass_get_session(att, bdb->db, + &bdb->adapter_bdaddr); struct iovec iov = { .iov_base = (void *)value, .iov_len = len, @@ -596,7 +968,7 @@ static void bass_bcast_audio_scan_cp_write(struct gatt_db_attribute *attrib, /* Call the appropriate opcode handler */ for (handler = bass_handlers; handler && handler->str; handler++) { if (handler->op == hdr->op) { - handler->func(bdb, attrib, opcode, id, &iov, att); + handler->func(bass, attrib, opcode, id, &iov, att); return; } } @@ -625,8 +997,10 @@ static void bass_bcast_recv_state_read(struct gatt_db_attribute *attrib, uint8_t *rsp; size_t rsp_len; struct bt_bcast_src *bcast_src; + struct bt_bass *bass = bass_get_session(att, bdb->db, + &bdb->adapter_bdaddr); - bcast_src = queue_find(bdb->bcast_srcs, + bcast_src = queue_find(bass->ldb->bcast_srcs, bass_src_match_attrib, attrib); @@ -712,6 +1086,14 @@ static void bass_bcast_src_free(void *data) free(bcast_src->subgroup_data[i].meta); free(bcast_src->subgroup_data); + + if (bcast_src->listen_io) { + g_io_channel_shutdown(bcast_src->listen_io, TRUE, NULL); + g_io_channel_unref(bcast_src->listen_io); + } + + queue_destroy(bcast_src->bises, bass_bis_unref); + free(bcast_src); } @@ -900,6 +1282,14 @@ static void foreach_bass_service(struct gatt_db_attribute *attr, gatt_db_service_foreach_char(attr, foreach_bass_char, bass); } +static void bass_attached(void *data, void *user_data) +{ + struct bt_bass_cb *cb = data; + struct bt_bass *bass = user_data; + + cb->attached(bass, cb->user_data); +} + bool bt_bass_attach(struct bt_bass *bass, struct bt_gatt_client *client) { bt_uuid_t uuid; @@ -909,6 +1299,8 @@ bool bt_bass_attach(struct bt_bass *bass, struct bt_gatt_client *client) queue_push_tail(sessions, bass); + queue_foreach(bass_cbs, bass_attached, bass); + if (!client) return true; @@ -990,7 +1382,8 @@ bool bt_bass_set_user_data(struct bt_bass *bass, void *user_data) return true; } -static struct bt_bass_db *bass_db_new(struct gatt_db *db) +static struct bt_bass_db *bass_db_new(struct gatt_db *db, + const bdaddr_t *adapter_bdaddr) { struct bt_bass_db *bdb; @@ -999,6 +1392,7 @@ static struct bt_bass_db *bass_db_new(struct gatt_db *db) bdb = new0(struct bt_bass_db, 1); bdb->db = gatt_db_ref(db); + bacpy(&bdb->adapter_bdaddr, adapter_bdaddr); bdb->bcast_srcs = queue_new(); if (!bass_db) @@ -1019,7 +1413,8 @@ static bool bass_db_match(const void *data, const void *match_data) return (bdb->db == db); } -static struct bt_bass_db *bass_get_db(struct gatt_db *db) +static struct bt_bass_db *bass_get_db(struct gatt_db *db, + const bdaddr_t *adapter_bdaddr) { struct bt_bass_db *bdb; @@ -1027,7 +1422,7 @@ static struct bt_bass_db *bass_get_db(struct gatt_db *db) if (bdb) return bdb; - return bass_db_new(db); + return bass_db_new(db, adapter_bdaddr); } static struct bt_bass *bt_bass_ref(struct bt_bass *bass) @@ -1040,7 +1435,8 @@ static struct bt_bass *bt_bass_ref(struct bt_bass *bass) return bass; } -struct bt_bass *bt_bass_new(struct gatt_db *ldb, struct gatt_db *rdb) +struct bt_bass *bt_bass_new(struct gatt_db *ldb, struct gatt_db *rdb, + const bdaddr_t *adapter_bdaddr) { struct bt_bass *bass; struct bt_bass_db *db; @@ -1048,7 +1444,7 @@ struct bt_bass *bt_bass_new(struct gatt_db *ldb, struct gatt_db *rdb) if (!ldb) return NULL; - db = bass_get_db(ldb); + db = bass_get_db(ldb, adapter_bdaddr); if (!db) return NULL; @@ -1140,3 +1536,8 @@ bool bt_bass_unregister(unsigned int id) return true; } + +void bt_bass_add_db(struct gatt_db *db, const bdaddr_t *adapter_bdaddr) +{ + bass_db_new(db, adapter_bdaddr); +} diff --git a/src/shared/bass.h b/src/shared/bass.h index d3474f7cf..fb4b72d7d 100644 --- a/src/shared/bass.h +++ b/src/shared/bass.h @@ -56,6 +56,8 @@ struct bt_bcast_src { uint8_t bad_code[BT_BASS_BCAST_CODE_SIZE]; uint8_t num_subgroups; struct bt_bass_subgroup_data *subgroup_data; + GIOChannel *listen_io; + struct queue *bises; }; /* Broadcast Audio Scan Control Point @@ -71,6 +73,14 @@ struct bt_bass_bcast_audio_scan_cp_hdr { #define BT_BASS_ADD_SRC 0x02 +/* PA_Sync values */ +#define PA_SYNC_NO_SYNC 0x00 +#define PA_SYNC_PAST 0x01 +#define PA_SYNC_NO_PAST 0x02 + +/* BIS_Sync no preference bitmask */ +#define BIS_SYNC_NO_PREF 0xFFFFFFFF + struct bt_bass_add_src_params { uint8_t addr_type; bdaddr_t addr; @@ -115,8 +125,10 @@ unsigned int bt_bass_register(bt_bass_func_t attached, bt_bass_func_t detached, 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); +struct bt_bass *bt_bass_new(struct gatt_db *ldb, struct gatt_db *rdb, + const bdaddr_t *adapter_bdaddr); 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); +void bt_bass_add_db(struct gatt_db *db, const bdaddr_t *adapter_bdaddr); diff --git a/unit/test-bass.c b/unit/test-bass.c index 8937c9478..2ab61f760 100644 --- a/unit/test-bass.c +++ b/unit/test-bass.c @@ -309,7 +309,7 @@ static void test_server(const void *user_data) gatt_db_ccc_register(data->db, gatt_ccc_read_cb, NULL, NULL, data); - data->bass = bt_bass_new(data->db, NULL); + data->bass = bt_bass_new(data->db, NULL, BDADDR_ANY); g_assert(data->bass); data->server = bt_gatt_server_new(data->db, att, 64, 0); -- 2.34.1