From a9795706bea035d84d1577680e0814ceedc9dc58 Mon Sep 17 00:00:00 2001 From: Mariusz Skamra Date: Thu, 25 Jan 2024 13:30:12 +0100 Subject: [PATCH 1/2] nimble/audio: Add BASE parser This adds BASE parser implementation along with unit tests. --- .../host/audio/include/host/audio/ble_audio.h | 155 ++++++++ nimble/host/audio/pkg.yml | 32 ++ nimble/host/audio/src/ble_audio.c | 298 +++++++++++++++ nimble/host/audio/syscfg.yml | 21 + nimble/host/audio/test/pkg.yml | 38 ++ nimble/host/audio/test/src/ble_audio_test.c | 38 ++ .../src/testcases/ble_audio_base_parse_test.c | 361 ++++++++++++++++++ nimble/host/audio/test/syscfg.yml | 29 ++ 8 files changed, 972 insertions(+) create mode 100644 nimble/host/audio/include/host/audio/ble_audio.h create mode 100644 nimble/host/audio/pkg.yml create mode 100644 nimble/host/audio/src/ble_audio.c create mode 100644 nimble/host/audio/syscfg.yml create mode 100644 nimble/host/audio/test/pkg.yml create mode 100644 nimble/host/audio/test/src/ble_audio_test.c create mode 100644 nimble/host/audio/test/src/testcases/ble_audio_base_parse_test.c create mode 100644 nimble/host/audio/test/syscfg.yml diff --git a/nimble/host/audio/include/host/audio/ble_audio.h b/nimble/host/audio/include/host/audio/ble_audio.h new file mode 100644 index 0000000000..52d1e8a05c --- /dev/null +++ b/nimble/host/audio/include/host/audio/ble_audio.h @@ -0,0 +1,155 @@ +/* + * 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. + */ + +#ifndef H_BLE_AUDIO_ +#define H_BLE_AUDIO_ + +#include + +#include "host/ble_audio_common.h" + +/** + * BASE iterator + * + * The iterator structure used by @ref ble_audio_base_subgroup_iter and + * @ble_audio_base_bis_iter functions to iterate the BASE Level 2 and 3 elements + * (Subgroups and BISes). + * This should be used as an opaque structure and not modified manually. + * + * Example: + * @code{.c} + * struct ble_audio_base_iter subgroup_iter; + * struct ble_audio_base_iter bis_iter; + * struct ble_audio_base_group group; + * struct ble_audio_base_subgroup subgroup; + * struct ble_audio_base_bis bis; + * + * rc = ble_audio_base_parse(data, data_size, &group, &subgroup_iter); + * if (rc == 0) { + * for (uint8_t i = 0; i < group->num_subgroups; i++) { + * rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + * if (rc == 0) { + * for (uint8_t j = 0; j < subgroup->num_bis; j++) { + * rc = ble_audio_base_bis_iter(&bis_iter, &bis); + * if (rc == 0) { + * foo(&group, &subgroup, &bis); + * } + * } + * } + * } + * } + * @endcode + */ +struct ble_audio_base_iter { + /** Data pointer */ + const uint8_t *data; + + /** Base length */ + uint8_t buf_len; + + /** Original BASE pointer */ + const uint8_t *buf; + + /** Remaining number of elements */ + uint8_t num_elements; +}; + +/** @brief Broadcast Audio Source Endpoint Group structure */ +struct ble_audio_base_group { + /** Presentation Delay */ + uint32_t presentation_delay; + + /** Number of subgroups */ + uint8_t num_subgroups; +}; + +/** + * Parse the BASE received from Basic Audio Announcement data. + * + * @param[in] data Pointer to the BASE data buffer to parse. + * @param[in] data_len Length of the BASE data buffer. + * @param[out] group Group object. + * @param[out] subgroup_iter Subgroup iterator object. + * + * @return 0 on success; nonzero on failure. + */ +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); + +/** @brief Broadcast Audio Source Endpoint Subgroup structure */ +struct ble_audio_base_subgroup { + /** Codec information for the subgroup */ + struct ble_audio_codec_id codec_id; + + /** Length of the Codec Specific Configuration for the subgroup */ + uint8_t codec_spec_config_len; + + /** Codec Specific Configuration for the subgroup */ + const uint8_t *codec_spec_config; + + /** Length of the Metadata for the subgroup */ + uint8_t metadata_len; + + /** Series of LTV structures containing Metadata */ + const uint8_t *metadata; + + /** Number of BISes in the subgroup */ + uint8_t num_bis; +}; + +/** + * @brief Basic Audio Announcement Subgroup information + * + * @param[in] subgroup_iter Subgroup iterator object. + * @param[out] subgroup Subgroup object. + * @param[out] bis_iter BIS iterator object. + * + * @return 0 on success; + * A non-zero value on failure. + */ +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); + +/** @brief Broadcast Audio Source Endpoint BIS structure */ +struct ble_audio_base_bis { + /** BIS_index value for the BIS */ + uint8_t index; + + /** Length of the Codec Specific Configuration for the BIS */ + uint8_t codec_spec_config_len; + + /** Codec Specific Configuration for the BIS */ + const uint8_t *codec_spec_config; +}; + +/** + * @brief Basic Audio Announcement Subgroup information + * + * @param[in] bis_iter BIS iterator object. + * @param[out] bis BIS object. + * + * @return 0 on success; + * A non-zero value on failure. + */ +int ble_audio_base_bis_iter(struct ble_audio_base_iter *bis_iter, + struct ble_audio_base_bis *bis); + +#endif /* H_BLE_AUDIO_ */ diff --git a/nimble/host/audio/pkg.yml b/nimble/host/audio/pkg.yml new file mode 100644 index 0000000000..66d418d311 --- /dev/null +++ b/nimble/host/audio/pkg.yml @@ -0,0 +1,32 @@ +# +# 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. +# + +pkg.name: nimble/host/audio +pkg.description: Bluetooth LE Audio +pkg.author: "Apache Mynewt " +pkg.homepage: "http://mynewt.apache.org/" +pkg.experimental: 1 +pkg.keywords: + - ble + - bluetooth + - audio + +pkg.deps: + - nimble + - nimble/host diff --git a/nimble/host/audio/src/ble_audio.c b/nimble/host/audio/src/ble_audio.c new file mode 100644 index 0000000000..2ac5c68e34 --- /dev/null +++ b/nimble/host/audio/src/ble_audio.c @@ -0,0 +1,298 @@ +/* + * 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 +#include + +#include "host/ble_hs.h" +#include "host/audio/ble_audio.h" + +/* 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; +} diff --git a/nimble/host/audio/syscfg.yml b/nimble/host/audio/syscfg.yml new file mode 100644 index 0000000000..d7af13d1a0 --- /dev/null +++ b/nimble/host/audio/syscfg.yml @@ -0,0 +1,21 @@ +# 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. +# + +syscfg.defs: + +syscfg.logs: diff --git a/nimble/host/audio/test/pkg.yml b/nimble/host/audio/test/pkg.yml new file mode 100644 index 0000000000..5020e9fa45 --- /dev/null +++ b/nimble/host/audio/test/pkg.yml @@ -0,0 +1,38 @@ +# +# 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. +# + +pkg.name: nimble/host/audio/test +pkg.type: unittest +pkg.description: "BLE Audio unit tests." +pkg.author: "Apache Mynewt " +pkg.homepage: "http://mynewt.apache.org/" +pkg.keywords: + +pkg.deps: + - "@apache-mynewt-core/test/testutil" + - nimble/host/audio + +pkg.deps.SELFTEST: + - "@apache-mynewt-core/sys/console/stub" + - "@apache-mynewt-core/sys/log/full" + - "@apache-mynewt-core/sys/stats/stub" + - nimble/drivers/native + +pkg.apis: + - ble_driver diff --git a/nimble/host/audio/test/src/ble_audio_test.c b/nimble/host/audio/test/src/ble_audio_test.c new file mode 100644 index 0000000000..14fcf15f5f --- /dev/null +++ b/nimble/host/audio/test/src/ble_audio_test.c @@ -0,0 +1,38 @@ +/* + * 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 "sysinit/sysinit.h" +#include "testutil/testutil.h" + +TEST_SUITE_DECL(ble_audio_base_parse_test_suite); + +TEST_SUITE(ble_audio_test) +{ + ble_audio_base_parse_test_suite(); +} + +int +main(int argc, char **argv) +{ + sysinit(); + + ble_audio_test(); + + return tu_any_failed; +} diff --git a/nimble/host/audio/test/src/testcases/ble_audio_base_parse_test.c b/nimble/host/audio/test/src/testcases/ble_audio_base_parse_test.c new file mode 100644 index 0000000000..2398d62dd2 --- /dev/null +++ b/nimble/host/audio/test/src/testcases/ble_audio_base_parse_test.c @@ -0,0 +1,361 @@ +/* + * 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 "testutil/testutil.h" + +#include "host/ble_hs.h" +#include "host/audio/ble_audio.h" + +/** + * BAP_v1.0.1 Table 3.16 + * BASE structure for the logical BASE structure example + */ +static const uint8_t example_base[] = { + 0x1e, 0x00, 0x00, /* Presentation_Delay: 40 ms */ + 0x02, /* Num_Subgroups: 2 Subgroups */ + 0x02, /* Num_BIS[0]: 2 BIS in Subgroup[0] */ + 0x06, 0x00, 0x00, 0x00, 0x00, /* Codec_ID[0]: LC3 */ + 0x0a, /* Codec_Specific_Configuration_Length[0] */ + 0x02, 0x01, 0x08, /* LTV 1: Sampling_Frequency: 48000 Hz */ + 0x02, 0x02, 0x02, /* LTV 2: Frame_Duration: 10 ms */ + 0x03, 0x04, 0x64, 0x00, /* LTV 3: Octets_Per_Codec_Frame: 100 octets */ + 0x09, /* Metadata_Length[0] */ + 0x03, 0x02, 0x04, 0x00, /* LTV 1: Streaming_Audio_Contexts: Media */ + 0x04, 0x04, 0x73, 0x70, 0x61, /* LTV 2: Language: Spanish */ + 0x01, /* BIS_index[0[0]] */ + 0x06, /* Codec_Specific_Configuration_Length[0[0]] */ + 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, /* LTV 1 = Audio_Channel_Allocation: FL */ + 0x02, /* BIS_index[0[1]] */ + 0x06, /* Codec_Specific_Configuration_Length[0[1]] */ + 0x05, 0x03, 0x02, 0x00, 0x00, 0x00, /* LTV 1 = Audio_Channel_Allocation: FR */ + 0x02, /* Num_BIS[1]: 2 BIS in Subgroup[0] */ + 0x06, 0x00, 0x00, 0x00, 0x00, /* Codec_ID[1]: LC3 */ + 0x0a, /* Codec_Specific_Configuration_Length[1] */ + 0x02, 0x01, 0x08, /* LTV 1: Sampling_Frequency: 48000 Hz */ + 0x02, 0x02, 0x02, /* LTV 2: Frame_Duration: 10 ms */ + 0x03, 0x04, 0x64, 0x00, /* LTV 3: Octets_Per_Codec_Frame: 100 octets */ + 0x09, /* Metadata_Length[1] */ + 0x03, 0x02, 0x04, 0x00, /* LTV 1: Streaming_Audio_Contexts: Media */ + 0x04, 0x04, 0x65, 0x6e, 0x67, /* LTV 2: Language: English */ + 0x03, /* BIS_index[1[0]] */ + 0x06, /* Codec_Specific_Configuration_Length[1[0]] */ + 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, /* LTV 1 = Audio_Channel_Allocation: FL */ + 0x04, /* BIS_index[1[1]] */ + 0x06, /* Codec_Specific_Configuration_Length[1[1]] */ + 0x05, 0x03, 0x02, 0x00, 0x00, 0x00, /* LTV 1 = Audio_Channel_Allocation: FR */ +}; + +TEST_CASE_SELF(ble_audio_base_parse_test) +{ + struct ble_audio_base_subgroup subgroup; + struct ble_audio_base_group group; + struct ble_audio_base_bis bis; + struct ble_audio_base_iter subgroup_iter; + struct ble_audio_base_iter bis_iter; + int rc; + + rc = ble_audio_base_parse(example_base, (uint8_t)sizeof(example_base), + &group, &subgroup_iter); + TEST_ASSERT(rc == 0); + + TEST_ASSERT(group.presentation_delay == 30); + TEST_ASSERT(group.num_subgroups == 2); + + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == 0); + + TEST_ASSERT(subgroup.codec_id.format == 0x06); + TEST_ASSERT(subgroup.codec_id.company_id == 0x0000); + TEST_ASSERT(subgroup.codec_id.vendor_specific == 0x0000); + TEST_ASSERT(subgroup.codec_spec_config_len == 10); + TEST_ASSERT(subgroup.codec_spec_config != NULL); + TEST_ASSERT(subgroup.metadata_len == 9); + TEST_ASSERT(subgroup.num_bis == 2); + + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == 0); + + TEST_ASSERT(bis.index == 0x01); + TEST_ASSERT(bis.codec_spec_config_len == 6); + TEST_ASSERT(bis.codec_spec_config != NULL); + + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == 0); + + TEST_ASSERT(bis.index == 0x02); + TEST_ASSERT(bis.codec_spec_config_len == 6); + TEST_ASSERT(bis.codec_spec_config != NULL); + + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == BLE_HS_ENOENT); + + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == 0); + + TEST_ASSERT(subgroup.codec_id.format == 0x06); + TEST_ASSERT(subgroup.codec_id.company_id == 0x0000); + TEST_ASSERT(subgroup.codec_id.vendor_specific == 0x0000); + TEST_ASSERT(subgroup.codec_spec_config_len == 10); + TEST_ASSERT(subgroup.codec_spec_config != NULL); + TEST_ASSERT(subgroup.metadata_len == 9); + TEST_ASSERT(subgroup.num_bis == 2); + + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == 0); + + TEST_ASSERT(bis.index == 0x03); + TEST_ASSERT(bis.codec_spec_config_len == 6); + TEST_ASSERT(bis.codec_spec_config != NULL); + + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == 0); + + TEST_ASSERT(bis.index == 0x04); + TEST_ASSERT(bis.codec_spec_config_len == 6); + TEST_ASSERT(bis.codec_spec_config != NULL); + + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == BLE_HS_ENOENT); + + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == BLE_HS_ENOENT); +} + +TEST_CASE_SELF(ble_audio_base_parse_test_params) +{ + struct ble_audio_base_subgroup subgroup; + struct ble_audio_base_group group; + struct ble_audio_base_bis bis; + struct ble_audio_base_iter subgroup_iter; + struct ble_audio_base_iter bis_iter; + int rc; + + rc = ble_audio_base_parse(NULL, (uint8_t)sizeof(example_base), &group, &subgroup_iter); + TEST_ASSERT(rc == BLE_HS_EINVAL); + + rc = ble_audio_base_parse(NULL, (uint8_t)sizeof(example_base), NULL, &subgroup_iter); + TEST_ASSERT(rc == BLE_HS_EINVAL); + + rc = ble_audio_base_parse(example_base, (uint8_t)sizeof(example_base), &group, NULL); + TEST_ASSERT(rc == 0); + + rc = ble_audio_base_parse(example_base, (uint8_t)sizeof(example_base), &group, &subgroup_iter); + TEST_ASSERT(rc == 0); + + rc = ble_audio_base_subgroup_iter(NULL, &subgroup, &bis_iter); + TEST_ASSERT(rc == BLE_HS_EINVAL); + + rc = ble_audio_base_subgroup_iter(&subgroup_iter, NULL, &bis_iter); + TEST_ASSERT(rc == BLE_HS_EINVAL); + + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, NULL); + TEST_ASSERT(rc == 0); + + rc = ble_audio_base_bis_iter(NULL, &bis); + TEST_ASSERT(rc == BLE_HS_EINVAL); + + rc = ble_audio_base_bis_iter(&bis_iter, NULL); + TEST_ASSERT(rc == BLE_HS_EINVAL); +} + +TEST_CASE_SELF(ble_audio_base_parse_test_data_length) +{ + struct ble_audio_base_subgroup subgroup; + struct ble_audio_base_group group; + struct ble_audio_base_bis bis; + struct ble_audio_base_iter subgroup_iter; + struct ble_audio_base_iter bis_iter; + int rc; + + /* Incomplete: empty */ + rc = ble_audio_base_parse(example_base, 0, &group, &subgroup_iter); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Truncated: Presentation_Delay Parameter */ + rc = ble_audio_base_parse(example_base, 2, &group, &subgroup_iter); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Incomplete: no Num_Subgroups[0] Parameter */ + rc = ble_audio_base_parse(example_base, 3, &group, &subgroup_iter); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Incomplete: no Num_BIS[0] Parameter */ + rc = ble_audio_base_parse(example_base, 4, &group, &subgroup_iter); + TEST_ASSERT(rc == 0); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Truncated: Codec_ID[0] Parameter */ + rc = ble_audio_base_parse(example_base, 9, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Incomplete: no Codec_Specific_Configuration_Length[0] Parameter */ + rc = ble_audio_base_parse(example_base, 13, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Truncated: Codec_Specific_Configuration[0] Parameter */ + rc = ble_audio_base_parse(example_base, 14, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Incomplete: no Metadata_Length[0] Parameter */ + rc = ble_audio_base_parse(example_base, 21, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Truncated: Metadata[0] Parameter */ + rc = ble_audio_base_parse(example_base, 30, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Incomplete: no BIS_index[0[0]] Parameter */ + rc = ble_audio_base_parse(example_base, 31, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == 0); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Incomplete: no Codec_Specific_Configuration_Length[0[0]] Parameter */ + rc = ble_audio_base_parse(example_base, 32, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Truncated: Codec_Specific_Configuration_Length[0[0]] Parameter */ + rc = ble_audio_base_parse(example_base, 38, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Incomplete: no BIS_index[0[1]] Parameter */ + rc = ble_audio_base_parse(example_base, 39, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == 0); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Incomplete: no Codec_Specific_Configuration_Length[0[1]] Parameter */ + rc = ble_audio_base_parse(example_base, 40, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Truncated: Codec_Specific_Configuration_Length[0[1]] Parameter */ + rc = ble_audio_base_parse(example_base, 46, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == 0); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Incomplete: no Num_BIS[1] Parameter */ + rc = ble_audio_base_parse(example_base, 47, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == 0); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Truncated: Codec_ID[1] Parameter */ + rc = ble_audio_base_parse(example_base, 52, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Incomplete: no Codec_Specific_Configuration_Length[1] Parameter */ + rc = ble_audio_base_parse(example_base, 53, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Truncated: Codec_Specific_Configuration[1] Parameter */ + rc = ble_audio_base_parse(example_base, 63, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Incomplete: no Metadata_Length[1] Parameter */ + rc = ble_audio_base_parse(example_base, 64, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Truncated: Metadata[1] Parameter */ + rc = ble_audio_base_parse(example_base, 73, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Incomplete: no BIS_index[1[0]] Parameter */ + rc = ble_audio_base_parse(example_base, 74, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + TEST_ASSERT(rc == 0); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Incomplete: no Codec_Specific_Configuration_Length[1[0]] Parameter */ + rc = ble_audio_base_parse(example_base, 75, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Truncated: Codec_Specific_Configuration_Length[1[0]] Parameter */ + rc = ble_audio_base_parse(example_base, 81, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Incomplete: no BIS_index[1[1]] Parameter */ + rc = ble_audio_base_parse(example_base, 82, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == 0); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Incomplete: no Codec_Specific_Configuration_Length[1[1]] Parameter */ + rc = ble_audio_base_parse(example_base, 83, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); + + /* Truncated: Codec_Specific_Configuration_Length[0[1]] Parameter */ + rc = ble_audio_base_parse(example_base, 89, &group, &subgroup_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_subgroup_iter(&subgroup_iter, &subgroup, &bis_iter); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + rc = ble_audio_base_bis_iter(&bis_iter, &bis); + TEST_ASSERT(rc == BLE_HS_EMSGSIZE); +} + +TEST_SUITE(ble_audio_base_parse_test_suite) +{ + ble_audio_base_parse_test(); + ble_audio_base_parse_test_params(); + ble_audio_base_parse_test_data_length(); +} diff --git a/nimble/host/audio/test/syscfg.yml b/nimble/host/audio/test/syscfg.yml new file mode 100644 index 0000000000..3c760594c5 --- /dev/null +++ b/nimble/host/audio/test/syscfg.yml @@ -0,0 +1,29 @@ +# +# 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. +# + +syscfg.defs: + +syscfg.vals: + # Prevent priority conflict with controller task. + MCU_TIMER_POLLER_PRIO: 1 + MCU_UART_POLLER_PRIO: 2 + NATIVE_SOCKETS_PRIO: 3 + + BLE_VERSION: 54 + BLE_HS_DEBUG: 1 From 3bf38350da1f13f82eaa20879f76d30850524e06 Mon Sep 17 00:00:00 2001 From: Mariusz Skamra Date: Wed, 21 Feb 2024 12:56:26 +0100 Subject: [PATCH 2/2] nimble/audio: Add LE Audio event listener This adds dedicated event listener for LE Audio events. --- .../host/audio/include/host/audio/ble_audio.h | 138 +++++++++++ nimble/host/audio/src/ble_audio.c | 232 ++++++++++++++++++ nimble/host/audio/src/ble_audio_priv.h | 27 ++ nimble/host/audio/test/src/ble_audio_test.c | 2 + .../ble_audio_listener_register_test.c | 59 +++++ nimble/host/audio/test/syscfg.yml | 2 + nimble/host/include/host/ble_audio_common.h | 4 +- .../services/auracast/src/ble_svc_auracast.c | 2 +- 8 files changed, 464 insertions(+), 2 deletions(-) create mode 100644 nimble/host/audio/src/ble_audio_priv.h create mode 100644 nimble/host/audio/test/src/testcases/ble_audio_listener_register_test.c diff --git a/nimble/host/audio/include/host/audio/ble_audio.h b/nimble/host/audio/include/host/audio/ble_audio.h index 52d1e8a05c..7d4200bb03 100644 --- a/nimble/host/audio/include/host/audio/ble_audio.h +++ b/nimble/host/audio/include/host/audio/ble_audio.h @@ -21,9 +21,147 @@ #define H_BLE_AUDIO_ #include +#include #include "host/ble_audio_common.h" +/** @brief Public Broadcast Announcement features bits */ +enum ble_audio_pub_bcst_announcement_feat { + /** Broadcast Stream Encryption */ + BLE_AUDIO_PUB_BCST_ANNOUNCEMENT_FEAT_ENCRYPTION = 1 << 0, + + /** Standard Quality Public Broadcast Audio */ + BLE_AUDIO_PUB_BCST_ANNOUNCEMENT_FEAT_SQ = 1 << 1, + + /** High Quality Public Broadcast Audio */ + BLE_AUDIO_PUB_BCST_ANNOUNCEMENT_FEAT_HQ = 1 << 2, +}; + +/** @brief Public Broadcast Announcement structure */ +struct ble_audio_pub_bcst_announcement { + /** Public Broadcast Announcement features bitfield */ + enum ble_audio_pub_bcst_announcement_feat features; + + /** Metadata length */ + uint8_t metadata_len; + + /** Metadata */ + const uint8_t *metadata; +}; + +struct ble_audio_bcst_name { + /** Broadcast Name length */ + uint8_t name_len; + + /** Broadcast Name */ + const char *name; +}; + +/** + * @defgroup ble_audio_events Bluetooth Low Energy Audio Events + * @{ + */ + +/** BLE Audio event: Broadcast Announcement */ +#define BLE_AUDIO_EVENT_BCST_ANNOUNCEMENT 0 + +/** @} */ + +/** @brief Broadcast Announcement */ +struct ble_audio_event_bcst_announcement { + /** Extended advertising report */ + const struct ble_gap_ext_disc_desc *ext_disc; + + /** Broadcast ID */ + uint32_t broadcast_id; + + /** Additional service data included in Broadcast Audio Announcement */ + const uint8_t *svc_data; + + /** Additional service data length */ + uint16_t svc_data_len; + + /** Optional Public Broadcast Announcement data */ + struct ble_audio_pub_bcst_announcement *pub_announcement_data; + + /** Optional Broadcast Name */ + struct ble_audio_bcst_name *name; +}; + +/** + * Represents a BLE Audio related event. When such an event occurs, the host + * notifies the application by passing an instance of this structure to an + * application-specified callback. + */ +struct ble_audio_event { + /** + * Indicates the type of BLE Audio event that occurred. This is one of the + * BLE_AUDIO_EVENT codes. + */ + uint8_t type; + + /** + * A discriminated union containing additional details concerning the event. + * The 'type' field indicates which member of the union is valid. + */ + union { + /** + * @ref BLE_AUDIO_EVENT_BCST_ANNOUNCEMENT + * + * Represents a received Broadcast Announcement. + */ + struct ble_audio_event_bcst_announcement bcst_announcement; + }; +}; + +/** Callback function type for handling BLE Audio events. */ +typedef int ble_audio_event_fn(struct ble_audio_event *event, void *arg); + +/** + * Event listener structure + * + * This should be used as an opaque structure and not modified manually. + */ +struct ble_audio_event_listener { + /** The function to call when a BLE Audio event occurs. */ + ble_audio_event_fn *fn; + + /** An optional argument to pass to the event handler function. */ + void *arg; + + /** Singly-linked list entry. */ + SLIST_ENTRY(ble_audio_event_listener) next; +}; + +/** + * Registers listener for BLE Audio events + * + * On success listener structure will be initialized automatically and does not + * need to be initialized prior to calling this function. To change callback + * and/or argument unregister listener first and register it again. + * + * @param[in] listener Listener structure + * @param[in] event_mask Optional event mask + * @param[in] fn Callback function + * @param[in] arg Optional callback argument + * + * @return 0 on success + * BLE_HS_EINVAL if no callback is specified + * BLE_HS_EALREADY if listener is already registered + */ +int ble_audio_event_listener_register(struct ble_audio_event_listener *listener, + ble_audio_event_fn *fn, void *arg); + +/** + * Unregisters listener for BLE Audio events + * + * @param[in] listener Listener structure + * + * @return 0 on success + * BLE_HS_ENOENT if listener was not registered + */ +int ble_audio_event_listener_unregister(struct ble_audio_event_listener *listener); + /** * BASE iterator * diff --git a/nimble/host/audio/src/ble_audio.c b/nimble/host/audio/src/ble_audio.c index 2ac5c68e34..61c42e4ff8 100644 --- a/nimble/host/audio/src/ble_audio.c +++ b/nimble/host/audio/src/ble_audio.c @@ -23,6 +23,238 @@ #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, diff --git a/nimble/host/audio/src/ble_audio_priv.h b/nimble/host/audio/src/ble_audio_priv.h new file mode 100644 index 0000000000..bedbdaf058 --- /dev/null +++ b/nimble/host/audio/src/ble_audio_priv.h @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#ifndef H_BLE_AUDIO_PRIV_ +#define H_BLE_AUDIO_PRIV_ + +#include "host/audio/ble_audio.h" + +int ble_audio_event_listener_call(struct ble_audio_event *event); + +#endif /* H_BLE_AUDIO_PRIV_ */ diff --git a/nimble/host/audio/test/src/ble_audio_test.c b/nimble/host/audio/test/src/ble_audio_test.c index 14fcf15f5f..91e4a91449 100644 --- a/nimble/host/audio/test/src/ble_audio_test.c +++ b/nimble/host/audio/test/src/ble_audio_test.c @@ -21,10 +21,12 @@ #include "testutil/testutil.h" TEST_SUITE_DECL(ble_audio_base_parse_test_suite); +TEST_CASE_DECL(ble_audio_listener_register_test); TEST_SUITE(ble_audio_test) { ble_audio_base_parse_test_suite(); + ble_audio_listener_register_test(); } int diff --git a/nimble/host/audio/test/src/testcases/ble_audio_listener_register_test.c b/nimble/host/audio/test/src/testcases/ble_audio_listener_register_test.c new file mode 100644 index 0000000000..f55b51a3c3 --- /dev/null +++ b/nimble/host/audio/test/src/testcases/ble_audio_listener_register_test.c @@ -0,0 +1,59 @@ +/* + * 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 "testutil/testutil.h" + +#include "host/ble_hs.h" +#include "host/audio/ble_audio.h" + +static struct ble_audio_event_listener event_listener; + +static int +event_handler(struct ble_audio_event *event, void *arg) +{ + return 0; +} + +TEST_CASE_SELF(ble_audio_listener_register_test) +{ + int rc; + + rc = ble_audio_event_listener_register(&event_listener, event_handler, + NULL); + TEST_ASSERT(rc == 0); + + rc = ble_audio_event_listener_register(&event_listener, event_handler, + NULL); + TEST_ASSERT(rc != 0); + + rc = ble_audio_event_listener_unregister(&event_listener); + TEST_ASSERT(rc == 0); + + rc = ble_audio_event_listener_register(NULL, event_handler, NULL); + TEST_ASSERT(rc != 0); + + rc = ble_audio_event_listener_register(&event_listener, NULL, NULL); + TEST_ASSERT(rc != 0); + + rc = ble_audio_event_listener_unregister(NULL); + TEST_ASSERT(rc != 0); + + rc = ble_audio_event_listener_unregister(&event_listener); + TEST_ASSERT(rc != 0); +} diff --git a/nimble/host/audio/test/syscfg.yml b/nimble/host/audio/test/syscfg.yml index 3c760594c5..7fad93f3fe 100644 --- a/nimble/host/audio/test/syscfg.yml +++ b/nimble/host/audio/test/syscfg.yml @@ -27,3 +27,5 @@ syscfg.vals: BLE_VERSION: 54 BLE_HS_DEBUG: 1 + + BLE_EXT_ADV: 1 diff --git a/nimble/host/include/host/ble_audio_common.h b/nimble/host/include/host/ble_audio_common.h index 815d3f4c0d..05780c0797 100644 --- a/nimble/host/include/host/ble_audio_common.h +++ b/nimble/host/include/host/ble_audio_common.h @@ -67,7 +67,9 @@ /** Broadcast Audio Announcement Service UUID. */ #define BLE_BROADCAST_AUDIO_ANNOUNCEMENT_SVC_UUID 0x1852 -#define BLE_BROADCAST_PUBLIC_BROADCAST_ANNOUNCEMENT_SVC_UUID 0x1856 + +/** Public Broadcast Announcement Service UUID. */ +#define BLE_BROADCAST_PUB_ANNOUNCEMENT_SVC_UUID 0x1856 /** * @defgroup ble_audio_sampling_rates Bluetooth Low Energy Audio Sampling Rates diff --git a/nimble/host/services/auracast/src/ble_svc_auracast.c b/nimble/host/services/auracast/src/ble_svc_auracast.c index b1bc7d33a6..7bd9112dfc 100644 --- a/nimble/host/services/auracast/src/ble_svc_auracast.c +++ b/nimble/host/services/auracast/src/ble_svc_auracast.c @@ -62,7 +62,7 @@ ble_svc_auracast_create(const struct ble_svc_auracast_create_params *params, auracast_svc_data[data_offset] = BLE_HS_ADV_TYPE_SVC_DATA_UUID16; data_offset++; put_le16(&auracast_svc_data[data_offset], - BLE_BROADCAST_PUBLIC_BROADCAST_ANNOUNCEMENT_SVC_UUID); + BLE_BROADCAST_PUB_ANNOUNCEMENT_SVC_UUID); data_offset += 2; auracast_svc_data[data_offset] = features; data_offset++;