blob: 61c42e4ff8d0924651eb38893bd122ea951405d8 [file] [log] [blame]
/*
* 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 <string.h>
#include <stddef.h>
#include "host/ble_hs.h"
#include "host/audio/ble_audio.h"
#include "ble_audio_priv.h"
static struct ble_gap_event_listener ble_audio_gap_event_listener;
static SLIST_HEAD(, ble_audio_event_listener) ble_audio_event_listener_list =
SLIST_HEAD_INITIALIZER(ble_audio_event_listener_list);
struct ble_audio_adv_parse_bcst_announcement_data {
struct ble_audio_event event;
struct ble_audio_pub_bcst_announcement pub;
struct ble_audio_bcst_name name;
bool success;
};
static int
ble_audio_adv_parse_bcst_announcement(const struct ble_hs_adv_field *field,
void *user_data)
{
struct ble_audio_adv_parse_bcst_announcement_data *data = user_data;
struct ble_audio_event_bcst_announcement *event;
const uint8_t value_len = field->length - sizeof(field->length);
ble_uuid16_t uuid16 = BLE_UUID16_INIT(0);
uint8_t offset = 0;
event = &data->event.bcst_announcement;
data->success = false;
switch (field->type) {
case BLE_HS_ADV_TYPE_SVC_DATA_UUID16:
if (value_len < 2) {
break;
}
uuid16.value = get_le16(&field->value[offset]);
offset += 2;
switch (uuid16.value) {
case BLE_BROADCAST_AUDIO_ANNOUNCEMENT_SVC_UUID:
if ((value_len - offset) < 3) {
/* Stop parsing */
data->success = false;
return 0;
}
event->broadcast_id = get_le24(&field->value[offset]);
offset += 3;
if (value_len > offset) {
event->svc_data = &field->value[offset];
event->svc_data_len = value_len - offset;
}
data->success = true;
break;
case BLE_BROADCAST_PUB_ANNOUNCEMENT_SVC_UUID:
if (event->pub_announcement_data != NULL) {
/* Ignore */
break;
}
if ((value_len - offset) < 2) {
/* Stop parsing */
data->success = false;
return 0;
}
data->pub.features = field->value[offset++];
data->pub.metadata_len = field->value[offset++];
if ((value_len - offset) < data->pub.metadata_len) {
break;
}
data->pub.metadata = &field->value[offset];
event->pub_announcement_data = &data->pub;
break;
default:
break;
}
break;
case BLE_HS_ADV_TYPE_BROADCAST_NAME:
if (event->name != NULL) {
/* Ignore */
break;
}
if (value_len < 4 || value_len > 32) {
/* Stop parsing */
data->success = false;
return 0;
}
data->name.name = (char *)field->value;
data->name.name_len = value_len;
event->name = &data->name;
break;
default:
break;
}
/* Continue parsing */
return BLE_HS_ENOENT;
}
static int
ble_audio_gap_event(struct ble_gap_event *gap_event, void *arg)
{
switch (gap_event->type) {
case BLE_GAP_EVENT_EXT_DISC: {
struct ble_audio_adv_parse_bcst_announcement_data data = { 0 };
int rc;
rc = ble_hs_adv_parse(gap_event->ext_disc.data,
gap_event->ext_disc.length_data,
ble_audio_adv_parse_bcst_announcement, &data);
if (rc == 0 && data.success) {
data.event.type = BLE_AUDIO_EVENT_BCST_ANNOUNCEMENT;
data.event.bcst_announcement.ext_disc = &gap_event->ext_disc;
(void)ble_audio_event_listener_call(&data.event);
}
break;
}
default:
break;
}
return 0;
}
int
ble_audio_event_listener_register(struct ble_audio_event_listener *listener,
ble_audio_event_fn *fn, void *arg)
{
struct ble_audio_event_listener *evl = NULL;
int rc;
if (listener == NULL) {
BLE_HS_LOG_ERROR("NULL listener!\n");
return BLE_HS_EINVAL;
}
if (fn == NULL) {
BLE_HS_LOG_ERROR("NULL fn!\n");
return BLE_HS_EINVAL;
}
SLIST_FOREACH(evl, &ble_audio_event_listener_list, next) {
if (evl == listener) {
break;
}
}
if (evl) {
return BLE_HS_EALREADY;
}
if (SLIST_EMPTY(&ble_audio_event_listener_list)) {
rc = ble_gap_event_listener_register(
&ble_audio_gap_event_listener,
ble_audio_gap_event, NULL);
if (rc != 0) {
return rc;
}
}
memset(listener, 0, sizeof(*listener));
listener->fn = fn;
listener->arg = arg;
SLIST_INSERT_HEAD(&ble_audio_event_listener_list, listener, next);
return 0;
}
int
ble_audio_event_listener_unregister(struct ble_audio_event_listener *listener)
{
struct ble_audio_event_listener *evl = NULL;
int rc;
if (listener == NULL) {
BLE_HS_LOG_ERROR("NULL listener!\n");
return BLE_HS_EINVAL;
}
/* We check if element exists on the list only for sanity to let caller
* know whether it registered its listener before.
*/
SLIST_FOREACH(evl, &ble_audio_event_listener_list, next) {
if (evl == listener) {
break;
}
}
if (!evl) {
return BLE_HS_ENOENT;
}
SLIST_REMOVE(&ble_audio_event_listener_list, listener,
ble_audio_event_listener, next);
if (SLIST_EMPTY(&ble_audio_event_listener_list)) {
rc = ble_gap_event_listener_unregister(
&ble_audio_gap_event_listener);
if (rc != 0) {
return rc;
}
}
return 0;
}
int
ble_audio_event_listener_call(struct ble_audio_event *event)
{
struct ble_audio_event_listener *evl = NULL;
SLIST_FOREACH(evl, &ble_audio_event_listener_list, next) {
evl->fn(event, evl->arg);
}
return 0;
}
/* Get the next subgroup data pointer */
static const uint8_t *
ble_audio_base_subgroup_next(uint8_t num_bis, const uint8_t *data,
uint8_t data_len)
{
uint8_t offset = 0;
for (uint8_t i = 0; i < num_bis; i++) {
uint8_t codec_specific_config_len;
/* BIS_index[i[k]] + Codec_Specific_Configuration_Length[i[k]] */
if ((data_len - offset) < 2) {
return NULL;
}
/* Skip BIS_index[i[k]] */
offset++;
codec_specific_config_len = data[offset];
offset++;
if ((data_len - offset) < codec_specific_config_len) {
return NULL;
}
offset += codec_specific_config_len;
}
return &data[offset];
}
int
ble_audio_base_parse(const uint8_t *data, uint8_t data_len,
struct ble_audio_base_group *group,
struct ble_audio_base_iter *subgroup_iter)
{
uint8_t offset = 0;
if (data == NULL) {
BLE_HS_LOG_ERROR("NULL data!\n");
return BLE_HS_EINVAL;
}
if (group == NULL) {
BLE_HS_LOG_ERROR("NULL group!\n");
return BLE_HS_EINVAL;
}
/* Presentation_Delay + Num_Subgroups */
if (data_len < 4) {
return BLE_HS_EMSGSIZE;
}
group->presentation_delay = get_le24(data);
offset += 3;
group->num_subgroups = data[offset];
offset++;
if (group->num_subgroups < 1) {
BLE_HS_LOG_ERROR("Invalid BASE: no subgroups!\n");
return BLE_HS_EINVAL;
}
if (subgroup_iter != NULL) {
subgroup_iter->data = &data[offset];
subgroup_iter->buf_len = data_len;
subgroup_iter->buf = data;
subgroup_iter->num_elements = group->num_subgroups;
}
return 0;
}
int
ble_audio_base_subgroup_iter(struct ble_audio_base_iter *subgroup_iter,
struct ble_audio_base_subgroup *subgroup,
struct ble_audio_base_iter *bis_iter)
{
const uint8_t *data;
uint8_t data_len;
ptrdiff_t offset;
uint8_t num_subgroups;
if (subgroup_iter == NULL) {
BLE_HS_LOG_ERROR("NULL subgroup_iter!\n");
return BLE_HS_EINVAL;
}
if (subgroup == NULL) {
BLE_HS_LOG_ERROR("NULL subgroup!\n");
return BLE_HS_EINVAL;
}
data = subgroup_iter->data;
if (data == NULL) {
return BLE_HS_ENOENT;
}
offset = data - subgroup_iter->buf;
if (offset < 0 || offset > subgroup_iter->buf_len) {
return BLE_HS_EINVAL;
}
num_subgroups = subgroup_iter->num_elements;
if (num_subgroups == 0) {
/* All subgroups have been parsed */
return BLE_HS_ENOENT;
}
data_len = subgroup_iter->buf_len - offset;
/* Reset the offset */
offset = 0;
memset(subgroup, 0, sizeof(*subgroup));
/* Num_BIS + Codec_ID + Codec_Specific_Configuration_Length[i] */
if (data_len < 7) {
return BLE_HS_EMSGSIZE;
}
subgroup->num_bis = data[offset];
offset++;
if (subgroup->num_bis < 1) {
BLE_HS_LOG_ERROR("Invalid BASE: no BISes!\n");
return BLE_HS_EINVAL;
}
subgroup->codec_id.format = data[offset];
offset++;
subgroup->codec_id.company_id = get_le16(&data[offset]);
offset += 2;
subgroup->codec_id.vendor_specific = get_le16(&data[offset]);
offset += 2;
subgroup->codec_spec_config_len = data[offset];
offset++;
if (subgroup->codec_spec_config_len < 1) {
BLE_HS_LOG_DEBUG("Rule 4: Codec_Specific_Configuration parameters shall"
"be present at Level 2\n");
}
if ((data_len - offset) < subgroup->codec_spec_config_len) {
return BLE_HS_EMSGSIZE;
}
subgroup->codec_spec_config = &data[offset];
offset += subgroup->codec_spec_config_len;
/* Metadata_Length[i] */
if ((data_len - offset) < 1) {
return BLE_HS_EMSGSIZE;
}
subgroup->metadata_len = data[offset];
offset++;
if (subgroup->metadata_len > 0) {
if ((data_len - offset) < subgroup->metadata_len) {
return BLE_HS_EMSGSIZE;
}
subgroup->metadata = &data[offset];
offset += subgroup->metadata_len;
} else {
subgroup->metadata = NULL;
}
if (bis_iter != 0) {
bis_iter->data = &data[offset];
bis_iter->buf_len = subgroup_iter->buf_len;
bis_iter->buf = subgroup_iter->buf;
bis_iter->num_elements = subgroup->num_bis;
}
num_subgroups--;
/* Update iterator */
subgroup_iter->num_elements = num_subgroups;
if (num_subgroups > 0) {
subgroup_iter->data = ble_audio_base_subgroup_next(subgroup->num_bis,
&data[offset],
data_len - offset);
} else {
subgroup_iter->data = NULL;
}
return 0;
}
int
ble_audio_base_bis_iter(struct ble_audio_base_iter *bis_iter,
struct ble_audio_base_bis *bis)
{
const uint8_t *data;
uint8_t data_len;
ptrdiff_t offset;
uint8_t num_bis;
if (bis_iter == NULL) {
BLE_HS_LOG_ERROR("NULL bis_iter!\n");
return BLE_HS_EINVAL;
}
if (bis == NULL) {
BLE_HS_LOG_ERROR("NULL bis!\n");
return BLE_HS_EINVAL;
}
data = bis_iter->data;
if (data == NULL) {
return BLE_HS_ENOENT;
}
offset = data - bis_iter->buf;
if (offset < 0 || offset > bis_iter->buf_len) {
return BLE_HS_EINVAL;
}
num_bis = bis_iter->num_elements;
if (num_bis == 0) {
/* All BISes have been parsed */
return BLE_HS_ENOENT;
}
data_len = bis_iter->buf_len - offset;
/* Reset the offset */
offset = 0;
memset(bis, 0, sizeof(*bis));
/* BIS_index[i[k]] + Codec_Specific_Configuration_Length[i[k]] */
if (data_len < 2) {
return BLE_HS_EMSGSIZE;
}
bis->index = data[0];
offset++;
bis->codec_spec_config_len = data[offset];
offset++;
if (bis->codec_spec_config_len > 0) {
if ((data_len - offset) < bis->codec_spec_config_len) {
return BLE_HS_EMSGSIZE;
}
bis->codec_spec_config = &data[offset];
offset += bis->codec_spec_config_len;
} else {
bis->codec_spec_config = NULL;
}
num_bis--;
/* Update iterator */
bis_iter->num_elements = num_bis;
if (num_bis > 0) {
bis_iter->data = &data[offset];
} else {
bis_iter->data = NULL;
}
return 0;
}