This implements the MediaAssistant "Push" method, which sends a GATT write command for the Broadcast Audio Scan Control Point characteristic with the "Add Source" opcode. This also registers a callback with shared/bass, to handle GATT notifications from the peer. A MediaAssistant object transitions to different states, depending on information received in the callback. The callback is also useful to check if the peer requires a Broadcast Code to decrypt the stream - in this case, the Assistant uses the "Set Broadcast_Code" operation to provide the code. --- profiles/audio/bass.c | 295 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) diff --git a/profiles/audio/bass.c b/profiles/audio/bass.c index 795b4b80a..b3740f64e 100644 --- a/profiles/audio/bass.c +++ b/profiles/audio/bass.c @@ -70,10 +70,18 @@ enum assistant_state { */ }; +static const char *const str_state[] = { + "ASSISTANT_STATE_IDLE", + "ASSISTANT_STATE_PENDING", + "ASSISTANT_STATE_REQUESTING", + "ASSISTANT_STATE_ACTIVE", +}; + struct bass_data { struct btd_device *device; struct btd_service *service; struct bt_bass *bass; + unsigned int src_id; }; struct bass_assistant { @@ -92,14 +100,206 @@ struct bass_assistant { static struct queue *sessions; static struct queue *assistants; +static const char *state2str(enum assistant_state state); + static void bass_debug(const char *str, void *user_data) { DBG_IDX(0xffff, "%s", str); } +static void assistant_set_state(struct bass_assistant *assistant, + enum assistant_state state) +{ + enum assistant_state old_state = assistant->state; + const char *str; + + if (old_state == state) + return; + + assistant->state = state; + + DBG("State changed %s: %s -> %s", assistant->path, str_state[old_state], + str_state[state]); + + str = state2str(state); + + if (g_strcmp0(str, state2str(old_state)) != 0) + g_dbus_emit_property_changed(btd_get_dbus_connection(), + assistant->path, + MEDIA_ASSISTANT_INTERFACE, + "State"); +} + +static int assistant_parse_qos(struct bass_assistant *assistant, + DBusMessageIter *iter) +{ + DBusMessageIter dict; + const char *key; + + dbus_message_iter_recurse(iter, &dict); + + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter value, entry; + int var; + + dbus_message_iter_recurse(&dict, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + var = dbus_message_iter_get_arg_type(&value); + + if (!strcasecmp(key, "BCode")) { + DBusMessageIter array; + struct iovec iov = {0}; + + if (var != DBUS_TYPE_ARRAY) + return -EINVAL; + + dbus_message_iter_recurse(&value, &array); + dbus_message_iter_get_fixed_array(&array, + &iov.iov_base, + (int *)&iov.iov_len); + + if (iov.iov_len != BT_BASS_BCAST_CODE_SIZE) { + error("Invalid size for BCode: %zu != 16", + iov.iov_len); + return -EINVAL; + } + + memcpy(assistant->qos.bcast.bcode, iov.iov_base, + iov.iov_len); + + return 0; + } + + dbus_message_iter_next(&dict); + } + + return 0; +} + +static int assistant_parse_props(struct bass_assistant *assistant, + DBusMessageIter *props) +{ + DBusMessageIter value, entry, array; + const char *key; + + while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { + dbus_message_iter_recurse(props, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + if (!strcasecmp(key, "Metadata")) { + struct iovec iov; + + if (dbus_message_iter_get_arg_type(&value) != + DBUS_TYPE_ARRAY) + goto fail; + + dbus_message_iter_recurse(&value, &array); + dbus_message_iter_get_fixed_array(&array, + &iov.iov_base, + (int *)&iov.iov_len); + + util_iov_free(assistant->meta, 1); + assistant->meta = util_iov_dup(&iov, 1); + DBG("Parsed Metadata"); + } else if (!strcasecmp(key, "QoS")) { + if (dbus_message_iter_get_arg_type(&value) != + DBUS_TYPE_ARRAY) + goto fail; + + if (assistant_parse_qos(assistant, &value)) + goto fail; + + DBG("Parsed QoS"); + } + + dbus_message_iter_next(props); + } + + return 0; + +fail: + DBG("Failed parsing %s", key); + + return -EINVAL; +} + static DBusMessage *push(DBusConnection *conn, DBusMessage *msg, void *user_data) { + struct bass_assistant *assistant = user_data; + struct bt_bass_bcast_audio_scan_cp_hdr hdr; + struct bt_bass_add_src_params params; + struct iovec iov = {0}; + uint32_t bis_sync = 0; + uint8_t meta_len = 0; + int err; + DBusMessageIter props, dict; + + DBG(""); + + dbus_message_iter_init(msg, &props); + + if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_ARRAY) { + DBG("Unable to parse properties"); + return btd_error_invalid_args(msg); + } + + dbus_message_iter_recurse(&props, &dict); + + if (assistant_parse_props(assistant, &dict)) { + DBG("Unable to parse properties"); + return btd_error_invalid_args(msg); + } + + hdr.op = BT_BASS_ADD_SRC; + + if (device_get_le_address_type(assistant->device) == BDADDR_LE_PUBLIC) + params.addr_type = BT_BASS_ADDR_PUBLIC; + else + params.addr_type = BT_BASS_ADDR_RANDOM; + + bacpy(¶ms.addr, device_get_address(assistant->device)); + put_le24(assistant->bid, params.bid); + params.pa_sync = PA_SYNC_NO_PAST; + params.pa_interval = PA_INTERVAL_UNKNOWN; + params.num_subgroups = assistant->sgrp + 1; + + util_iov_append(&iov, ¶ms, sizeof(params)); + + /* Metadata and the BIS index associated with the MediaAssistant + * object will be set in the subgroup they belong to. For the other + * subgroups, no metadata and no BIS index will be provided. + */ + for (uint8_t sgrp = 0; sgrp < assistant->sgrp; sgrp++) { + util_iov_append(&iov, &bis_sync, sizeof(bis_sync)); + util_iov_append(&iov, &meta_len, sizeof(meta_len)); + } + + bis_sync = (1 << (assistant->bis - 1)); + meta_len = assistant->meta->iov_len; + + util_iov_append(&iov, &bis_sync, sizeof(bis_sync)); + util_iov_append(&iov, &meta_len, sizeof(meta_len)); + util_iov_append(&iov, assistant->meta->iov_base, + assistant->meta->iov_len); + + err = bt_bass_send(assistant->data->bass, &hdr, &iov); + if (err) { + DBG("Unable to send BASS Write Command"); + return btd_error_failed(msg, strerror(-err)); + } + + free(iov.iov_base); + + assistant_set_state(assistant, ASSISTANT_STATE_PENDING); + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } @@ -381,6 +581,8 @@ static void bass_data_free(struct bass_data *data) bt_bass_set_user_data(data->bass, NULL); } + bt_bass_src_unregister(data->bass, data->src_id); + bt_bass_unref(data->bass); queue_remove_all(assistants, assistant_match_data, @@ -453,6 +655,93 @@ static void bass_attached(struct bt_bass *bass, void *user_data) bass_data_add(data); } +static void bass_handle_bcode_req(struct bass_assistant *assistant, int id) +{ + struct bt_bass_bcast_audio_scan_cp_hdr hdr; + struct bt_bass_set_bcast_code_params params; + struct iovec iov = {0}; + int err; + + assistant_set_state(assistant, ASSISTANT_STATE_REQUESTING); + + hdr.op = BT_BASS_SET_BCAST_CODE; + + params.id = id; + memcpy(params.bcast_code, assistant->qos.bcast.bcode, + BT_BASS_BCAST_CODE_SIZE); + + iov.iov_base = malloc0(sizeof(params)); + if (!iov.iov_base) + return; + + util_iov_push_mem(&iov, sizeof(params), ¶ms); + + err = bt_bass_send(assistant->data->bass, &hdr, &iov); + if (err) { + DBG("Unable to send BASS Write Command"); + return; + } + + free(iov.iov_base); +} + +static void bass_src_changed(uint8_t id, uint32_t bid, uint8_t enc, + uint32_t bis_sync, void *user_data) +{ + const struct queue_entry *entry; + + for (entry = queue_get_entries(assistants); entry; + entry = entry->next) { + struct bass_assistant *assistant = entry->data; + uint32_t bis = 1 << (assistant->bis - 1); + + if (assistant->bid != bid) + /* Only handle assistant objects + * that match the source + */ + continue; + + switch (enc) { + case BT_BASS_BIG_ENC_STATE_BCODE_REQ: + if (assistant->state != ASSISTANT_STATE_PENDING) + /* Only handle assistant objects that + * have been pushed by the user + */ + break; + + /* Provide Broadcast Code to peer */ + bass_handle_bcode_req(assistant, id); + break; + case BT_BASS_BIG_ENC_STATE_NO_ENC: + if (assistant->state != ASSISTANT_STATE_PENDING) + /* Only handle assistant objects that + * have been pushed by the user + */ + break; + + /* Match BIS index */ + if (bis & bis_sync) + assistant_set_state(assistant, + ASSISTANT_STATE_ACTIVE); + break; + case BT_BASS_BIG_ENC_STATE_DEC: + /* Only handle assistant objects that + * have requested a Broadcast Code + */ + if (assistant->state != ASSISTANT_STATE_REQUESTING) + break; + + /* Match BIS index */ + if (bis & bis_sync) + assistant_set_state(assistant, + ASSISTANT_STATE_ACTIVE); + break; + default: + continue; + } + } +} + static int bass_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); @@ -485,6 +774,12 @@ static int bass_probe(struct btd_service *service) bass_data_add(data); bt_bass_set_user_data(data->bass, service); + /* Register callback to be called when notifications for + * Broadcast Receive State characteristics are received. + */ + data->src_id = bt_bass_src_register(data->bass, bass_src_changed, + data, NULL); + return 0; } -- 2.39.2