| // SPDX-License-Identifier: LGPL-2.1-or-later |
| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright 2023-2024 NXP |
| * |
| */ |
| |
| #define _GNU_SOURCE |
| #include <inttypes.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdbool.h> |
| #include <unistd.h> |
| #include <errno.h> |
| |
| #include "bluetooth/bluetooth.h" |
| #include "bluetooth/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; |
| bdaddr_t adapter_bdaddr; |
| 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; |
| |
| struct queue *src_cbs; |
| struct queue *cp_handlers; |
| |
| unsigned int disconn_id; |
| |
| void *user_data; |
| }; |
| |
| struct bt_bass_cp_handler { |
| unsigned int id; |
| bt_bass_cp_handler_func_t handler; |
| bt_bass_destroy_func_t destroy; |
| void *data; |
| }; |
| |
| /* 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; |
| }; |
| |
| 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; |
| |
| struct bt_bass_src_changed { |
| unsigned int id; |
| bt_bass_src_func_t cb; |
| bt_bass_destroy_func_t destroy; |
| void *data; |
| }; |
| |
| static void bass_bcast_src_free(void *data); |
| |
| 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); |
| } |
| |
| unsigned int bt_bass_cp_handler_register(struct bt_bass *bass, |
| bt_bass_cp_handler_func_t handler, |
| bt_bass_destroy_func_t destroy, |
| void *user_data) |
| { |
| struct bt_bass_cp_handler *cb; |
| static unsigned int id; |
| |
| if (!bass) |
| return 0; |
| |
| cb = new0(struct bt_bass_cp_handler, 1); |
| cb->id = ++id ? id : ++id; |
| cb->handler = handler; |
| cb->destroy = destroy; |
| cb->data = user_data; |
| |
| queue_push_tail(bass->cp_handlers, cb); |
| |
| return cb->id; |
| } |
| |
| static void bass_cp_handler_free(void *data) |
| { |
| struct bt_bass_cp_handler *cb = data; |
| |
| if (cb->destroy) |
| cb->destroy(cb->data); |
| |
| free(cb); |
| } |
| |
| static bool match_cb_id(const void *data, const void *match_data) |
| { |
| const struct bt_bass_cp_handler *cb = data; |
| unsigned int id = PTR_TO_UINT(match_data); |
| |
| return (cb->id == id); |
| } |
| |
| bool bt_bass_cp_handler_unregister(struct bt_bass *bass, |
| unsigned int id) |
| { |
| struct bt_bass_cp_handler *cb; |
| |
| if (!bass) |
| return false; |
| |
| cb = queue_remove_if(bass->cp_handlers, match_cb_id, |
| UINT_TO_PTR(id)); |
| if (!cb) |
| return false; |
| |
| bass_cp_handler_free(cb); |
| |
| return true; |
| } |
| |
| unsigned int bt_bass_src_register(struct bt_bass *bass, bt_bass_src_func_t cb, |
| void *user_data, bt_bass_destroy_func_t destroy) |
| { |
| struct bt_bass_src_changed *changed; |
| static unsigned int id; |
| |
| if (!bass) |
| return 0; |
| |
| changed = new0(struct bt_bass_src_changed, 1); |
| if (!changed) |
| return 0; |
| |
| changed->id = ++id ? id : ++id; |
| changed->cb = cb; |
| changed->destroy = destroy; |
| changed->data = user_data; |
| |
| queue_push_tail(bass->src_cbs, changed); |
| |
| return changed->id; |
| } |
| |
| static void bass_src_changed_free(void *data) |
| { |
| struct bt_bass_src_changed *changed = data; |
| |
| if (changed->destroy) |
| changed->destroy(changed->data); |
| |
| free(changed); |
| } |
| |
| static bool match_src_changed_id(const void *data, const void *match_data) |
| { |
| const struct bt_bass_src_changed *changed = data; |
| unsigned int id = PTR_TO_UINT(match_data); |
| |
| return (changed->id == id); |
| } |
| |
| bool bt_bass_src_unregister(struct bt_bass *bass, unsigned int id) |
| { |
| struct bt_bass_src_changed *changed; |
| |
| if (!bass) |
| return false; |
| |
| changed = queue_remove_if(bass->src_cbs, match_src_changed_id, |
| UINT_TO_PTR(id)); |
| if (!changed) |
| return false; |
| |
| bass_src_changed_free(changed); |
| |
| return true; |
| } |
| |
| static int bass_build_bcast_src(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 */ |
| if (!util_iov_pull_u8(&iov, &id)) { |
| DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); |
| return -1; |
| } |
| |
| if (!util_iov_pull_u8(&iov, &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; |
| } |
| |
| if (!util_iov_pull_u8(&iov, &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; |
| } |
| |
| if (!util_iov_pull_u8(&iov, &pa_sync_state)) { |
| DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); |
| return -1; |
| } |
| |
| if (!util_iov_pull_u8(&iov, &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; |
| } |
| } |
| |
| if (!util_iov_pull_u8(&iov, &num_subgroups)) { |
| DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); |
| return -1; |
| } |
| |
| if (num_subgroups == 0) |
| goto done; |
| |
| subgroup_data = new0(struct bt_bass_subgroup_data, 1); |
| if (!subgroup_data) { |
| DBG(bcast_src->bass, "Unable to allocate memory"); |
| return -1; |
| } |
| |
| 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; |
| |
| if (!util_iov_pull_u8(&iov, &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 = malloc0(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 struct iovec *bass_parse_bcast_src(struct bt_bcast_src *bcast_src) |
| { |
| size_t len = 0; |
| uint8_t *notif = NULL; |
| struct iovec *iov; |
| |
| 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 = malloc0(len); |
| if (!notif) |
| return NULL; |
| |
| iov = new0(struct iovec, 1); |
| if (!iov) { |
| free(notif); |
| return NULL; |
| } |
| |
| iov->iov_base = notif; |
| iov->iov_len = 0; |
| |
| util_iov_push_u8(iov, bcast_src->id); |
| util_iov_push_u8(iov, bcast_src->addr_type); |
| util_iov_push_mem(iov, sizeof(bcast_src->addr), |
| &bcast_src->addr); |
| util_iov_push_u8(iov, bcast_src->sid); |
| util_iov_push_le24(iov, bcast_src->bid); |
| util_iov_push_u8(iov, bcast_src->sync_state); |
| util_iov_push_u8(iov, 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_u8(iov, 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_u8(iov, 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); |
| } |
| |
| return iov; |
| } |
| |
| 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(const uint8_t *value, size_t len) |
| { |
| 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; |
| |
| struct iovec iov = { |
| .iov_base = (void *)value, |
| .iov_len = len, |
| }; |
| |
| /* 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_handle_remote_scan_stopped_op(struct bt_bass *bass, |
| struct gatt_db_attribute *attrib, |
| uint8_t opcode, |
| unsigned int id, |
| struct iovec *iov, |
| struct bt_att *att) |
| { |
| gatt_db_attribute_write_result(attrib, id, 0x00); |
| } |
| |
| static void bass_handle_remote_scan_started_op(struct bt_bass *bass, |
| struct gatt_db_attribute *attrib, |
| uint8_t opcode, |
| unsigned int id, |
| struct iovec *iov, |
| struct bt_att *att) |
| { |
| gatt_db_attribute_write_result(attrib, id, 0x00); |
| } |
| |
| static bool bass_src_id_match(const void *data, const void *match_data) |
| { |
| const struct bt_bcast_src *bcast_src = data; |
| const uint8_t *id = match_data; |
| |
| return (bcast_src->id == *id); |
| } |
| |
| static void bass_handle_remove_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_bass_remove_src_params *params; |
| struct bt_bcast_src *bcast_src; |
| int att_err = 0; |
| |
| /* Get Remove Source command parameters */ |
| params = util_iov_pull_mem(iov, sizeof(*params)); |
| |
| bcast_src = queue_find(bass->ldb->bcast_srcs, |
| bass_src_id_match, |
| ¶ms->id); |
| |
| if (!bcast_src) { |
| /* No source matches the written source id */ |
| att_err = BT_BASS_ERROR_INVALID_SOURCE_ID; |
| goto done; |
| } |
| |
| /* Ignore if server is synchronized to the PA |
| * of the source |
| */ |
| if (bcast_src->sync_state == BT_BASS_SYNCHRONIZED_TO_PA) |
| goto done; |
| |
| /* Ignore if server is synchronized to any BIS |
| * of the source |
| */ |
| for (int i = 0; i < bcast_src->num_subgroups; i++) |
| if (bcast_src->subgroup_data[i].bis_sync) |
| goto done; |
| |
| /* Accept the operation and remove source */ |
| queue_remove(bass->ldb->bcast_srcs, bcast_src); |
| gatt_db_attribute_notify(bcast_src->attr, NULL, 0, att); |
| bass_bcast_src_free(bcast_src); |
| |
| done: |
| gatt_db_attribute_write_result(attrib, id, |
| att_err); |
| } |
| |
| 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 bool bass_trigger_big_sync(struct bt_bcast_src *bcast_src) |
| { |
| for (int i = 0; i < bcast_src->num_subgroups; i++) { |
| struct bt_bass_subgroup_data *data = |
| &bcast_src->subgroup_data[i]; |
| |
| if (data->pending_bis_sync && |
| data->pending_bis_sync != BIS_SYNC_NO_PREF) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| 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 bool bass_validate_bis_sync(uint8_t num_subgroups, |
| struct iovec *iov) |
| { |
| uint32_t bis_sync_state; |
| uint32_t bitmask = 0U; |
| uint8_t *meta_len; |
| |
| for (int i = 0; i < num_subgroups; i++) { |
| util_iov_pull_le32(iov, &bis_sync_state); |
| |
| if (bis_sync_state != BIS_SYNC_NO_PREF) |
| for (int bis_idx = 0; bis_idx < 31; bis_idx++) { |
| if (bis_sync_state & (1 << bis_idx)) { |
| if (bitmask & (1 << bis_idx)) |
| return false; |
| |
| bitmask |= (1 << bis_idx); |
| } |
| } |
| |
| meta_len = util_iov_pull_mem(iov, |
| sizeof(*meta_len)); |
| util_iov_pull_mem(iov, *meta_len); |
| } |
| |
| return true; |
| } |
| |
| static bool bass_validate_add_src_params(uint8_t *value, size_t len) |
| { |
| struct bt_bass_add_src_params *params; |
| struct iovec iov = { |
| .iov_base = (void *)value, |
| .iov_len = len, |
| }; |
| |
| params = util_iov_pull_mem(&iov, sizeof(*params)); |
| |
| if (params->pa_sync > PA_SYNC_NO_PAST) |
| return false; |
| |
| if (params->addr_type > 0x01) |
| return false; |
| |
| if (params->sid > 0x0F) |
| return false; |
| |
| if (!bass_validate_bis_sync(params->num_subgroups, |
| &iov)) |
| return false; |
| |
| return true; |
| } |
| |
| 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; |
| struct iovec *notif; |
| int ret; |
| const struct queue_entry *entry; |
| struct bt_bass_add_src_params *params; |
| |
| gatt_db_attribute_write_result(attrib, id, 0x00); |
| |
| /* Ignore operation if parameters are invalid */ |
| if (!bass_validate_add_src_params(iov->iov_base, iov->iov_len)) |
| return; |
| |
| /* Allocate a new broadcast source */ |
| bcast_src = new0(struct bt_bcast_src, 1); |
| if (!bcast_src) { |
| DBG(bass, "Unable to allocate broadcast source"); |
| return; |
| } |
| |
| queue_push_tail(bass->ldb->bcast_srcs, bcast_src); |
| |
| 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; |
| |
| params = util_iov_pull_mem(iov, sizeof(*params)); |
| |
| /* Populate broadcast source fields from command parameters */ |
| bcast_src->addr_type = params->addr_type; |
| |
| /* Convert to three-value type */ |
| if (bcast_src->addr_type) |
| params->addr_type = BDADDR_LE_RANDOM; |
| else |
| params->addr_type = BDADDR_LE_PUBLIC; |
| |
| bacpy(&bcast_src->addr, ¶ms->addr); |
| bcast_src->sid = params->sid; |
| memcpy(&bcast_src->bid, params->bid, sizeof(params->bid)); |
| |
| pa_sync = params->pa_sync; |
| bcast_src->sync_state = BT_BASS_NOT_SYNCHRONIZED_TO_PA; |
| |
| bcast_src->num_subgroups = params->num_subgroups; |
| |
| if (!bcast_src->num_subgroups) |
| return; |
| |
| bcast_src->subgroup_data = new0(struct bt_bass_subgroup_data, |
| bcast_src->num_subgroups); |
| if (!bcast_src->subgroup_data) { |
| DBG(bass, "Unable to allocate subgroup data"); |
| goto err; |
| } |
| |
| 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); |
| |
| data->meta_len = *(uint8_t *)util_iov_pull_mem(iov, |
| sizeof(data->meta_len)); |
| if (!data->meta_len) |
| continue; |
| |
| data->meta = malloc0(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) { |
| for (entry = queue_get_entries(bass->cp_handlers); entry; |
| entry = entry->next) { |
| struct bt_bass_cp_handler *cb = entry->data; |
| |
| if (cb->handler) { |
| ret = cb->handler(bcast_src, |
| BT_BASS_ADD_SRC, |
| params, cb->data); |
| if (ret) |
| goto err; |
| } |
| } |
| } 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; |
| |
| notif = bass_parse_bcast_src(bcast_src); |
| if (!notif) |
| return; |
| |
| gatt_db_attribute_notify(bcast_src->attr, |
| notif->iov_base, notif->iov_len, |
| bt_bass_get_att(bcast_src->bass)); |
| |
| free(notif->iov_base); |
| free(notif); |
| } |
| |
| 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); |
| } |
| |
| static void bass_handle_set_bcast_code_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_bass_set_bcast_code_params *params; |
| struct bt_bcast_src *bcast_src; |
| struct iovec *notif; |
| const struct queue_entry *entry; |
| int ret; |
| |
| /* Get Set Broadcast Code command parameters */ |
| params = util_iov_pull_mem(iov, sizeof(*params)); |
| |
| bcast_src = queue_find(bass->ldb->bcast_srcs, |
| bass_src_id_match, |
| ¶ms->id); |
| |
| if (!bcast_src) { |
| /* No source matches the written source id */ |
| gatt_db_attribute_write_result(attrib, id, |
| BT_BASS_ERROR_INVALID_SOURCE_ID); |
| |
| return; |
| } |
| |
| gatt_db_attribute_write_result(attrib, id, 0x00); |
| |
| for (entry = queue_get_entries(bass->cp_handlers); entry; |
| entry = entry->next) { |
| struct bt_bass_cp_handler *cb = entry->data; |
| |
| if (cb->handler) { |
| ret = cb->handler(bcast_src, |
| BT_BASS_SET_BCAST_CODE, |
| params, cb->data); |
| if (ret) |
| DBG(bass, "Unable to handle Set " |
| "Broadcast Code operation"); |
| } |
| } |
| |
| if (!bass_trigger_big_sync(bcast_src)) { |
| bcast_src->enc = BT_BASS_BIG_ENC_STATE_DEC; |
| |
| notif = bass_parse_bcast_src(bcast_src); |
| if (!notif) |
| return; |
| |
| gatt_db_attribute_notify(bcast_src->attr, |
| notif->iov_base, notif->iov_len, |
| bt_bass_get_att(bcast_src->bass)); |
| |
| free(notif->iov_base); |
| free(notif); |
| } |
| } |
| |
| static void bass_handle_mod_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; |
| struct bt_bass_mod_src_params *params; |
| const struct queue_entry *entry; |
| struct iovec *notif; |
| bool updated = false; |
| int err = 0; |
| |
| /* Get Modify Source command parameters */ |
| params = util_iov_pull_mem(iov, sizeof(*params)); |
| |
| bcast_src = queue_find(bass->ldb->bcast_srcs, |
| bass_src_id_match, |
| ¶ms->id); |
| |
| if (!bcast_src) { |
| /* No source matches the written source id */ |
| gatt_db_attribute_write_result(attrib, id, |
| BT_BASS_ERROR_INVALID_SOURCE_ID); |
| |
| return; |
| } |
| |
| gatt_db_attribute_write_result(attrib, id, 0x00); |
| |
| for (int i = 0; i < bcast_src->num_subgroups; i++) { |
| struct bt_bass_subgroup_data *data = |
| &bcast_src->subgroup_data[i]; |
| uint8_t meta_len; |
| uint8_t *meta; |
| |
| if (!util_iov_pull_le32(iov, &data->pending_bis_sync)) |
| return; |
| |
| if (!util_iov_pull_u8(iov, &meta_len)) |
| return; |
| |
| /* Check for metadata updates and notify peers */ |
| if (meta_len != data->meta_len) { |
| updated = true; |
| data->meta_len = meta_len; |
| |
| free(data->meta); |
| data->meta = malloc0(data->meta_len); |
| if (!data->meta) |
| return; |
| } |
| |
| if (!data->meta_len) |
| continue; |
| |
| meta = (uint8_t *)util_iov_pull_mem(iov, meta_len); |
| if (!meta) |
| return; |
| |
| if (memcmp(meta, data->meta, data->meta_len)) { |
| updated = true; |
| memcpy(data->meta, meta, data->meta_len); |
| } |
| } |
| |
| for (entry = queue_get_entries(bass->cp_handlers); entry; |
| entry = entry->next) { |
| struct bt_bass_cp_handler *cb = entry->data; |
| |
| if (cb->handler) { |
| err = cb->handler(bcast_src, |
| BT_BASS_MOD_SRC, |
| params, cb->data); |
| if (err) |
| DBG(bass, "Unable to handle Modify Source " |
| "operation"); |
| } |
| } |
| |
| if (!updated) |
| return; |
| |
| notif = bass_parse_bcast_src(bcast_src); |
| if (!notif) |
| return; |
| |
| gatt_db_attribute_notify(bcast_src->attr, |
| notif->iov_base, notif->iov_len, |
| bt_bass_get_att(bcast_src->bass)); |
| |
| free(notif->iov_base); |
| free(notif); |
| } |
| |
| #define BASS_OP(_str, _op, _size, _func) \ |
| { \ |
| .str = _str, \ |
| .op = _op, \ |
| .size = _size, \ |
| .func = _func, \ |
| } |
| |
| struct bass_op_handler { |
| const char *str; |
| uint8_t op; |
| size_t size; |
| void (*func)(struct bt_bass *bass, |
| struct gatt_db_attribute *attrib, |
| uint8_t opcode, |
| unsigned int id, |
| struct iovec *iov, |
| struct bt_att *att); |
| } bass_handlers[] = { |
| BASS_OP("Remote Scan Stopped", BT_BASS_REMOTE_SCAN_STOPPED, |
| 0, bass_handle_remote_scan_stopped_op), |
| BASS_OP("Remote Scan Started", BT_BASS_REMOTE_SCAN_STARTED, |
| 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), |
| BASS_OP("Set Broadcast Code", BT_BASS_SET_BCAST_CODE, |
| 0, bass_handle_set_bcast_code_op), |
| BASS_OP("Modify Source", BT_BASS_MOD_SRC, |
| 0, bass_handle_mod_src_op), |
| {} |
| }; |
| |
| 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 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, |
| }; |
| |
| /* Validate written command length */ |
| if (!bass_check_cp_command_len(value, len)) { |
| gatt_db_attribute_write_result(attrib, id, |
| BT_ERROR_WRITE_REQUEST_REJECTED); |
| return; |
| } |
| |
| /* Get command header */ |
| hdr = util_iov_pull_mem(&iov, sizeof(*hdr)); |
| |
| /* Call the appropriate opcode handler */ |
| for (handler = bass_handlers; handler && handler->str; handler++) { |
| if (handler->op == hdr->op) { |
| handler->func(bass, attrib, opcode, id, &iov, att); |
| return; |
| } |
| } |
| |
| /* Send error response if unsupported opcode was written */ |
| 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; |
| struct iovec *rsp; |
| struct bt_bcast_src *bcast_src; |
| struct bt_bass *bass = bass_get_session(att, bdb->db, |
| &bdb->adapter_bdaddr); |
| |
| bcast_src = queue_find(bass->ldb->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_parse_bcast_src(bcast_src); |
| |
| if (!rsp) { |
| gatt_db_attribute_read_result(attrib, id, |
| BT_ATT_ERROR_UNLIKELY, |
| NULL, 0); |
| return; |
| } |
| |
| gatt_db_attribute_read_result(attrib, id, 0, rsp->iov_base, |
| rsp->iov_len); |
| |
| free(rsp->iov_base); |
| 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_ATT_PERM_READ_ENCRYPT, |
| 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_ATT_PERM_WRITE_ENCRYPT, |
| BT_GATT_CHRC_PROP_WRITE | |
| BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP, |
| 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; |
| |
| if (!bcast_src) |
| return; |
| |
| 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(bcast_src, value, length)) { |
| queue_remove(bcast_src->bass->rdb->bcast_srcs, bcast_src); |
| bass_bcast_src_free(bcast_src); |
| return; |
| } |
| } |
| |
| static void notify_src_changed(void *data, void *user_data) |
| { |
| struct bt_bass_src_changed *changed = data; |
| struct bt_bcast_src *bcast_src = user_data; |
| uint32_t bis_sync = 0; |
| |
| for (uint8_t i = 0; i < bcast_src->num_subgroups; i++) { |
| struct bt_bass_subgroup_data *sgrp = |
| &bcast_src->subgroup_data[i]; |
| |
| /* Create a bitmask of all BIS indices that the peer has |
| * synchronized with. |
| */ |
| bis_sync |= sgrp->bis_sync; |
| } |
| |
| if (changed->cb) |
| changed->cb(bcast_src->id, bcast_src->bid, |
| bcast_src->sync_state, bcast_src->enc, |
| bis_sync, changed->data); |
| } |
| |
| 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 = new0(struct bt_bcast_src, 1); |
| |
| if (!bcast_src) { |
| DBG(bass, "Failed to allocate " |
| "memory for broadcast source"); |
| return; |
| } |
| |
| bcast_src->bass = bass; |
| bcast_src->attr = attr; |
| } |
| |
| if (bass_build_bcast_src(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); |
| |
| /* Notify the update in the Broadcast Receive State characteristic |
| * to all drivers that registered a callback. |
| */ |
| queue_foreach(bass->src_cbs, notify_src_changed, 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 = new0(struct bt_bcast_src, 1); |
| |
| if (bcast_src == NULL) { |
| DBG(bass, "Failed to allocate " |
| "memory for broadcast source"); |
| return; |
| } |
| |
| 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); |
| } |
| |
| 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); |
| } |
| |
| static void bass_disconnected(int err, void *user_data) |
| { |
| struct bt_bass *bass = user_data; |
| |
| bass->disconn_id = 0; |
| |
| DBG(bass, "bass %p disconnected err %d", bass, err); |
| |
| bt_bass_detach(bass); |
| } |
| |
| static void bass_attach_att(struct bt_bass *bass, struct bt_att *att) |
| { |
| if (bass->disconn_id) { |
| if (att == bt_bass_get_att(bass)) |
| return; |
| |
| bt_att_unregister_disconnect(bt_bass_get_att(bass), |
| bass->disconn_id); |
| } |
| |
| bass->disconn_id = bt_att_register_disconnect(att, |
| bass_disconnected, |
| bass, NULL); |
| } |
| |
| 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); |
| |
| queue_foreach(bass_cbs, bass_attached, bass); |
| |
| if (!client) { |
| if (bass->att) |
| bass_attach_att(bass, bass->att); |
| return true; |
| } |
| |
| if (bass->client) |
| return false; |
| |
| bass->client = bt_gatt_client_clone(client); |
| if (!bass->client) |
| return false; |
| |
| bass_attach_att(bass, bt_gatt_client_get_att(client)); |
| |
| bt_uuid16_create(&uuid, BASS_UUID); |
| gatt_db_foreach_service(bass->rdb->db, &uuid, foreach_bass_service, |
| bass); |
| |
| return true; |
| } |
| |
| bool bt_bass_set_att(struct bt_bass *bass, struct bt_att *att) |
| { |
| if (!bass) |
| return false; |
| |
| bass->att = att; |
| 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) |
| { |
| struct bt_att *att; |
| |
| if (!queue_remove(sessions, bass)) |
| return; |
| |
| if (bass->client) |
| att = bt_gatt_client_get_att(bass->client); |
| else |
| att = bass->att; |
| |
| bt_att_unregister_disconnect(att, bass->disconn_id); |
| |
| bt_gatt_client_unref(bass->client); |
| bass->client = NULL; |
| |
| bass->att = 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); |
| queue_destroy(bass->src_cbs, bass_src_changed_free); |
| queue_destroy(bass->cp_handlers, bass_cp_handler_free); |
| |
| 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, |
| const bdaddr_t *adapter_bdaddr) |
| { |
| struct bt_bass_db *bdb; |
| |
| if (!db) |
| return NULL; |
| |
| 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) |
| 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, |
| const bdaddr_t *adapter_bdaddr) |
| { |
| struct bt_bass_db *bdb; |
| |
| bdb = queue_find(bass_db, bass_db_match, db); |
| if (bdb) |
| return bdb; |
| |
| return bass_db_new(db, adapter_bdaddr); |
| } |
| |
| 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, |
| const bdaddr_t *adapter_bdaddr) |
| { |
| struct bt_bass *bass; |
| struct bt_bass_db *db; |
| |
| if (!ldb) |
| return NULL; |
| |
| db = bass_get_db(ldb, adapter_bdaddr); |
| if (!db) |
| return NULL; |
| |
| bass = new0(struct bt_bass, 1); |
| bass->ldb = db; |
| bass->notify = queue_new(); |
| bass->src_cbs = queue_new(); |
| bass->cp_handlers = 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); |
| } |
| |
| struct bt_gatt_client *bt_bass_get_client(struct bt_bass *bass) |
| { |
| if (!bass) |
| return NULL; |
| |
| return 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; |
| } |
| |
| void bt_bass_add_db(struct gatt_db *db, const bdaddr_t *adapter_bdaddr) |
| { |
| bass_db_new(db, adapter_bdaddr); |
| } |
| |
| int bt_bass_send(struct bt_bass *bass, |
| struct bt_bass_bcast_audio_scan_cp_hdr *hdr, |
| struct iovec *params) |
| { |
| struct iovec req = {0}; |
| uint16_t handle; |
| int err = 0; |
| |
| if (!bass || !bass->client || !bass->rdb) |
| return -EINVAL; |
| |
| DBG(bass, "bass %p", bass); |
| |
| req.iov_base = malloc0(sizeof(*hdr) + params->iov_len); |
| if (!req.iov_base) |
| return -EINVAL; |
| |
| util_iov_push_mem(&req, sizeof(*hdr), hdr); |
| util_iov_push_mem(&req, params->iov_len, params->iov_base); |
| |
| if (!gatt_db_attribute_get_char_data(bass->rdb->bcast_audio_scan_cp, |
| NULL, &handle, NULL, NULL, NULL)) { |
| err = -EINVAL; |
| goto done; |
| } |
| |
| if (!bt_gatt_client_write_without_response(bass->client, handle, |
| false, req.iov_base, req.iov_len)) |
| err = -EINVAL; |
| |
| done: |
| free(req.iov_base); |
| |
| return err; |
| } |
| |
| static void bt_bass_notify_all(struct gatt_db_attribute *attr, |
| struct iovec *iov) |
| { |
| const struct queue_entry *entry; |
| |
| for (entry = queue_get_entries(sessions); entry; entry = entry->next) { |
| struct bt_bass *bass = entry->data; |
| |
| gatt_db_attribute_notify(attr, iov->iov_base, |
| iov->iov_len, bt_bass_get_att(bass)); |
| } |
| } |
| |
| int bt_bass_set_pa_sync(struct bt_bcast_src *bcast_src, uint8_t sync_state) |
| { |
| struct iovec *iov; |
| |
| if (!bcast_src) |
| return -EINVAL; |
| |
| bcast_src->sync_state = sync_state; |
| |
| iov = bass_parse_bcast_src(bcast_src); |
| if (!iov) |
| return -ENOMEM; |
| |
| bt_bass_notify_all(bcast_src->attr, iov); |
| |
| free(iov->iov_base); |
| free(iov); |
| |
| return 0; |
| } |
| |
| int bt_bass_get_pa_sync(struct bt_bcast_src *bcast_src, uint8_t *sync_state) |
| { |
| if (!bcast_src) |
| return -EINVAL; |
| |
| *sync_state = bcast_src->sync_state; |
| |
| return 0; |
| } |
| |
| int bt_bass_set_bis_sync(struct bt_bcast_src *bcast_src, uint8_t bis) |
| { |
| struct iovec *iov; |
| |
| for (uint8_t i = 0; i < bcast_src->num_subgroups; i++) { |
| struct bt_bass_subgroup_data *sgrp = |
| &bcast_src->subgroup_data[i]; |
| uint32_t bitmask = 1 << (bis - 1); |
| |
| if (sgrp->pending_bis_sync & bitmask) { |
| sgrp->bis_sync |= bitmask; |
| |
| if (bcast_src->enc == BT_BASS_BIG_ENC_STATE_BCODE_REQ) |
| bcast_src->enc = BT_BASS_BIG_ENC_STATE_DEC; |
| |
| iov = bass_parse_bcast_src(bcast_src); |
| if (!iov) |
| return -ENOMEM; |
| |
| bt_bass_notify_all(bcast_src->attr, iov); |
| |
| free(iov->iov_base); |
| free(iov); |
| } |
| } |
| |
| return 0; |
| } |
| |
| int bt_bass_clear_bis_sync(struct bt_bcast_src *bcast_src, uint8_t bis) |
| { |
| struct iovec *iov; |
| |
| for (uint8_t i = 0; i < bcast_src->num_subgroups; i++) { |
| struct bt_bass_subgroup_data *sgrp = |
| &bcast_src->subgroup_data[i]; |
| uint32_t bitmask = 1 << (bis - 1); |
| |
| if (sgrp->bis_sync & bitmask) { |
| sgrp->bis_sync &= ~bitmask; |
| |
| iov = bass_parse_bcast_src(bcast_src); |
| if (!iov) |
| return -ENOMEM; |
| |
| bt_bass_notify_all(bcast_src->attr, iov); |
| |
| free(iov->iov_base); |
| free(iov); |
| } |
| } |
| |
| return 0; |
| } |
| |
| bool bt_bass_check_bis(struct bt_bcast_src *bcast_src, uint8_t bis) |
| { |
| for (uint8_t i = 0; i < bcast_src->num_subgroups; i++) { |
| struct bt_bass_subgroup_data *sgrp = |
| &bcast_src->subgroup_data[i]; |
| uint32_t bitmask = 1 << (bis - 1); |
| |
| if (sgrp->pending_bis_sync & bitmask) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| int bt_bass_set_enc(struct bt_bcast_src *bcast_src, uint8_t enc) |
| { |
| struct iovec *iov; |
| |
| if (!bcast_src) |
| return -EINVAL; |
| |
| if (bcast_src->enc == enc) |
| return 0; |
| |
| bcast_src->enc = enc; |
| |
| iov = bass_parse_bcast_src(bcast_src); |
| if (!iov) |
| return -ENOMEM; |
| |
| bt_bass_notify_all(bcast_src->attr, iov); |
| |
| free(iov->iov_base); |
| free(iov); |
| |
| return 0; |
| } |