Added initial BASS code - added support for the Broadcast Receive State characteristic and for the Set Broadcast_Code operation from the Broadcast Audio Scan Control Point characteristic. The BASS implementation exposes HCI event callbacks that enable the BASS server to autonomously synchronize to BIGs. --- src/shared/att-types.h | 1 + src/shared/bap.c | 1064 ++++++++++++++++++++++++++++++++++++++++ src/shared/bap.h | 25 + src/shared/bass.h | 42 ++ 4 files changed, 1132 insertions(+) create mode 100644 src/shared/bass.h diff --git a/src/shared/att-types.h b/src/shared/att-types.h index a08b24155..35bf41118 100644 --- a/src/shared/att-types.h +++ b/src/shared/att-types.h @@ -104,6 +104,7 @@ struct bt_att_pdu_error_rsp { * 0xE0-0xFC are reserved for future use. The remaining 3 are defined as the * following: */ +#define BT_ERROR_WRITE_REQUEST_REJECTED 0xfc #define BT_ERROR_CCC_IMPROPERLY_CONFIGURED 0xfd #define BT_ERROR_ALREADY_IN_PROGRESS 0xfe #define BT_ERROR_OUT_OF_RANGE 0xff diff --git a/src/shared/bap.c b/src/shared/bap.c index db7def799..9a378b8b5 100644 --- a/src/shared/bap.c +++ b/src/shared/bap.c @@ -17,6 +17,8 @@ #include "lib/bluetooth.h" #include "lib/uuid.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" #include "src/shared/io.h" #include "src/shared/queue.h" @@ -28,6 +30,8 @@ #include "src/shared/gatt-client.h" #include "src/shared/bap.h" #include "src/shared/ascs.h" +#include "src/shared/bass.h" +#include "src/shared/ad.h" /* Maximum number of ASE(s) */ #define NUM_SINKS 2 @@ -108,13 +112,54 @@ struct bt_ascs { struct gatt_db_attribute *ase_cp_ccc; }; +/* BASS subgroup field of the Broadcast + * Receive State characteristic + */ +struct bt_bass_subgroup_data { + uint32_t bis_sync; + uint8_t meta_len; + uint8_t *meta; +} __packed; + +/* BASS Broadcast Source structure */ +struct bt_bcst_source { + struct gatt_db_attribute *attr; + uint8_t id; + uint8_t addr_type; + uint8_t addr[6]; + uint8_t sid; + uint8_t bid[BT_BAP_BROADCAST_ID_SIZE]; + uint8_t sync_state; + uint8_t encryption; + uint8_t bad_code[BT_BAP_BROADCAST_CODE_SIZE]; + uint8_t num_subgroups; + struct bt_bass_subgroup_data *subgroup_data; +} __packed; + +/* Broadcast Receive State characteristic structure */ +struct bt_bcst_recv_state { + struct bt_bass *bass; + struct gatt_db_attribute *attr; + struct gatt_db_attribute *ccc; +}; + +/* BASS instance structure */ +struct bt_bass { + struct bt_bap_db *bdb; + struct gatt_db_attribute *service; + struct gatt_db_attribute *broadcast_audio_scan_cp; + struct bt_bcst_recv_state *bcst_recv_states[NUM_BCST_RECV_STATES]; +}; + struct bt_bap_db { struct gatt_db *db; struct bt_pacs *pacs; struct bt_ascs *ascs; + struct bt_bass *bass; struct queue *sinks; struct queue *sources; struct queue *endpoints; + struct queue *bass_bcst_sources; }; struct bt_bap_req { @@ -255,6 +300,15 @@ static struct queue *bap_db; static struct queue *bap_cbs; static struct queue *sessions; +static int hci_fd = -1; + +static struct bt_hci_cmd_le_big_create_sync *big_sync_cmd; +static int big_sync_cmd_len; + +static struct bt_hci_le_pa_report *pa_report; + +static uint8_t next_available_source_id; + static bool bap_db_match(const void *data, const void *match_data) { const struct bt_bap_db *bdb = data; @@ -2170,6 +2224,643 @@ static struct bt_ascs *ascs_new(struct gatt_db *db) return ascs; } +static int bass_build_bcst_source_from_notif(struct bt_bcst_source *bcst_source, + const uint8_t *value) +{ + struct bt_bass_subgroup_data *subgroup_data; + + if (!bcst_source || !value) + return -1; + + bcst_source->id = *value; + value++; + + bcst_source->addr_type = *value; + value++; + + memcpy(bcst_source->addr, value, 6); + value += 6; + + bcst_source->sid = *value; + value++; + + memcpy(bcst_source->bid, value, BT_BAP_BROADCAST_ID_SIZE); + value += BT_BAP_BROADCAST_ID_SIZE; + + bcst_source->sync_state = *value; + value++; + + bcst_source->encryption = *value; + value++; + + if (bcst_source->encryption == BT_BASS_BIG_ENC_STATE_BAD_CODE) { + memcpy(bcst_source->bad_code, value, BT_BAP_BROADCAST_CODE_SIZE); + value += BT_BAP_BROADCAST_CODE_SIZE; + } else { + memset(bcst_source->bad_code, 0, BT_BAP_BROADCAST_CODE_SIZE); + } + + bcst_source->num_subgroups = *value; + value++; + + free(bcst_source->subgroup_data); + bcst_source->subgroup_data = malloc(bcst_source->num_subgroups * + sizeof(struct bt_bass_subgroup_data)); + + if (!bcst_source->subgroup_data) + return -1; + + for (int i = 0; i < bcst_source->num_subgroups; i++) { + subgroup_data = &bcst_source->subgroup_data[i]; + + memcpy(&subgroup_data->bis_sync, value, sizeof(uint32_t)); + value += sizeof(uint32_t); + + subgroup_data->meta_len = *value; + value++; + + free(subgroup_data->meta); + subgroup_data->meta = malloc(subgroup_data->meta_len); + if (!subgroup_data->meta) { + for (int j = 0; j < i; j++) + free(bcst_source->subgroup_data[j].meta); + + free(bcst_source->subgroup_data); + return -1; + } + + memcpy(subgroup_data->meta, value, subgroup_data->meta_len); + value += subgroup_data->meta_len; + } + + return 0; +} + +static int bass_build_bcst_source_from_read_rsp( + struct bt_bcst_source *bcst_source, + const uint8_t *value) +{ + return bass_build_bcst_source_from_notif(bcst_source, value); +} + +static uint8_t *bass_build_notif_from_bcst_source(struct bt_bcst_source *source, + size_t *notif_len) +{ + size_t len = 0; + uint8_t *notif = NULL; + uint8_t *ptr; + + *notif_len = 0; + + if (!source) + return NULL; + + len = 15 + source->num_subgroups * 5; + + if (source->encryption == BT_BASS_BIG_ENC_STATE_BAD_CODE) + len += BT_BAP_BROADCAST_CODE_SIZE; + + for (size_t i = 0; i < source->num_subgroups; i++) { + /* add length for subgroup metadata */ + len += source->subgroup_data[i].meta_len; + } + + notif = malloc(len); + if (!notif) + return NULL; + + memset(notif, 0, len); + ptr = notif; + + /* add source_id field */ + *ptr = source->id; + ptr++; + + /* add addr_type field */ + *ptr = source->addr_type; + ptr++; + + /* add addr field */ + memcpy(ptr, source->addr, 6); + ptr += 6; + + /* add sid field */ + *ptr = source->sid; + ptr++; + + /* add bid field */ + memcpy(ptr, source->bid, BT_BAP_BROADCAST_ID_SIZE); + ptr += 3; + + /* add sync_state field */ + *ptr = source->sync_state; + ptr++; + + /* add encryption field */ + *ptr = source->encryption; + ptr++; + + if (source->encryption == BT_BASS_BIG_ENC_STATE_BAD_CODE) { + memcpy(ptr, source->bad_code, BT_BAP_BROADCAST_CODE_SIZE); + ptr += BT_BAP_BROADCAST_CODE_SIZE; + } + + /* add num_subgroups field */ + *ptr = source->num_subgroups; + ptr++; + + for (size_t i = 0; i < source->num_subgroups; i++) { + /* add subgroup bis_sync */ + memcpy(ptr, &source->subgroup_data[i].bis_sync, + sizeof(uint32_t)); + ptr += sizeof(uint32_t); + + /* add subgroup meta_len */ + *ptr = source->subgroup_data[i].meta_len; + ptr++; + + /* add subgroup metadata */ + if (source->subgroup_data[i].meta_len > 0) { + memcpy(ptr, source->subgroup_data[i].meta, + source->subgroup_data[i].meta_len); + ptr += source->subgroup_data[i].meta_len; + } + } + + *notif_len = len; + return notif; +} + +static uint8_t *bass_build_read_rsp_from_bcst_source(struct bt_bcst_source *source, + size_t *rsp_len) +{ + return bass_build_notif_from_bcst_source(source, rsp_len); +} + +static bool bass_src_id_match(const void *data, const void *match_data) +{ + const struct bt_bcst_source *src = data; + const uint8_t *id = match_data; + + return (src->id == *id); +} + +static void bass_fill_create_big_sync_cmd_from_pa_report( + struct bt_hci_cmd_le_big_create_sync *cmd) +{ + struct iovec iov; + struct bt_ad_structure *ad_structure; + uint16_t uuid; + struct bt_hci_le_pa_base_data *base_data; + uint8_t *bis_index = (uint8_t *)cmd->bis; + + if (!pa_report) + return; + + iov.iov_base = pa_report->data; + iov.iov_len = pa_report->data_len; + + ad_structure = util_iov_pull_mem(&iov, sizeof(*ad_structure)); + if (ad_structure->ad_type != BT_AD_SERVICE_DATA16) + return; + + uuid = bt_get_le16(util_iov_pull_mem(&iov, sizeof(uint16_t))); + if (uuid != BASIC_AUDIO_ANNOUNCEMENT_SERVICE_UUID) + return; + + base_data = util_iov_pull_mem(&iov, sizeof(*base_data)); + + for (int i = 0; i < base_data->num_subgroups; i++) { + struct bt_hci_le_pa_base_subgroup *subgroup; + struct bt_hci_lv_data *codec_cfg; + struct bt_hci_lv_data *metadata; + + subgroup = util_iov_pull_mem(&iov, sizeof(*subgroup)); + + codec_cfg = util_iov_pull_mem(&iov, sizeof(*codec_cfg)); + util_iov_pull_mem(&iov, codec_cfg->len); + + metadata = util_iov_pull_mem(&iov, sizeof(*metadata)); + util_iov_pull_mem(&iov, metadata->len); + + for (int j = 0; j < subgroup->num_bis; j++) { + struct bt_hci_le_pa_base_bis *bis; + struct bt_hci_lv_data *codec_cfg; + + bis = util_iov_pull_mem(&iov, sizeof(*bis)); + *bis_index = bis->index; + bis_index++; + + codec_cfg = util_iov_pull_mem(&iov, + sizeof(*codec_cfg)); + util_iov_pull_mem(&iov, codec_cfg->len); + } + } +} + +static void bass_fill_bcst_source_from_pa_report( + struct bt_bcst_source *bcst_source) +{ + struct iovec iov; + struct bt_ad_structure *ad_structure; + uint16_t uuid; + struct bt_hci_le_pa_base_data *base_data; + + if (!pa_report) + return; + + iov.iov_base = pa_report->data; + iov.iov_len = pa_report->data_len; + + ad_structure = util_iov_pull_mem(&iov, sizeof(*ad_structure)); + if (ad_structure->ad_type != BT_AD_SERVICE_DATA16) + return; + + uuid = bt_get_le16(util_iov_pull_mem(&iov, sizeof(uint16_t))); + if (uuid != BASIC_AUDIO_ANNOUNCEMENT_SERVICE_UUID) + return; + + base_data = util_iov_pull_mem(&iov, sizeof(*base_data)); + + if (bcst_source->sync_state == BT_BASS_NOT_SYNCHRONIZED_TO_PA) { + bcst_source->sync_state = BT_BASS_SYNCHRONIZED_TO_PA; + bcst_source->num_subgroups = base_data->num_subgroups; + + bcst_source->subgroup_data = malloc(base_data->num_subgroups * + sizeof(struct bt_bcst_source)); + if (!bcst_source->subgroup_data) + return; + + memset(bcst_source->subgroup_data, 0, base_data->num_subgroups * + sizeof(struct bt_bcst_source)); + } + + if (bcst_source->num_subgroups != base_data->num_subgroups) + return; + + for (int i = 0; i < base_data->num_subgroups; i++) { + struct bt_hci_le_pa_base_subgroup *subgroup; + struct bt_hci_lv_data *codec_cfg; + struct bt_hci_lv_data *metadata; + + subgroup = util_iov_pull_mem(&iov, sizeof(*subgroup)); + codec_cfg = util_iov_pull_mem(&iov, sizeof(*codec_cfg)); + util_iov_pull_mem(&iov, codec_cfg->len); + + metadata = util_iov_pull_mem(&iov, sizeof(*metadata)); + util_iov_pull_mem(&iov, metadata->len); + + bcst_source->subgroup_data[i].meta_len = metadata->len; + free(bcst_source->subgroup_data[i].meta); + + bcst_source->subgroup_data[i].meta = malloc(metadata->len); + if (!bcst_source->subgroup_data[i].meta) + return; + + memcpy(bcst_source->subgroup_data[i].meta, + metadata->data, metadata->len); + + for (int j = 0; j < subgroup->num_bis; j++) { + struct bt_hci_le_pa_base_bis *bis; + struct bt_hci_lv_data *codec_cfg; + + bis = util_iov_pull_mem(&iov, sizeof(*bis)); + codec_cfg = util_iov_pull_mem(&iov, sizeof(*codec_cfg)); + util_iov_pull_mem(&iov, codec_cfg->len); + } + } +} + +static void bass_fill_bcst_source_bis_sync_bitmask( + struct bt_bcst_source *bcst_source) +{ + struct iovec iov; + struct bt_ad_structure *ad_structure; + uint16_t uuid; + struct bt_hci_le_pa_base_data *base_data; + + if (!pa_report) + return; + + iov.iov_base = pa_report->data; + iov.iov_len = pa_report->data_len; + + ad_structure = util_iov_pull_mem(&iov, sizeof(*ad_structure)); + if (ad_structure->ad_type != BT_AD_SERVICE_DATA16) + return; + + uuid = bt_get_le16(util_iov_pull_mem(&iov, sizeof(uint16_t))); + if (uuid != BASIC_AUDIO_ANNOUNCEMENT_SERVICE_UUID) + return; + + base_data = util_iov_pull_mem(&iov, sizeof(*base_data)); + + for (int i = 0; i < base_data->num_subgroups; i++) { + struct bt_hci_le_pa_base_subgroup *subgroup; + struct bt_hci_lv_data *codec_cfg; + struct bt_hci_lv_data *metadata; + + subgroup = util_iov_pull_mem(&iov, sizeof(*subgroup)); + + codec_cfg = util_iov_pull_mem(&iov, sizeof(*codec_cfg)); + util_iov_pull_mem(&iov, codec_cfg->len); + + metadata = util_iov_pull_mem(&iov, sizeof(*metadata)); + util_iov_pull_mem(&iov, metadata->len); + + for (int j = 0; j < subgroup->num_bis; j++) { + struct bt_hci_le_pa_base_bis *bis; + struct bt_hci_lv_data *codec_cfg; + + bis = util_iov_pull_mem(&iov, sizeof(*bis)); + bcst_source->subgroup_data[i].bis_sync |= + (1 << (bis->index - 1)); + + codec_cfg = util_iov_pull_mem(&iov, + sizeof(*codec_cfg)); + util_iov_pull_mem(&iov, codec_cfg->len); + } + } +} + +static void bass_handle_set_broadcast_code_opcode(struct bt_bass *bass, + struct gatt_db_attribute *attrib, + uint8_t opcode, + unsigned int id, + struct iovec *iov, + struct bt_att *att) +{ + struct bt_bap *bap = bap_get_session(att, bass->bdb->db); + struct bt_bass_set_bcst_code_params *params; + struct bt_bcst_source *bcst_source; + uint8_t *notify_data; + size_t notify_data_len; + struct hci_request rq; + + struct bt_hci_evt_le_big_sync_estabilished *big_sync_established_evt = NULL; + int big_sync_established_evt_len = 0; + + if (!big_sync_cmd) + goto done; + + big_sync_established_evt_len = + sizeof(struct bt_hci_evt_le_big_sync_estabilished) + + big_sync_cmd->num_bis * sizeof(uint16_t); + + big_sync_established_evt = malloc(big_sync_established_evt_len); + if (big_sync_established_evt == NULL) + goto done; + + /* validate Set Broadcast_Code command length */ + if (iov->iov_len < sizeof(struct bt_bass_set_bcst_code_params)) { + if (opcode == BT_ATT_OP_WRITE_REQ) + gatt_db_attribute_write_result(attrib, id, + BT_ERROR_WRITE_REQUEST_REJECTED); + + goto done; + } + + /* get Set Broadcast_Code command parameters */ + params = util_iov_pull_mem(iov, sizeof(*params)); + + bcst_source = queue_find(bap->ldb->bass_bcst_sources, + bass_src_id_match, + ¶ms->source_id); + + if (bcst_source == NULL) { + /* no source matches the written source_id */ + if (opcode == BT_ATT_OP_WRITE_REQ) + gatt_db_attribute_write_result(attrib, id, + BT_BASS_ERROR_INVALID_SOURCE_ID); + + goto done; + } + + memcpy(big_sync_cmd->bcode, params->bcst_code, + BT_BAP_BROADCAST_CODE_SIZE); + + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_BIG_CREATE_SYNC; + rq.event = BT_HCI_EVT_LE_BIG_SYNC_ESTABILISHED; + rq.cparam = big_sync_cmd; + rq.clen = big_sync_cmd_len; + rq.rparam = big_sync_established_evt; + rq.rlen = big_sync_established_evt_len; + + if (hci_send_req(hci_fd, &rq, 0) < 0) { + DBG(bap, "Failed to send Big Sync Create command: %s", + strerror(errno)); + goto done; + } + + if (big_sync_established_evt->status == 0x00) { + bcst_source->encryption = BT_BASS_BIG_ENC_STATE_DEC; + + bass_fill_bcst_source_bis_sync_bitmask(bcst_source); + } else { + bcst_source->encryption = BT_BASS_BIG_ENC_STATE_BAD_CODE; + memcpy(bcst_source->bad_code, params->bcst_code, + BT_BAP_BROADCAST_CODE_SIZE); + } + + notify_data = bass_build_notif_from_bcst_source(bcst_source, + ¬ify_data_len); + + gatt_db_attribute_notify(bcst_source->attr, + (void *)notify_data, + notify_data_len, att); + + free(notify_data); + +done: + + free(big_sync_cmd); + big_sync_cmd = NULL; + free(big_sync_established_evt); + free(pa_report); + pa_report = NULL; + big_sync_cmd_len = 0; +} + +#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("Set Broadcast_Code", BT_BASS_SET_BCST_CODE, + sizeof(struct bt_bass_set_bcst_code_params), + bass_handle_set_broadcast_code_opcode) +}; + +static void bass_broadcast_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 *bass = user_data; + struct bt_bass_bcst_audio_scan_cp_hdr *hdr; + struct bass_op_handler *handler; + struct iovec iov = { + .iov_base = (void *)value, + .iov_len = len, + }; + + /* validate written command length */ + if (len < (sizeof(*hdr))) { + if (opcode == BT_ATT_OP_WRITE_REQ) { + gatt_db_attribute_write_result(attrib, id, + BT_ERROR_WRITE_REQUEST_REJECTED); + } + return; + } + + /* get command header */ + hdr = util_iov_pull_mem(&iov, sizeof(*hdr)); + + if (hdr->op != BT_BASS_SET_BCST_CODE) { + if (opcode == BT_ATT_OP_WRITE_REQ) { + gatt_db_attribute_write_result(attrib, id, + BT_BASS_ERROR_OPCODE_NOT_SUPPORTED); + } + + return; + } + + /* 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); + break; + } + } + + gatt_db_attribute_write_result(attrib, id, 0x00); +} + +static bool bass_source_match_attrib(const void *data, const void *match_data) +{ + const struct bt_bcst_source *src = data; + const struct gatt_db_attribute *attr = match_data; + + return (src->attr == attr); +} + +static bool bass_source_match_sid(const void *data, const void *match_data) +{ + const struct bt_bcst_source *src = data; + const uint8_t sid = *(const uint8_t *)match_data; + + return (src->sid == sid); +} + +static void bass_bcst_receive_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 *bass = user_data; + struct bt_bap *bap = bap_get_session(att, bass->bdb->db); + uint8_t *rsp; + size_t rsp_len; + struct bt_bcst_source *bcst_source; + + bcst_source = queue_find(bap->ldb->bass_bcst_sources, + bass_source_match_attrib, + attrib); + + if (!bcst_source) { + gatt_db_attribute_read_result(attrib, id, 0, NULL, + 0); + return; + } + + /* build read response */ + rsp = bass_build_read_rsp_from_bcst_source(bcst_source, &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 bcst_recv_new(struct bt_bass *bass, int i) +{ + struct bt_bcst_recv_state *bcst_recv_state; + bt_uuid_t uuid; + + if (!bass) + return; + + bcst_recv_state = new0(struct bt_bcst_recv_state, 1); + bcst_recv_state->bass = bass; + + bt_uuid16_create(&uuid, BCST_RECV_STATE_UUID); + bcst_recv_state->attr = gatt_db_service_add_characteristic(bass->service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_NOTIFY, + bass_bcst_receive_state_read, NULL, + bass); + + bcst_recv_state->ccc = gatt_db_service_add_ccc(bass->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + bass->bcst_recv_states[i] = bcst_recv_state; +} + +static struct bt_bass *bass_new(struct gatt_db *db) +{ + struct bt_bass *bass; + bt_uuid_t uuid; + int i; + + if (!db) + return NULL; + + bass = new0(struct bt_bass, 1); + + /* Populate DB with BASS attributes */ + bt_uuid16_create(&uuid, BASS_UUID); + bass->service = gatt_db_add_service(db, &uuid, true, + 3 + (NUM_BCST_RECV_STATES * 3)); + + for (i = 0; i < NUM_BCST_RECV_STATES; i++) + bcst_recv_new(bass, i); + + bt_uuid16_create(&uuid, BCST_AUDIO_SCAN_CP_UUID); + bass->broadcast_audio_scan_cp = gatt_db_service_add_characteristic(bass->service, + &uuid, + BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_WRITE, + NULL, bass_broadcast_audio_scan_cp_write, + bass); + + gatt_db_service_set_active(bass->service, true); + + return bass; +} + static struct bt_bap_db *bap_db_new(struct gatt_db *db) { struct bt_bap_db *bdb; @@ -2182,6 +2873,7 @@ static struct bt_bap_db *bap_db_new(struct gatt_db *db) bdb->sinks = queue_new(); bdb->sources = queue_new(); bdb->endpoints = queue_new(); + bdb->bass_bcst_sources = queue_new(); if (!bap_db) bap_db = queue_new(); @@ -2192,6 +2884,9 @@ static struct bt_bap_db *bap_db_new(struct gatt_db *db) bdb->ascs = ascs_new(db); bdb->ascs->bdb = bdb; + bdb->bass = bass_new(db); + bdb->bass->bdb = bdb; + queue_push_tail(bap_db, bdb); return bdb; @@ -2236,6 +2931,20 @@ static struct bt_ascs *bap_get_ascs(struct bt_bap *bap) return bap->rdb->ascs; } +static struct bt_bass *bap_get_bass(struct bt_bap *bap) +{ + if (!bap) + return NULL; + + if (bap->rdb->bass) + return bap->rdb->bass; + + bap->rdb->bass = new0(struct bt_bass, 1); + bap->rdb->bass->bdb = bap->rdb; + + return bap->rdb->bass; +} + static bool match_codec(const void *data, const void *user_data) { const struct bt_bap_pac *pac = data; @@ -2321,6 +3030,17 @@ static void bap_pac_free(void *data) free(pac); } +static void bass_bcst_source_free(void *data) +{ + struct bt_bcst_source *bcst_source = data; + + for (int i = 0; i < bcst_source->num_subgroups; i++) + free(bcst_source->subgroup_data[i].meta); + + free(bcst_source->subgroup_data); + free(bcst_source); +} + static void bap_add_sink(struct bt_bap_pac *pac) { struct iovec iov; @@ -2512,10 +3232,12 @@ static void bap_db_free(void *data) queue_destroy(bdb->sinks, bap_pac_free); queue_destroy(bdb->sources, bap_pac_free); queue_destroy(bdb->endpoints, free); + queue_destroy(bdb->bass_bcst_sources, bass_bcst_source_free); gatt_db_unref(bdb->db); free(bdb->pacs); free(bdb->ascs); + free(bdb->bass); free(bdb); } @@ -2663,6 +3385,7 @@ struct bt_bap *bt_bap_new(struct gatt_db *ldb, struct gatt_db *rdb) bdb->sinks = queue_new(); bdb->sources = queue_new(); bdb->endpoints = queue_new(); + bdb->bass_bcst_sources = queue_new(); bap->rdb = bdb; @@ -3670,6 +4393,119 @@ static void foreach_ascs_char(struct gatt_db_attribute *attr, void *user_data) } } +static void read_broadcast_receive_state(struct bt_bap *bap, bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct gatt_db_attribute *attr = user_data; + struct bt_bcst_source *bcst_source = NULL; + + if (!success) { + DBG(bap, "Unable to read Broadcast Receive State: error 0x%02x", att_ecode); + return; + } + + if (length == 0) + return; + + bcst_source = queue_find(bap->rdb->bass_bcst_sources, + bass_source_match_attrib, attr); + + if (!bcst_source) { + bcst_source = malloc(sizeof(struct bt_bcst_source)); + + if (bcst_source == NULL) { + DBG(bap, "Failed to allocate memory for broadcast source"); + return; + } + + memset(bcst_source, 0, sizeof(struct bt_bcst_source)); + bcst_source->attr = attr; + + queue_push_tail(bap->rdb->bass_bcst_sources, bcst_source); + } + + if (bass_build_bcst_source_from_read_rsp(bcst_source, value)) { + free(bcst_source); + DBG(bap, "Failed to populate broadcast source data"); + return; + } +} + +static void bcst_recv_state_notify(struct bt_bap *bap, uint16_t value_handle, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct gatt_db_attribute *attr = user_data; + struct bt_bcst_source *bcst_source = NULL; + + bcst_source = queue_find(bap->rdb->bass_bcst_sources, + bass_source_match_attrib, attr); + + if (!bcst_source) { + bcst_source = malloc(sizeof(struct bt_bcst_source)); + + if (bcst_source == NULL) { + DBG(bap, "Failed to allocate memory for broadcast source"); + return; + } + + memset(bcst_source, 0, sizeof(struct bt_bcst_source)); + bcst_source->attr = attr; + + queue_push_tail(bap->rdb->bass_bcst_sources, bcst_source); + } + + if (bass_build_bcst_source_from_notif(bcst_source, value)) { + free(bcst_source); + DBG(bap, "Failed to populate broadcast source data"); + return; + } +} + +static void foreach_bass_char(struct gatt_db_attribute *attr, void *user_data) +{ + struct bt_bap *bap = user_data; + uint16_t value_handle; + bt_uuid_t uuid, uuid_bcst_audio_scan_cp, uuid_bcst_recv_state; + struct bt_bass *bass; + + /* 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_bcst_audio_scan_cp, BCST_AUDIO_SCAN_CP_UUID); + bt_uuid16_create(&uuid_bcst_recv_state, BCST_RECV_STATE_UUID); + + if (!bt_uuid_cmp(&uuid, &uuid_bcst_audio_scan_cp)) { + + /* found Broadcast Audio Scan Control Point characteristic */ + bass = bap_get_bass(bap); + + if (!bass || bass->broadcast_audio_scan_cp) + return; + + /* store characteristic reference */ + bass->broadcast_audio_scan_cp = attr; + + DBG(bap, "Broadcast Audio Scan Control Point found: handle 0x%04x", + value_handle); + } + + if (!bt_uuid_cmp(&uuid, &uuid_bcst_recv_state)) { + + /* found Broadcast Receive State characteristic */ + bap_read_value(bap, value_handle, read_broadcast_receive_state, attr); + + (void)bap_register_notify(bap, value_handle, + bcst_recv_state_notify, attr); + + DBG(bap, "Broadcast receive State found: handle 0x%04x", + value_handle); + } +} + static void foreach_ascs_service(struct gatt_db_attribute *attr, void *user_data) { @@ -3683,6 +4519,19 @@ static void foreach_ascs_service(struct gatt_db_attribute *attr, gatt_db_service_foreach_char(attr, foreach_ascs_char, bap); } +static void foreach_bass_service(struct gatt_db_attribute *attr, + void *user_data) +{ + struct bt_bap *bap = user_data; + struct bt_bass *bass = bap_get_bass(bap); + + /* store BASS attribute reference */ + bass->service = attr; + + /* handle BASS attributes */ + gatt_db_service_foreach_char(attr, foreach_bass_char, bap); +} + static void bap_endpoint_foreach(void *data, void *user_data) { struct bt_bap_endpoint *ep = data; @@ -3778,6 +4627,9 @@ clone: bt_uuid16_create(&uuid, ASCS_UUID); gatt_db_foreach_service(bap->rdb->db, &uuid, foreach_ascs_service, bap); + bt_uuid16_create(&uuid, BASS_UUID); + gatt_db_foreach_service(bap->rdb->db, &uuid, foreach_bass_service, bap); + return true; } @@ -4834,3 +5686,215 @@ bool bt_bap_stream_io_is_connecting(struct bt_bap_stream *stream, int *fd) return io->connecting; } + +void bap_big_info_adv_report_received_cb(struct gatt_db *db, uint8_t source_id, + struct bt_hci_evt_le_big_info_adv_report *big_info_adv_report) +{ + struct bt_bap_db *bdb = bap_get_db(db); + struct bt_bcst_source *bcst_source = NULL; + size_t notify_data_len = 0; + uint8_t *notify_data; + struct hci_request rq; + struct bt_hci_evt_le_big_sync_estabilished *big_sync_established_evt = NULL; + int big_sync_established_evt_len = 0; + + if (!pa_report) + return; + + bcst_source = queue_find(bdb->bass_bcst_sources, + bass_src_id_match, + &source_id); + + if (bcst_source == NULL) { + free(pa_report); + pa_report = NULL; + return; + } + + big_sync_cmd_len = sizeof(struct bt_hci_cmd_le_big_create_sync) + + big_info_adv_report->num_bis * + sizeof(struct bt_hci_bis_sync); + + free(big_sync_cmd); + big_sync_cmd = malloc(big_sync_cmd_len); + + if (!big_sync_cmd) { + free(pa_report); + pa_report = NULL; + return; + } + + memset(big_sync_cmd, 0, big_sync_cmd_len); + + big_sync_cmd->sync_handle = big_info_adv_report->sync_handle; + big_sync_cmd->encryption = big_info_adv_report->encryption; + big_sync_cmd->timeout = 0x4000; + big_sync_cmd->num_bis = big_info_adv_report->num_bis; + + bass_fill_create_big_sync_cmd_from_pa_report(big_sync_cmd); + + if (big_info_adv_report->encryption == 0x01) { + bcst_source->encryption = BT_BASS_BIG_ENC_STATE_BCODE_REQ; + } else { + bcst_source->encryption = BT_BASS_BIG_ENC_STATE_NO_ENC; + + big_sync_established_evt_len = + sizeof(struct bt_hci_evt_le_big_sync_estabilished) + + big_sync_cmd->num_bis * sizeof(uint16_t); + + big_sync_established_evt = malloc(big_sync_established_evt_len); + if (big_sync_established_evt == NULL) { + free(pa_report); + pa_report = NULL; + + free(big_sync_cmd); + big_sync_cmd = NULL; + big_sync_cmd_len = 0; + + goto done; + } + + /* create BIG sync */ + rq.ogf = OGF_LE_CTL; + rq.ocf = 0x006B; + rq.event = BT_HCI_EVT_LE_BIG_SYNC_ESTABILISHED; + rq.cparam = big_sync_cmd; + rq.clen = big_sync_cmd_len; + rq.rparam = big_sync_established_evt; + rq.rlen = big_sync_established_evt_len; + + if (hci_send_req(hci_fd, &rq, 0) < 0) { + free(pa_report); + pa_report = NULL; + + free(big_sync_cmd); + big_sync_cmd = NULL; + big_sync_cmd_len = 0; + + free(big_sync_established_evt); + + goto done; + } + + if (!big_sync_established_evt->status) + bass_fill_bcst_source_bis_sync_bitmask(bcst_source); + } + +done: + + notify_data = bass_build_notif_from_bcst_source(bcst_source, + ¬ify_data_len); + + gatt_db_attribute_notify(bcst_source->attr, (void *)notify_data, + notify_data_len, NULL); + + free(notify_data); +} + +void bap_pa_report_received_cb(struct gatt_db *db, uint8_t source_id, + struct bt_hci_le_pa_report *report) +{ + struct bt_bcst_source *bcst_source = NULL; + struct bt_bap_db *bdb = bap_get_db(db); + uint8_t report_len = sizeof(struct bt_hci_le_pa_report) + + report->data_len; + + printf("Report len = %d\n\n", report->data_len); + + free(pa_report); + pa_report = malloc(report_len); + if (!pa_report) + return; + + memcpy(pa_report, report, report_len); + + bcst_source = queue_find(bdb->bass_bcst_sources, + bass_src_id_match, + &source_id); + + if (!bcst_source) + return; + + bass_fill_bcst_source_from_pa_report(bcst_source); +} + +int bap_ext_adv_report_received_cb(struct gatt_db *db, + struct bt_hci_le_ext_adv_report *ext_adv_report) +{ + struct bt_bcst_source *bcst_source = NULL; + struct gatt_db_attribute *attr = NULL; + struct bt_bap_db *bdb = bap_get_db(db); + struct bt_ad_structure *ad_structure; + uint16_t uuid; + uint8_t *bid; + + struct iovec iov = { + .iov_base = ext_adv_report->data, + .iov_len = ext_adv_report->data_len, + }; + + bcst_source = queue_find(bdb->bass_bcst_sources, + bass_source_match_sid, + &ext_adv_report->sid); + + if (bcst_source != NULL) + return -1; + + bcst_source = malloc(sizeof(struct bt_bcst_source)); + + if (bcst_source == NULL) + return -1; + + memset(bcst_source, 0, sizeof(struct bt_bcst_source)); + + bcst_source->id = next_available_source_id++; + bcst_source->addr_type = ext_adv_report->addr_type; + memcpy(bcst_source->addr, ext_adv_report->addr, 6); + bcst_source->sid = ext_adv_report->sid; + + ad_structure = util_iov_pull_mem(&iov, sizeof(*ad_structure)); + + while (ad_structure) { + if (ad_structure->ad_type == BT_AD_SERVICE_DATA16) { + uuid = bt_get_le16(util_iov_pull_mem(&iov, sizeof(uint16_t))); + if (uuid == BCST_AUDIO_ANNOUNCEMENT_SERVICE_UUID) { + bid = util_iov_pull_mem(&iov, + BT_BAP_BROADCAST_ID_SIZE); + memcpy(bcst_source->bid, bid, + BT_BAP_BROADCAST_ID_SIZE); + break; + } + } + + ad_structure = util_iov_pull_mem(&iov, sizeof(*ad_structure)); + } + + for (int i = 0; i < NUM_BCST_RECV_STATES; i++) { + attr = bdb->bass->bcst_recv_states[i]->attr; + + if (queue_find(bdb->bass_bcst_sources, + bass_source_match_attrib, attr) == NULL) { + bcst_source->attr = attr; + break; + } + } + + queue_push_tail(bdb->bass_bcst_sources, bcst_source); + + return bcst_source->id; +} + +int bt_bap_register_device(int dev_id) +{ + /* Open HCI */ + hci_fd = hci_open_dev(dev_id); + if (hci_fd == -1) + return -1; + + return 0; +} + +void bt_bap_register_db(struct gatt_db *db) +{ + bap_db_new(db); +} diff --git a/src/shared/bap.h b/src/shared/bap.h index 47a15636c..f72ecbd76 100644 --- a/src/shared/bap.h +++ b/src/shared/bap.h @@ -9,6 +9,7 @@ #include <stdbool.h> #include <inttypes.h> +#include "monitor/bt.h" #ifndef __packed #define __packed __attribute__((packed)) @@ -33,6 +34,9 @@ #define BT_BAP_CONFIG_PHY_2M 0x02 #define BT_BAP_CONFIG_PHY_CODEC 0x03 +#define BT_BAP_BROADCAST_CODE_SIZE 16 +#define BT_BAP_BROADCAST_ID_SIZE 3 + struct bt_bap; struct bt_bap_pac; struct bt_bap_stream; @@ -62,6 +66,17 @@ struct bt_bap_qos { uint8_t target_latency; /* Target Latency */ }; +struct bt_ad_structure { + uint8_t ad_len; + uint8_t ad_type; + uint8_t value[0]; +} __packed; + +struct bt_broadcast_audio_announcement { + uint16_t uuid; + uint8_t bid[BT_BAP_BROADCAST_ID_SIZE]; +} __packed; + typedef void (*bt_bap_ready_func_t)(struct bt_bap *bap, void *user_data); typedef void (*bt_bap_destroy_func_t)(void *user_data); typedef void (*bt_bap_debug_func_t)(const char *str, void *user_data); @@ -267,3 +282,13 @@ uint8_t bt_bap_stream_io_dir(struct bt_bap_stream *stream); int bt_bap_stream_io_connecting(struct bt_bap_stream *stream, int fd); bool bt_bap_stream_io_is_connecting(struct bt_bap_stream *stream, int *fd); + +int bap_ext_adv_report_received_cb(struct gatt_db *db, + struct bt_hci_le_ext_adv_report *ext_adv_report); +void bap_pa_report_received_cb(struct gatt_db *db, uint8_t source_id, + struct bt_hci_le_pa_report *report); +void bap_big_info_adv_report_received_cb(struct gatt_db *db, uint8_t source_id, + struct bt_hci_evt_le_big_info_adv_report *big_info_adv_report); + +int bt_bap_register_device(int dev_id); +void bt_bap_register_db(struct gatt_db *db); diff --git a/src/shared/bass.h b/src/shared/bass.h new file mode 100644 index 000000000..ad85e8f62 --- /dev/null +++ b/src/shared/bass.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2022 Intel Corporation. All rights reserved. + * + */ + +#define NUM_BCST_RECV_STATES 2 + +/* 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 + +/* Broadcast Audio Scan Control Point + * header structure + */ +struct bt_bass_bcst_audio_scan_cp_hdr { + uint8_t op; +} __packed; + +#define BT_BASS_SET_BCST_CODE 0x04 + +struct bt_bass_set_bcst_code_params { + uint8_t source_id; + uint8_t bcst_code[BT_BAP_BROADCAST_CODE_SIZE]; +} __packed; -- 2.34.1