| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| #include "host/ble_uuid.h" |
| #include "audio/ble_audio_broadcast_source.h" |
| |
| #include "os/util.h" |
| |
| #if MYNEWT_VAL(BLE_ISO_BROADCAST_SOURCE) |
| struct ble_audio_broadcast { |
| SLIST_ENTRY(ble_audio_broadcast) next; |
| uint8_t big_handle; |
| uint8_t adv_instance; |
| struct ble_audio_base *base; |
| struct ble_iso_big_params *big_params; |
| ble_audio_broadcast_destroy_fn *destroy_cb; |
| void *args; |
| }; |
| |
| static SLIST_HEAD(, ble_audio_broadcast) ble_audio_broadcasts; |
| static struct os_mempool ble_audio_broadcast_pool; |
| static os_membuf_t ble_audio_broadcast_mem[ |
| OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_ISO_MAX_BIGS), |
| sizeof(struct ble_audio_broadcast))]; |
| |
| static bool |
| broadcast_bis_idx_ok(uint8_t bis_idx, struct ble_audio_base *base) |
| { |
| struct ble_audio_big_subgroup *big; |
| struct ble_audio_bis *bis; |
| uint8_t idx_cnt = 0; |
| |
| STAILQ_FOREACH(big, &base->subs, next) { |
| STAILQ_FOREACH(bis, &big->bises, next) { |
| if (bis_idx == bis->idx) { |
| idx_cnt++; |
| } |
| } |
| } |
| |
| return idx_cnt == 1; |
| } |
| |
| static bool |
| broadcast_ltv_ok(uint8_t total_len, uint8_t *data) |
| { |
| if (total_len == 0 && data == NULL) { |
| return true; |
| } |
| uint8_t len = data[0]; |
| uint8_t sum_len = 0; |
| |
| while (total_len > sum_len) { |
| sum_len += len + 1; |
| len = data[sum_len]; |
| } |
| |
| return total_len == sum_len; |
| } |
| |
| static struct ble_audio_broadcast * |
| ble_audio_broadcast_find(uint8_t adv_instance) |
| { |
| struct ble_audio_broadcast *broadcast; |
| |
| SLIST_FOREACH(broadcast, &ble_audio_broadcasts, next) { |
| if (broadcast->adv_instance == adv_instance) { |
| return broadcast; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static struct ble_audio_broadcast * |
| ble_audio_broadcast_find_last() |
| { |
| struct ble_audio_broadcast *broadcast; |
| |
| SLIST_FOREACH(broadcast, &ble_audio_broadcasts, next) { |
| } |
| |
| return broadcast; |
| } |
| |
| int |
| ble_audio_broadcast_create(const struct ble_broadcast_create_params *params, |
| ble_audio_broadcast_destroy_fn *destroy_cb, |
| void *args, |
| ble_gap_event_fn *gap_cb) |
| { |
| int rc; |
| uint8_t offset = 2; |
| uint8_t service_data[5] = {0x52, 0x18 }; |
| uint8_t per_svc_data[MYNEWT_VAL(BLE_EXT_ADV_MAX_SIZE)] = { 0x51, 0x18 }; |
| struct ble_audio_big_subgroup *subgroup; |
| struct ble_audio_bis *bis; |
| struct ble_hs_adv_fields adv_fields = { |
| .broadcast_name = (uint8_t *) params->name, |
| .broadcast_name_len = params->name != NULL ? strlen(params->name) : 0, |
| .svc_data_uuid16 = service_data, |
| .svc_data_uuid16_len = sizeof(service_data), |
| }; |
| struct ble_hs_adv_fields per_adv_fields = { 0 }; |
| struct os_mbuf *adv_data; |
| ble_uuid16_t audio_announcement_uuid[1]; |
| uint8_t broadcast_id[3]; |
| struct ble_audio_broadcast *broadcast; |
| |
| if (params->adv_instance >= BLE_ADV_INSTANCES) { |
| BLE_HS_LOG_ERROR("Invalid advertising instance"); |
| return BLE_HS_EINVAL; |
| } |
| |
| if (strlen(params->name) < 4 || strlen(params->name) > 32) { |
| return BLE_HS_EINVAL; |
| } |
| |
| broadcast = ble_audio_broadcast_find(params->adv_instance); |
| if (broadcast) { |
| BLE_HS_LOG_ERROR("Advertising instance (%d) already in use", |
| params->adv_instance); |
| return BLE_HS_EALREADY; |
| } |
| |
| ble_hs_hci_rand(broadcast_id, 3); |
| params->base->broadcast_id = get_le24(broadcast_id); |
| |
| broadcast = os_memblock_get(&ble_audio_broadcast_pool); |
| |
| broadcast->adv_instance = params->adv_instance; |
| broadcast->base = params->base; |
| broadcast->big_params = params->big_params; |
| broadcast->destroy_cb = destroy_cb; |
| broadcast->args = args; |
| |
| if (SLIST_EMPTY(&ble_audio_broadcasts)) { |
| SLIST_INSERT_HEAD(&ble_audio_broadcasts, broadcast, next); |
| } else { |
| SLIST_INSERT_AFTER(ble_audio_broadcast_find_last(), broadcast, next); |
| } |
| |
| if (params->base->num_subgroups == 0) { |
| /** |
| * BAP specification 3.7.2.2 Basic Audio Announcements: |
| * Rule 1: There shall be at least one subgroup. |
| */ |
| BLE_HS_LOG_ERROR("No subgroups in BASE!\n"); |
| return BLE_HS_EINVAL; |
| } |
| |
| put_le24(&service_data[2], broadcast->base->broadcast_id); |
| put_le24(&per_svc_data[offset], params->base->presentation_delay); |
| offset += 3; |
| per_svc_data[offset] = params->base->num_subgroups; |
| offset++; |
| |
| STAILQ_FOREACH(subgroup, ¶ms->base->subs, next) { |
| if (subgroup->bis_cnt == 0) { |
| /** |
| * BAP specification 3.7.2.2 Basic Audio Announcements: |
| * Rule 2: There shall be at least one BIS per subgroup. |
| */ |
| BLE_HS_LOG_ERROR("No BIS in BIG!\n"); |
| return BLE_HS_EINVAL; |
| } |
| |
| if (!broadcast_ltv_ok(subgroup->metadata_len, subgroup->metadata)) { |
| BLE_HS_LOG_ERROR("Invalid Metadata!\n"); |
| return BLE_HS_EINVAL; |
| } |
| |
| if (!broadcast_ltv_ok(subgroup->codec_spec_config_len, subgroup->codec_spec_config)) { |
| BLE_HS_LOG_ERROR("Invalid Codec Configuration in subgroup!\n"); |
| return BLE_HS_EINVAL; |
| } |
| |
| per_svc_data[offset] = subgroup->bis_cnt; |
| offset++; |
| memcpy(&per_svc_data[offset], &subgroup->codec_id, 5); |
| offset += 5; |
| per_svc_data[offset] = subgroup->codec_spec_config_len; |
| offset++; |
| memcpy(&per_svc_data[offset], &subgroup->codec_spec_config, |
| subgroup->codec_spec_config_len); |
| offset += subgroup->codec_spec_config_len; |
| per_svc_data[offset] = subgroup->metadata_len; |
| offset++; |
| memcpy(&per_svc_data[offset], &subgroup->metadata, |
| subgroup->metadata_len); |
| offset += subgroup->metadata_len; |
| STAILQ_FOREACH(bis, &subgroup->bises, next) { |
| if (!broadcast_bis_idx_ok(bis->idx, params->base)) { |
| /** |
| * BAP specification 3.7.2.2 Basic Audio Announcements: |
| * Rule 3: Every BIS in the BIG, denoted by its BIS_index |
| * value, shall only be present in one subgroup. |
| */ |
| BLE_HS_LOG_ERROR("Duplicated BIS index!\n"); |
| return BLE_HS_EINVAL; |
| } |
| |
| if (!broadcast_ltv_ok(bis->codec_spec_config_len, |
| bis->codec_spec_config)) { |
| BLE_HS_LOG_ERROR("Invalid Codec Configuration in BIS!\n"); |
| return BLE_HS_EINVAL; |
| } |
| per_svc_data[offset] = bis->idx; |
| offset++; |
| per_svc_data[offset] = bis->codec_spec_config_len; |
| offset++; |
| memcpy(&per_svc_data[offset], bis->codec_spec_config, |
| bis->codec_spec_config_len); |
| offset += bis->codec_spec_config_len; |
| } |
| } |
| |
| audio_announcement_uuid[0] = (ble_uuid16_t) BLE_UUID16_INIT(BLE_BROADCAST_AUDIO_ANNOUNCEMENT_SVC_UUID); |
| per_adv_fields.uuids16 = audio_announcement_uuid; |
| per_adv_fields.num_uuids16 = 1; |
| per_adv_fields.uuids16_is_complete = 1; |
| per_adv_fields.svc_data_uuid16 = per_svc_data; |
| per_adv_fields.svc_data_uuid16_len = offset; |
| |
| rc = ble_gap_ext_adv_configure(params->adv_instance, |
| params->extended_params, 0, |
| gap_cb, NULL); |
| if (rc) { |
| BLE_HS_LOG_ERROR("Could not configure the broadcast (rc=%d)\n", rc); |
| return 0; |
| } |
| |
| adv_data = os_msys_get_pkthdr(BLE_HCI_MAX_EXT_ADV_DATA_LEN, 0); |
| if (!adv_data) { |
| BLE_HS_LOG_ERROR("No memory\n"); |
| return BLE_HS_ENOMEM; |
| } |
| |
| /* Set ext advertising data */ |
| rc = ble_hs_adv_set_fields_mbuf(&adv_fields, adv_data); |
| if (rc) { |
| BLE_HS_LOG_ERROR("Failed to set extended advertising fields" |
| "(rc=%d)\n", rc); |
| return rc; |
| } |
| |
| os_mbuf_append(adv_data, params->svc_data, params->svc_data_len); |
| |
| rc = ble_gap_ext_adv_set_data(params->adv_instance, adv_data); |
| if (rc) { |
| BLE_HS_LOG_ERROR("Failed to set extended advertising data" |
| "(rc=%d)\n", rc); |
| return rc; |
| } |
| |
| /* Clear buffer */ |
| adv_data = os_msys_get_pkthdr(BLE_HCI_MAX_EXT_ADV_DATA_LEN, 0); |
| if (!adv_data) { |
| BLE_HS_LOG_ERROR("No memory\n"); |
| return BLE_HS_ENOMEM; |
| } |
| |
| rc = ble_gap_periodic_adv_configure(params->adv_instance, params->periodic_params); |
| if (rc) { |
| BLE_HS_LOG_ERROR("failed to configure periodic advertising" |
| "(rc=%d)\n", rc); |
| return rc; |
| } |
| |
| rc = ble_hs_adv_set_fields_mbuf(&per_adv_fields, adv_data); |
| if (rc) { |
| BLE_HS_LOG_ERROR("Failed to set periodic advertising fields" |
| "(rc=%d)\n", rc); |
| return rc; |
| } |
| |
| rc = ble_gap_periodic_adv_set_data(params->adv_instance, adv_data, NULL); |
| if (rc) { |
| BLE_HS_LOG_ERROR("Failed to set periodic advertising data" |
| "(rc=%d)\n", rc); |
| } |
| |
| return rc; |
| } |
| |
| int |
| ble_audio_broadcast_start(uint8_t adv_instance, |
| ble_iso_event_fn *cb, void *cb_arg) |
| { |
| struct ble_audio_big_subgroup *subgroup; |
| struct ble_iso_create_big_params create_big_params = { 0 }; |
| struct ble_audio_broadcast *broadcast; |
| int rc; |
| |
| broadcast = ble_audio_broadcast_find(adv_instance); |
| if (!broadcast) { |
| return BLE_HS_ENOENT; |
| } |
| |
| STAILQ_FOREACH(subgroup, &broadcast->base->subs, next) { |
| create_big_params.bis_cnt += subgroup->bis_cnt; |
| } |
| |
| create_big_params.adv_handle = broadcast->adv_instance; |
| create_big_params.cb = cb; |
| create_big_params.cb_arg = cb_arg; |
| |
| rc = ble_gap_ext_adv_start(broadcast->adv_instance, 0, 0); |
| if (rc) { |
| BLE_HS_LOG_ERROR("Failed to start extended advertising (rc=%d)\n", rc); |
| return rc; |
| } |
| |
| rc = ble_gap_periodic_adv_start(broadcast->adv_instance, NULL); |
| if (rc) { |
| BLE_HS_LOG_ERROR("Failed to start periodic advertising (rc=%d)\n", rc); |
| return rc; |
| } |
| |
| rc = ble_iso_create_big(&create_big_params, broadcast->big_params, |
| &broadcast->big_handle); |
| if (rc) { |
| BLE_HS_LOG_ERROR("Failed to create BIG (rc=%d)\n", rc); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| int |
| ble_audio_broadcast_stop(uint8_t adv_instance) |
| { |
| int rc; |
| |
| rc = ble_gap_ext_adv_stop(adv_instance); |
| if (rc) { |
| BLE_HS_LOG_ERROR("Failed to stop extended advertising (rc=%d)\n", rc); |
| return rc; |
| } |
| |
| rc = ble_gap_periodic_adv_stop(adv_instance); |
| if (rc) { |
| BLE_HS_LOG_ERROR("Failed to stop periodic advertising (rc=%d)\n", rc); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| int |
| ble_audio_broadcast_destroy(uint8_t adv_instance) |
| { |
| struct ble_audio_broadcast *broadcast; |
| int rc; |
| |
| broadcast = ble_audio_broadcast_find(adv_instance); |
| if (!broadcast) { |
| return BLE_HS_ENOENT; |
| } |
| |
| rc = ble_gap_ext_adv_remove(adv_instance); |
| if (rc) { |
| BLE_HS_LOG_ERROR("Failed to remove extended advertising\n"); |
| return rc; |
| } |
| |
| rc = ble_iso_terminate_big(broadcast->big_handle); |
| if (rc) { |
| BLE_HS_LOG_ERROR("Failed to terminate BIG\n"); |
| return rc; |
| } |
| |
| os_memblock_put(&ble_audio_broadcast_pool, broadcast); |
| SLIST_REMOVE(&ble_audio_broadcasts, broadcast, ble_audio_broadcast, next); |
| |
| return 0; |
| } |
| |
| int |
| ble_audio_broadcast_update(const struct ble_broadcast_update_params *params) |
| { |
| uint8_t service_data[5] = {0x52, 0x18 }; |
| struct ble_hs_adv_fields adv_fields = { |
| .broadcast_name = (uint8_t *) params->name, |
| .broadcast_name_len = params->name != NULL ? strlen(params->name) : 0, |
| .svc_data_uuid16 = service_data, |
| .svc_data_uuid16_len = sizeof(service_data) |
| }; |
| struct os_mbuf *adv_data; |
| int rc; |
| |
| if (strlen(params->name) < 4 || strlen(params->name) > 32) { |
| return BLE_HS_EINVAL; |
| } |
| |
| memcpy(&service_data[2], ¶ms->broadcast_id, 3); |
| |
| adv_data = os_msys_get_pkthdr(BLE_HCI_MAX_EXT_ADV_DATA_LEN, 0); |
| |
| os_mbuf_append(adv_data, params->svc_data, params->svc_data_len); |
| |
| /* Set ext advertising data */ |
| rc = ble_hs_adv_set_fields_mbuf(&adv_fields, adv_data); |
| if (rc) { |
| BLE_HS_LOG_ERROR("Failed to set extended advertising fields\n"); |
| return rc; |
| } |
| |
| rc = ble_gap_ext_adv_set_data(params->adv_instance, adv_data); |
| if (rc) { |
| BLE_HS_LOG_ERROR("Failed to set extended advertising data\n"); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| int |
| ble_audio_broadcast_build_sub(struct ble_audio_base *base, |
| struct ble_audio_big_subgroup *sub, |
| const struct ble_broadcast_subgroup_params |
| *params) |
| { |
| sub->codec_id = *params->codec_id; |
| memcpy(sub->codec_spec_config, params->codec_spec_config, |
| params->codec_spec_config_len); |
| sub->codec_spec_config_len = params->codec_spec_config_len; |
| memcpy(sub->metadata, params->metadata, |
| params->metadata_len); |
| sub->metadata_len = params->metadata_len; |
| |
| if (STAILQ_EMPTY(&base->subs)) { |
| STAILQ_INSERT_HEAD(&base->subs, sub, next); |
| } else { |
| STAILQ_INSERT_TAIL(&base->subs, sub, next); |
| } |
| |
| base->num_subgroups++; |
| return 0; |
| } |
| |
| int |
| ble_audio_broadcast_build_bis(struct ble_audio_big_subgroup *sub, |
| struct ble_audio_bis *bis, |
| const struct ble_broadcast_bis_params |
| *params) |
| { |
| bis->idx = params->idx; |
| memcpy(bis->codec_spec_config, params->codec_spec_config, |
| params->codec_spec_config_len); |
| bis->codec_spec_config_len = params->codec_spec_config_len; |
| |
| if (STAILQ_EMPTY(&sub->bises)) { |
| STAILQ_INSERT_HEAD(&sub->bises, bis, next); |
| } else { |
| STAILQ_INSERT_TAIL(&sub->bises, bis, next); |
| } |
| |
| sub->bis_cnt++; |
| return 0; |
| } |
| |
| |
| int |
| ble_audio_broadcast_init(void) |
| { |
| int rc; |
| |
| SLIST_INIT(&ble_audio_broadcasts); |
| |
| rc = os_mempool_init(&ble_audio_broadcast_pool, |
| MYNEWT_VAL(BLE_ISO_MAX_BIGS), |
| sizeof(struct ble_audio_broadcast), |
| ble_audio_broadcast_mem, "ble_audio_broadcast_pool"); |
| SYSINIT_PANIC_ASSERT(rc == 0); |
| |
| return 0; |
| } |
| #endif |