From b661adb4827c7fd90d8a0131b73842765ea03a3c Mon Sep 17 00:00:00 2001 From: Matthias Ringwald Date: Tue, 20 Sep 2022 11:28:03 +0200 Subject: [PATCH] gatt-service: add broadcast_audio_scan_service_[server|client|util] --- CHANGELOG.md | 2 + src/ble/gatt-service/Makefile.inc | 3 + .../broadcast_audio_scan_service.gatt | 4 + .../broadcast_audio_scan_service_client.c | 865 ++++++++++++++++++ .../broadcast_audio_scan_service_client.h | 181 ++++ .../broadcast_audio_scan_service_server.c | 679 ++++++++++++++ .../broadcast_audio_scan_service_server.h | 130 +++ .../broadcast_audio_scan_service_util.c | 228 +++++ .../broadcast_audio_scan_service_util.h | 120 +++ src/btstack.h | 2 + 10 files changed, 2214 insertions(+) create mode 100644 src/ble/gatt-service/broadcast_audio_scan_service.gatt create mode 100644 src/ble/gatt-service/broadcast_audio_scan_service_client.c create mode 100644 src/ble/gatt-service/broadcast_audio_scan_service_client.h create mode 100644 src/ble/gatt-service/broadcast_audio_scan_service_server.c create mode 100644 src/ble/gatt-service/broadcast_audio_scan_service_server.h create mode 100644 src/ble/gatt-service/broadcast_audio_scan_service_util.c create mode 100644 src/ble/gatt-service/broadcast_audio_scan_service_util.h diff --git a/CHANGELOG.md b/CHANGELOG.md index f32ff7c1d..a94d72234 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Unreleased ### Added +- GATT Service: Broadcast Audio Scamn Service Server and Client (BASS 1.0) + ### Fixed ### Changed diff --git a/src/ble/gatt-service/Makefile.inc b/src/ble/gatt-service/Makefile.inc index 1e95c9c57..0bd9ba8fe 100644 --- a/src/ble/gatt-service/Makefile.inc +++ b/src/ble/gatt-service/Makefile.inc @@ -6,6 +6,9 @@ SRC_BLE_GATT_SERVICE_FILES = \ battery_service_client.c \ battery_service_server.c \ bond_management_service_server.c \ + broadcast_audio_scan_service_client.c \ + broadcast_audio_scan_service_util.c \ + broadcast_audio_scan_service_server.c \ cycling_power_service_server.c \ cycling_speed_and_cadence_service_server.c \ device_information_service_client.c \ diff --git a/src/ble/gatt-service/broadcast_audio_scan_service.gatt b/src/ble/gatt-service/broadcast_audio_scan_service.gatt new file mode 100644 index 000000000..b26e5513f --- /dev/null +++ b/src/ble/gatt-service/broadcast_audio_scan_service.gatt @@ -0,0 +1,4 @@ +// Broadcast Audio Scan Service 0x184F +PRIMARY_SERVICE, ORG_BLUETOOTH_SERVICE_BROADCAST_AUDIO_SCAN_SERVICE +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_AUDIO_SCAN_CONTROL_POINT , DYNAMIC | WRITE | WRITE_WITHOUT_RESPONSE | ENCRYPTION_KEY_SIZE_16, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_RECEIVE_STATE , DYNAMIC | READ | NOTIFY | ENCRYPTION_KEY_SIZE_16, \ No newline at end of file diff --git a/src/ble/gatt-service/broadcast_audio_scan_service_client.c b/src/ble/gatt-service/broadcast_audio_scan_service_client.c new file mode 100644 index 000000000..3d279bce3 --- /dev/null +++ b/src/ble/gatt-service/broadcast_audio_scan_service_client.c @@ -0,0 +1,865 @@ +/* + * Copyright (C) 2022 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN + * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +#define BTSTACK_FILE__ "broadcast_audio_scan_service_client.c" + +#include "ble/att_db.h" +#include "ble/att_server.h" +#include "bluetooth_gatt.h" +#include "btstack_debug.h" +#include "btstack_defines.h" +#include "btstack_event.h" +#include "btstack_util.h" +#include "btstack_memory.h" + +#include "ble/gatt-service/le_audio_util.h" +#include "ble/gatt-service/broadcast_audio_scan_service_client.h" + +#ifdef ENABLE_TESTING_SUPPORT +#include +#endif + +static btstack_linked_list_t bass_connections; + +static uint16_t bass_client_cid_counter = 0; +static btstack_packet_handler_t bass_event_callback; + +static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); + +static uint16_t bass_client_get_next_cid(void){ + bass_client_cid_counter = btstack_next_cid_ignoring_zero(bass_client_cid_counter); + return bass_client_cid_counter; +} + +static void bass_client_finalize_connection(bass_client_connection_t * connection){ + btstack_linked_list_remove(&bass_connections, (btstack_linked_item_t*) connection); +} + +static bass_client_connection_t * bass_client_get_connection_for_con_handle(hci_con_handle_t con_handle){ + btstack_linked_list_iterator_t it; + btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &bass_connections); + while (btstack_linked_list_iterator_has_next(&it)){ + bass_client_connection_t * connection = (bass_client_connection_t *)btstack_linked_list_iterator_next(&it); + if (connection->con_handle != con_handle) continue; + return connection; + } + return NULL; +} + +static bass_client_connection_t * bass_get_client_for_cid(uint16_t bass_cid){ + btstack_linked_list_iterator_t it; + btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &bass_connections); + while (btstack_linked_list_iterator_has_next(&it)){ + bass_client_connection_t * connection = (bass_client_connection_t *)btstack_linked_list_iterator_next(&it); + if (connection->cid != bass_cid) continue; + return connection; + } + return NULL; +} + +static bass_client_source_t * bass_get_receive_state_for_value_handle(bass_client_connection_t * connection, uint16_t value_handle){ + uint8_t i; + for (i = 0; i < connection->receive_states_instances_num; i++){ + if (connection->receive_states[i].receive_state_value_handle == value_handle){ + return &connection->receive_states[i]; + } + } + return NULL; +} + +static bass_client_source_t * bass_get_source_for_source_id(bass_client_connection_t * connection, uint8_t source_id){ + uint8_t i; + for (i = 0; i < connection->receive_states_instances_num; i++){ + if (connection->receive_states[i].source_id == source_id){ + return &connection->receive_states[i]; + } + } + return NULL; +} + +static void bass_client_reset_source(bass_client_source_t * source){ + if (source == NULL){ + return; + } + source->source_id = BASS_INVALID_SOURCE_INDEX; + source->in_use = false; + memset(&source->data, 0, sizeof(bass_source_data_t)); +} + +static void bass_client_emit_connection_established(bass_client_connection_t * connection, uint8_t status){ + uint8_t event[8]; + uint16_t pos = 0; + event[pos++] = HCI_EVENT_GATTSERVICE_META; + event[pos++] = sizeof(event) - 2; + event[pos++] = GATTSERVICE_SUBEVENT_BASS_CONNECTED; + little_endian_store_16(event, pos, connection->con_handle); + pos += 2; + little_endian_store_16(event, pos, connection->cid); + pos += 2; + event[pos++] = status; + (*bass_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); +} + +static void bass_client_emit_scan_operation_complete(bass_client_connection_t * connection, uint8_t status, bass_opcode_t opcode){ + uint8_t event[7]; + uint16_t pos = 0; + event[pos++] = HCI_EVENT_GATTSERVICE_META; + event[pos++] = sizeof(event) - 2; + event[pos++] = GATTSERVICE_SUBEVENT_BASS_SCAN_OPERATION_COMPLETE; + little_endian_store_16(event, pos, connection->cid); + pos += 2; + event[pos++] = status; + event[pos++] = (uint8_t)opcode; + (*bass_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); +} + +static void bass_client_emit_source_operation_complete(bass_client_connection_t * connection, uint8_t status, bass_opcode_t opcode, uint8_t source_id){ + uint8_t event[8]; + uint16_t pos = 0; + event[pos++] = HCI_EVENT_GATTSERVICE_META; + event[pos++] = sizeof(event) - 2; + event[pos++] = GATTSERVICE_SUBEVENT_BASS_SOURCE_OPERATION_COMPLETE; + little_endian_store_16(event, pos, connection->cid); + pos += 2; + event[pos++] = status; + event[pos++] = (uint8_t)opcode; + event[pos++] = source_id; + (*bass_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); +} + +static void bass_client_emit_receive_state(bass_client_connection_t * connection, uint8_t source_id){ + uint8_t pos = 0; + uint8_t event[7]; + event[pos++] = HCI_EVENT_GATTSERVICE_META; + event[pos++] = sizeof(event) - 2; + event[pos++] = GATTSERVICE_SUBEVENT_BASS_NOTIFICATION_COMPLETE; + little_endian_store_16(event, pos, connection->cid); + pos += 2; + event[pos++] = source_id; + (*bass_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); +} + +static bool bass_client_remote_broadcast_receive_state_buffer_valid(uint8_t *buffer, uint16_t buffer_size){ + if (buffer_size < 1){ + log_info("Add Source opcode, buffer too small"); + return false; + } + uint8_t pos = 0; + // source_id + pos++; + return bass_util_add_source_buffer_in_valid_range(buffer+pos, buffer_size-pos); +} + +static void handle_gatt_server_notification(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(packet_type); + UNUSED(channel); + UNUSED(size); + + if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION){ + return; + } + + bass_client_connection_t * connection = bass_client_get_connection_for_con_handle(channel); + if (connection == NULL){ + return; + } + + uint16_t value_handle = gatt_event_notification_get_value_handle(packet); + uint16_t value_length = gatt_event_notification_get_value_length(packet); + uint8_t * value = (uint8_t *)gatt_event_notification_get_value(packet); + + if (bass_client_remote_broadcast_receive_state_buffer_valid(value, value_length)){ + bass_client_source_t * source = bass_get_receive_state_for_value_handle(connection, value_handle); + if (source == NULL){ + return; + } + source->source_id = value[0]; + bass_util_get_source_from_buffer(value + 1, value_length - 1, &source->data); + bass_client_emit_receive_state(connection, source->source_id); + } +} + +static bool bass_client_register_notification(bass_client_connection_t * connection){ + bass_client_source_t * receive_state = &connection->receive_states[connection->receive_states_index]; + if (receive_state == NULL){ + return false; + } + + gatt_client_characteristic_t characteristic; + characteristic.value_handle = receive_state->receive_state_value_handle; + characteristic.properties = receive_state->receive_state_properties; + characteristic.end_handle = connection->end_handle; + + uint8_t status = gatt_client_write_client_characteristic_configuration( + &handle_gatt_client_event, + connection->con_handle, + &characteristic, + GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION); + + // notification supported, register for value updates + if (status == ERROR_CODE_SUCCESS){ + gatt_client_listen_for_characteristic_value_updates( + &connection->notification_listener, + &handle_gatt_server_notification, + connection->con_handle, &characteristic); + } + return status; +} + +static uint16_t bass_client_prepare_add_source_buffer(bass_client_connection_t * connection){ + bass_source_data_t * receive_state = connection->receive_state_data; + + uint16_t buffer_offset = connection->buffer_offset; + uint8_t * buffer = connection->buffer; + uint16_t buffer_size = btstack_min(sizeof(connection->buffer), connection->mtu); + + btstack_assert(buffer_offset == 0); + + uint8_t field_data[6]; + uint16_t source_offset = 0; + uint16_t stored_bytes = 0; + memset(buffer, 0, buffer_size); + + field_data[0] = (uint8_t)BASS_OPCODE_ADD_SOURCE; + stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset); + source_offset++; + + stored_bytes += bass_util_copy_source_common_data_to_buffer(receive_state, &source_offset, buffer_offset, buffer, buffer_size); + + field_data[0] = (uint8_t)receive_state->pa_sync; + stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset); + source_offset++; + + little_endian_store_16(field_data, 0, receive_state->pa_interval); + stored_bytes += le_audio_virtual_memcpy_helper(field_data, 2, source_offset, buffer, buffer_size, buffer_offset); + source_offset += 2; + + stored_bytes += bass_util_copy_source_metadata_to_buffer(receive_state, &source_offset, buffer_offset, buffer, buffer_size); + + return stored_bytes; +} + +static uint16_t bass_client_prepare_modify_source_buffer(bass_client_connection_t * connection){ + bass_source_data_t * receive_state = connection->receive_state_data; + + uint16_t buffer_offset = connection->buffer_offset; + uint8_t * buffer = connection->buffer; + uint16_t buffer_size = btstack_min(sizeof(connection->buffer), connection->mtu); + + btstack_assert(buffer_offset == 0); + + uint8_t field_data[6]; + uint16_t source_offset = 0; + uint16_t stored_bytes = 0; + memset(buffer, 0, buffer_size); + + field_data[0] = (uint8_t)BASS_OPCODE_MODIFY_SOURCE; + stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset); + source_offset++; + + field_data[0] = connection->receive_state_source_id; + stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset); + source_offset++; + + field_data[0] = (uint8_t)receive_state->pa_sync; + stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset); + source_offset++; + + little_endian_store_16(field_data, 0, receive_state->pa_interval); + stored_bytes += le_audio_virtual_memcpy_helper(field_data, 2, source_offset, buffer, buffer_size, buffer_offset); + source_offset += 2; + + stored_bytes += bass_util_copy_source_metadata_to_buffer(receive_state, &source_offset, buffer_offset, buffer, buffer_size); + return stored_bytes; +} + +static uint16_t bass_client_receive_state_len(bass_source_data_t * source_data){ + uint16_t source_len = 0; + // opcode(1), address_type(1), address(6), adv_sid(1), broadcast_id(3), pa_sync(1), subgroups_num(1) + source_len = 1 + 1 + 6 + 1 + 3 + 1 + 1; + + uint8_t i; + for (i = 0; i < source_data->subgroups_num; i++){ + bass_subgroup_t subgroup = source_data->subgroups[i]; + // bis_sync(4), metadata_length(1), metadata_length + source_len += 4 + 1 + subgroup.metadata_length; + } + return source_len; +} + +static void bass_client_run_for_connection(bass_client_connection_t * connection){ + uint8_t status; + gatt_client_characteristic_t characteristic; + gatt_client_service_t service; + + uint8_t value[18]; + uint16_t stored_bytes; + + switch (connection->state){ + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED: + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_START_SCAN: +#ifdef ENABLE_TESTING_SUPPORT + printf(" Write START SCAN [0x%04X]:\n", + connection->control_point_value_handle); +#endif + + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_START_SCAN; + value[0] = BASS_OPCODE_REMOTE_SCAN_STARTED; + // see GATT_EVENT_QUERY_COMPLETE for end of write + status = gatt_client_write_value_of_characteristic( + &handle_gatt_client_event, connection->con_handle, + connection->control_point_value_handle, 1, &value[0]); + UNUSED(status); + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_STOP_SCAN: +#ifdef ENABLE_TESTING_SUPPORT + printf(" Write START SCAN [0x%04X]:\n", + connection->control_point_value_handle); +#endif + + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_STOP_SCAN; + value[0] = BASS_OPCODE_REMOTE_SCAN_STOPPED; + // see GATT_EVENT_QUERY_COMPLETE for end of write + status = gatt_client_write_value_of_characteristic( + &handle_gatt_client_event, connection->con_handle, + connection->control_point_value_handle, 1, &value[0]); + UNUSED(status); + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_ADD_SOURCE: + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_ADD_SOURCE; +#ifdef ENABLE_TESTING_SUPPORT + printf(" ADD SOURCE [0x%04X]:\n", connection->control_point_value_handle); +#endif + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_ADD_SOURCE; + stored_bytes = bass_client_prepare_add_source_buffer(connection); + connection->buffer_offset += stored_bytes; + + gatt_client_write_long_value_of_characteristic(&handle_gatt_client_event, connection->con_handle, + connection->control_point_value_handle, stored_bytes, connection->buffer); + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_MODIFY_SOURCE: +#ifdef ENABLE_TESTING_SUPPORT + printf(" MODIFY SOURCE [%d]:\n", connection->receive_state_source_id); +#endif + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_MODIFY_SOURCE; + stored_bytes = bass_client_prepare_modify_source_buffer(connection); + connection->buffer_offset += stored_bytes; + + gatt_client_write_long_value_of_characteristic(&handle_gatt_client_event, connection->con_handle, + connection->control_point_value_handle, stored_bytes, connection->buffer); + + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_REMOVE_SOURCE: +#ifdef ENABLE_TESTING_SUPPORT + printf(" REMOVE SOURCE [%d]:\n", connection->receive_state_source_id); +#endif + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_REMOVE_SOURCE; + value[0] = BASS_OPCODE_REMOVE_SOURCE; + value[1] = connection->receive_state_source_id; + // see GATT_EVENT_QUERY_COMPLETE for end of write + status = gatt_client_write_value_of_characteristic( + &handle_gatt_client_event, connection->con_handle, + connection->control_point_value_handle, 2, &value[0]); + UNUSED(status); + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_SET_BROADCAST_CODE: +#ifdef ENABLE_TESTING_SUPPORT + printf(" SET BROADCAST CODE [%d]:\n", connection->receive_state_source_id); +#endif + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_SET_BROADCAST_CODE; + value[0] = BASS_OPCODE_SET_BROADCAST_CODE; + value[1] = connection->receive_state_source_id; + reverse_128(&value[2], connection->broadcast_code); + + // see GATT_EVENT_QUERY_COMPLETE for end of write + status = gatt_client_write_value_of_characteristic( + &handle_gatt_client_event, connection->con_handle, + connection->control_point_value_handle, 18, &value[0]); + UNUSED(status); + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_SERVICE: + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_SERVICE_RESULT; + gatt_client_discover_primary_services_by_uuid16(&handle_gatt_client_event, connection->con_handle, ORG_BLUETOOTH_SERVICE_BROADCAST_AUDIO_SCAN_SERVICE); + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTICS: + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_RESULT; + + service.start_group_handle = connection->start_handle; + service.end_group_handle = connection->end_handle; + service.uuid16 = ORG_BLUETOOTH_SERVICE_BROADCAST_AUDIO_SCAN_SERVICE; + + gatt_client_discover_characteristics_for_service( + &handle_gatt_client_event, + connection->con_handle, + &service); + + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTIC_DESCRIPTORS: +#ifdef ENABLE_TESTING_SUPPORT + printf("Read client characteristic descriptors [index %d]:\n", connection->receive_states_index); +#endif + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_DESCRIPTORS_RESULT; + characteristic.value_handle = connection->receive_states[connection->receive_states_index].receive_state_value_handle; + characteristic.properties = connection->receive_states[connection->receive_states_index].receive_state_properties; + characteristic.end_handle = connection->end_handle; + + (void) gatt_client_discover_characteristic_descriptors(&handle_gatt_client_event, connection->con_handle, &characteristic); + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_READ_CHARACTERISTIC_CONFIGURATION: +#ifdef ENABLE_TESTING_SUPPORT + printf("Read client characteristic value [index %d, handle 0x%04X]:\n", connection->receive_states_index, connection->receive_states[connection->receive_states_index].receive_state_ccc_handle); +#endif + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT; + + // result in GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT + (void) gatt_client_read_characteristic_descriptor_using_descriptor_handle( + &handle_gatt_client_event, + connection->con_handle, + connection->receive_states[connection->receive_states_index].receive_state_ccc_handle); + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION: +#ifdef ENABLE_TESTING_SUPPORT + printf("Register notification [index %d, handle 0x%04X]:\n", connection->receive_states_index, connection->receive_states[connection->receive_states_index].receive_state_ccc_handle); +#endif + + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_NOTIFICATION_REGISTERED; + + status = bass_client_register_notification(connection); + if (status == ERROR_CODE_SUCCESS) return; + + if (connection->receive_states[connection->receive_states_index].receive_state_ccc_handle != 0){ + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_READ_CHARACTERISTIC_CONFIGURATION; + break; + } + + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED; + bass_client_emit_connection_established(connection, ERROR_CODE_SUCCESS); + break; + default: + break; + } +} +// @return true if client valid / run function should be called +static bool bass_client_handle_query_complete(bass_client_connection_t * connection, uint8_t status){ + switch (connection->state){ + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_SERVICE_RESULT: + if (status != ATT_ERROR_SUCCESS){ + bass_client_emit_connection_established(connection, status); + bass_client_finalize_connection(connection); + return false; + } + + if (connection->service_instances_num == 0){ + bass_client_emit_connection_established(connection, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE); + bass_client_finalize_connection(connection); + return false; + } + connection->receive_states_index = 0; + connection->receive_states_instances_num = 0; + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTICS; + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_RESULT: + if (status != ATT_ERROR_SUCCESS){ + bass_client_emit_connection_established(connection, status); + bass_client_finalize_connection(connection); + return false; + } + + connection->receive_states_index = 0; + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTIC_DESCRIPTORS; + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_DESCRIPTORS_RESULT: + if (connection->receive_states_index < (connection->receive_states_instances_num - 1)){ + connection->receive_states_index++; + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTIC_DESCRIPTORS; + break; + } + connection->receive_states_index = 0; + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION; + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_NOTIFICATION_REGISTERED: + if (connection->receive_states_index < (connection->receive_states_instances_num - 1)){ + connection->receive_states_index++; + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION; + break; + } + + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED; + bass_client_emit_connection_established(connection, ERROR_CODE_SUCCESS); + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT: + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED; + bass_client_emit_connection_established(connection, ERROR_CODE_SUCCESS); + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_START_SCAN: + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED; + bass_client_emit_scan_operation_complete(connection, status, BASS_OPCODE_REMOTE_SCAN_STARTED); + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_STOP_SCAN: + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED; + bass_client_emit_scan_operation_complete(connection, status, BASS_OPCODE_REMOTE_SCAN_STOPPED); + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_ADD_SOURCE: + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED; + bass_client_emit_source_operation_complete(connection, status, BASS_OPCODE_ADD_SOURCE, BASS_INVALID_SOURCE_INDEX); + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_MODIFY_SOURCE: + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED; + bass_client_emit_source_operation_complete(connection, status, BASS_OPCODE_MODIFY_SOURCE, connection->receive_state_source_id); + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_REMOVE_SOURCE: + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED; + bass_client_reset_source(bass_get_source_for_source_id(connection, connection->receive_state_source_id)); + bass_client_emit_source_operation_complete(connection, status, BASS_OPCODE_REMOVE_SOURCE, connection->receive_state_source_id); + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_SET_BROADCAST_CODE: + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED; + bass_client_emit_source_operation_complete(connection, status, BASS_OPCODE_SET_BROADCAST_CODE, connection->receive_state_source_id); + break; + + case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED: + if (status != ATT_ERROR_SUCCESS){ + break; + } + + break; + + default: + break; + + } + return true; +} + +static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(packet_type); + UNUSED(channel); + UNUSED(size); + + bass_client_connection_t * connection = NULL; + gatt_client_service_t service; + gatt_client_characteristic_t characteristic; + gatt_client_characteristic_descriptor_t characteristic_descriptor; + + bool call_run = true; + + switch(hci_event_packet_get_type(packet)){ + case GATT_EVENT_MTU: + connection = bass_client_get_connection_for_con_handle(gatt_event_mtu_get_handle(packet)); + btstack_assert(connection != NULL); + connection->mtu = gatt_event_mtu_get_MTU(packet); + break; + + case GATT_EVENT_SERVICE_QUERY_RESULT: + connection = bass_client_get_connection_for_con_handle(gatt_event_service_query_result_get_handle(packet)); + btstack_assert(connection != NULL); + + if (connection->service_instances_num < 1){ + gatt_event_service_query_result_get_service(packet, &service); + connection->start_handle = service.start_group_handle; + connection->end_handle = service.end_group_handle; + +#ifdef ENABLE_TESTING_SUPPORT + printf("BASS Service: start handle 0x%04X, end handle 0x%04X\n", connection->start_handle, connection->end_handle); +#endif + connection->service_instances_num++; + } else { + log_info("Found more then one BASS Service instance. "); + } + break; + + case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT: + connection = bass_client_get_connection_for_con_handle(gatt_event_characteristic_query_result_get_handle(packet)); + btstack_assert(connection != NULL); + + gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic); + + switch (characteristic.uuid16){ + case ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_AUDIO_SCAN_CONTROL_POINT: + connection->control_point_value_handle = characteristic.value_handle; + break; + case ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_RECEIVE_STATE: + if (connection->receive_states_instances_num < connection->max_receive_states_num){ + connection->receive_states[connection->receive_states_instances_num].receive_state_value_handle = characteristic.value_handle; + connection->receive_states[connection->receive_states_instances_num].receive_state_properties = characteristic.properties; + } else { + log_info("Found more BASS receive_states chrs then it can be stored. "); + } + break; + default: + btstack_assert(false); + return; + } + +#ifdef ENABLE_TESTING_SUPPORT + printf("BASS Characteristic:\n Attribute Handle 0x%04X, Properties 0x%02X, Handle 0x%04X, UUID 0x%04X, %s\n", + characteristic.start_handle, + characteristic.properties, + characteristic.value_handle, characteristic.uuid16, + characteristic.uuid16 == ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_RECEIVE_STATE ? "RECEIVE_STATE" : "CONTROL_POINT"); +#endif + break; + + + case GATT_EVENT_ALL_CHARACTERISTIC_DESCRIPTORS_QUERY_RESULT: + connection = bass_client_get_connection_for_con_handle(gatt_event_all_characteristic_descriptors_query_result_get_handle(packet)); + btstack_assert(connection != NULL); + gatt_event_all_characteristic_descriptors_query_result_get_characteristic_descriptor(packet, &characteristic_descriptor); + + if (characteristic_descriptor.uuid16 == ORG_BLUETOOTH_DESCRIPTOR_GATT_CLIENT_CHARACTERISTIC_CONFIGURATION){ + connection->receive_states[connection->receive_states_index].receive_state_ccc_handle = characteristic_descriptor.handle; + +#ifdef ENABLE_TESTING_SUPPORT + printf(" BASS Characteristic Configuration Descriptor: Handle 0x%04X, UUID 0x%04X\n", + characteristic_descriptor.handle, + characteristic_descriptor.uuid16); +#endif + } + break; + + + case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT: + connection = bass_client_get_connection_for_con_handle(gatt_event_characteristic_value_query_result_get_handle(packet)); + btstack_assert(connection != NULL); + + if (connection->state == BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT){ +#ifdef ENABLE_TESTING_SUPPORT + printf(" Received CCC value: "); + printf_hexdump(gatt_event_characteristic_value_query_result_get_value(packet), gatt_event_characteristic_value_query_result_get_value_length(packet)); +#endif + break; + } + + if (gatt_event_characteristic_value_query_result_get_value_length(packet) == 0 ){ + break; + } + + // TODO + // bass_client_emit_receive_state(connection, + // gatt_event_characteristic_value_query_result_get_value_handle(packet), + // ATT_ERROR_SUCCESS, + // gatt_event_notification_get_value(packet), + // gatt_event_notification_get_value_length(packet)); + break; + + case GATT_EVENT_QUERY_COMPLETE: + connection = bass_client_get_connection_for_con_handle(gatt_event_query_complete_get_handle(packet)); + btstack_assert(connection != NULL); + call_run = bass_client_handle_query_complete(connection, gatt_event_query_complete_get_att_status(packet)); + break; + + default: + break; + } + + if (call_run && (connection != NULL)){ + bass_client_run_for_connection(connection); + } +} + +void broadcast_audio_scan_service_client_init(btstack_packet_handler_t packet_handler){ + btstack_assert(packet_handler != NULL); + bass_event_callback = packet_handler; +} + +uint8_t broadcast_audio_scan_service_client_connect(bass_client_connection_t * connection, + bass_client_source_t * receive_states, uint8_t receive_states_num, + hci_con_handle_t con_handle, uint16_t * bass_cid){ + + btstack_assert(receive_states != NULL); + btstack_assert(receive_states_num > 0); + + if (bass_client_get_connection_for_con_handle(con_handle) != NULL){ + return ERROR_CODE_COMMAND_DISALLOWED; + } + + uint16_t cid = bass_client_get_next_cid(); + if (bass_cid != NULL) { + *bass_cid = cid; + } + + connection->cid = cid; + connection->con_handle = con_handle; + connection->mtu = ATT_DEFAULT_MTU; + + connection->max_receive_states_num = receive_states_num; + connection->receive_states = receive_states; + + uint8_t i; + for (i = 0; i < connection->max_receive_states_num; i++){ + connection->receive_states[i].in_use = false; + connection->receive_states[i].source_id = BASS_INVALID_SOURCE_INDEX; + } + + connection->service_instances_num = 0; + connection->receive_states_instances_num = 0; + connection->receive_states_index = 0; + + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_SERVICE; + btstack_linked_list_add(&bass_connections, (btstack_linked_item_t *) connection); + + bass_client_run_for_connection(connection); + return ERROR_CODE_SUCCESS; +} + +uint8_t broadcast_audio_scan_service_client_scanning_started(uint16_t bass_cid){ + bass_client_connection_t * connection = bass_get_client_for_cid(bass_cid); + if (connection == NULL){ + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED){ + return ERROR_CODE_COMMAND_DISALLOWED; + } + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_START_SCAN; + bass_client_run_for_connection(connection); + return ERROR_CODE_SUCCESS; +} + +uint8_t broadcast_audio_scan_service_client_scanning_stopped(uint16_t bass_cid){ + bass_client_connection_t * connection = bass_get_client_for_cid(bass_cid); + if (connection == NULL){ + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED){ + return ERROR_CODE_COMMAND_DISALLOWED; + } + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_STOP_SCAN; + bass_client_run_for_connection(connection); + return ERROR_CODE_SUCCESS; +} + +uint8_t broadcast_audio_scan_service_client_add_source(uint16_t bass_cid, bass_source_data_t * receive_state){ + bass_client_connection_t * connection = bass_get_client_for_cid(bass_cid); + if (connection == NULL){ + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED){ + return ERROR_CODE_COMMAND_DISALLOWED; + } + + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_ADD_SOURCE; + connection->receive_state_data = receive_state; + connection->buffer_offset = 0; + connection->data_size = bass_client_receive_state_len(receive_state); + + bass_client_run_for_connection(connection); + return ERROR_CODE_SUCCESS; +} + +uint8_t broadcast_audio_scan_service_client_modify_source(uint16_t bass_cid, uint8_t source_id, bass_source_data_t * source_data){ + bass_client_connection_t * connection = bass_get_client_for_cid(bass_cid); + if (connection == NULL){ + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED){ + return ERROR_CODE_COMMAND_DISALLOWED; + } + + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_MODIFY_SOURCE; + connection->receive_state_data = source_data; + connection->receive_state_source_id = source_id; + connection->buffer_offset = 0; + connection->data_size = bass_client_receive_state_len(source_data); + + bass_client_run_for_connection(connection); + return ERROR_CODE_SUCCESS; +} + +uint8_t broadcast_audio_scan_service_client_delete_source(uint16_t bass_cid, uint8_t source_id){ + bass_client_connection_t * connection = bass_get_client_for_cid(bass_cid); + if (connection == NULL){ + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED){ + return ERROR_CODE_COMMAND_DISALLOWED; + } + + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_REMOVE_SOURCE; + connection->receive_state_source_id = source_id; + + bass_client_run_for_connection(connection); + return ERROR_CODE_SUCCESS; +} + +uint8_t broadcast_audio_scan_service_client_set_broadcast_code(uint16_t bass_cid, uint8_t source_id, uint8_t * broadcast_code){ + bass_client_connection_t * connection = bass_get_client_for_cid(bass_cid); + if (connection == NULL){ + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED){ + return ERROR_CODE_COMMAND_DISALLOWED; + } + + connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_SET_BROADCAST_CODE; + connection->receive_state_source_id = source_id; + connection->broadcast_code = broadcast_code; + + bass_client_run_for_connection(connection); + return ERROR_CODE_SUCCESS; +} + +void broadcast_audio_scan_service_client_deinit(uint16_t bass_cid){ + bass_client_connection_t * connection = bass_get_client_for_cid(bass_cid); + if (connection == NULL){ + return; + } + // finalize connections + bass_client_finalize_connection(connection); +} + diff --git a/src/ble/gatt-service/broadcast_audio_scan_service_client.h b/src/ble/gatt-service/broadcast_audio_scan_service_client.h new file mode 100644 index 000000000..ed79a0e1d --- /dev/null +++ b/src/ble/gatt-service/broadcast_audio_scan_service_client.h @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2022 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN + * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +/** + * @title Audio Stream Control Service Client + * + */ + +#ifndef BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_H +#define BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_H + +#include +#include "le_audio.h" +#include "broadcast_audio_scan_service_util.h" + +#if defined __cplusplus +extern "C" { +#endif + +#define BASS_CLIENT_MAX_ATT_BUFFER_SIZE 512 +/* API_START */ + +typedef enum { + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_IDLE, + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_SERVICE, + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_SERVICE_RESULT, + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTICS, + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_RESULT, + + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTIC_DESCRIPTORS, + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_DESCRIPTORS_RESULT, + + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION, + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_NOTIFICATION_REGISTERED, + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED, + + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_READ_CHARACTERISTIC_CONFIGURATION, + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT, + + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_START_SCAN, + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_START_SCAN, + + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_STOP_SCAN, + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_STOP_SCAN, + + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_ADD_SOURCE, + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_ADD_SOURCE, + + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_MODIFY_SOURCE, + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_MODIFY_SOURCE, + + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_SET_BROADCAST_CODE, + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_SET_BROADCAST_CODE, + + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_REMOVE_SOURCE, + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_REMOVE_SOURCE, + + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_READE_RECEIVE_STATE, + BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_READE_RECEIVE_STATE, + +} broadcast_audio_scan_service_client_state_t; + +typedef struct { + // used for add source command + bass_source_data_t data; + + // received via notification + uint8_t source_id; + bool in_use; + le_audio_pa_sync_t pa_sync; + + // characteristic + uint16_t receive_state_value_handle; + uint16_t receive_state_ccc_handle; + uint16_t receive_state_properties; +} bass_client_source_t; + + +typedef struct { + btstack_linked_item_t item; + + hci_con_handle_t con_handle; + uint16_t cid; + uint16_t mtu; + broadcast_audio_scan_service_client_state_t state; + + // service + uint16_t start_handle; + uint16_t end_handle; + uint16_t control_point_value_handle; + + // used for memory capacity checking + uint8_t service_instances_num; + uint8_t receive_states_instances_num; + // used for notification registration + uint8_t receive_states_index; + + uint8_t max_receive_states_num; + bass_client_source_t * receive_states; + + // used for write segmentation + uint8_t buffer[BASS_CLIENT_MAX_ATT_BUFFER_SIZE]; + uint16_t buffer_offset; + uint16_t data_size; + + gatt_client_notification_t notification_listener; + + // used for adding and modifying source + bass_source_data_t * receive_state_data; + uint8_t receive_state_source_id; + // used for setting the broadcast code + uint8_t * broadcast_code; +} bass_client_connection_t; + +/** + * @brief Init Broadcast Audio Scan Service Client + */ +void broadcast_audio_scan_service_client_init(btstack_packet_handler_t packet_handler); + +uint8_t broadcast_audio_scan_service_client_connect(bass_client_connection_t * connection, bass_client_source_t * sources, uint8_t num_sources, hci_con_handle_t con_handle, uint16_t * bass_cid); + +uint8_t broadcast_audio_scan_service_client_scanning_started(uint16_t bass_cid); + +uint8_t broadcast_audio_scan_service_client_scanning_stopped(uint16_t bass_cid); + +uint8_t broadcast_audio_scan_service_client_add_source(uint16_t bass_cid, bass_source_data_t * source_data); + +uint8_t broadcast_audio_scan_service_client_modify_source(uint16_t bass_cid, uint8_t source_id, bass_source_data_t * source_data); + +uint8_t broadcast_audio_scan_service_client_set_broadcast_code(uint16_t bass_cid, uint8_t source_id, uint8_t * broadcast_code); + +uint8_t broadcast_audio_scan_service_client_delete_source(uint16_t bass_cid, uint8_t source_id); + + +/** + * @brief Deinit Broadcast Audio Scan Service Client + */ +void broadcast_audio_scan_service_client_deinit(uint16_t bass_cid); + +/* API_END */ + +#if defined __cplusplus +} +#endif + +#endif + diff --git a/src/ble/gatt-service/broadcast_audio_scan_service_server.c b/src/ble/gatt-service/broadcast_audio_scan_service_server.c new file mode 100644 index 000000000..39b8d6b7d --- /dev/null +++ b/src/ble/gatt-service/broadcast_audio_scan_service_server.c @@ -0,0 +1,679 @@ +/* + * Copyright (C) 2022 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN + * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +#define BTSTACK_FILE__ "broadcast_audio_scan_service_server.c" + +#include + +#include "ble/att_db.h" +#include "ble/att_server.h" +#include "bluetooth_gatt.h" +#include "btstack_debug.h" +#include "btstack_defines.h" +#include "btstack_event.h" +#include "btstack_util.h" + +#include "ble/gatt-service/broadcast_audio_scan_service_server.h" +#include "ble/gatt-service/le_audio_util.h" + +#ifdef ENABLE_TESTING_SUPPORT +#include +#endif + +#define BASS_MAX_NOTIFY_BUFFER_SIZE 200 +#define BASS_INVALID_SOURCE_INDEX 0xFF + +static att_service_handler_t broadcast_audio_scan_service; +static btstack_packet_handler_t bass_event_callback; + +// characteristic: AUDIO_SCAN_CONTROL_POINT +static uint16_t bass_audio_scan_control_point_handle; + +static uint8_t bass_logic_time = 0; + +static bass_server_source_t * bass_sources; +static uint8_t bass_sources_num = 0; +static bass_remote_client_t * bass_clients; +static uint8_t bass_clients_num = 0; +static btstack_context_callback_registration_t scheduled_tasks_callback; + +static uint8_t bass_get_next_update_counter(void){ + uint8_t next_update_counter; + if (bass_logic_time == 0xff) { + next_update_counter = 0; + } else { + next_update_counter = bass_logic_time + 1; + } + bass_logic_time = next_update_counter; + return next_update_counter; +} + +// returns positive number if counter a > b +static int8_t bass_counter_delta(uint8_t counter_a, uint8_t counter_b){ + return (int8_t)(counter_a - counter_b); +} + +static uint8_t bass_find_empty_or_last_used_source_index(void){ + bass_server_source_t * last_used_source = NULL; + uint8_t last_used_source_index = BASS_INVALID_SOURCE_INDEX; + + uint8_t i; + for (i = 0; i < bass_sources_num; i++){ + if (!bass_sources[i].in_use){ + return i; + } + if (last_used_source == NULL){ + last_used_source = &bass_sources[i]; + last_used_source_index = i; + continue; + } + if (bass_counter_delta(bass_sources[i].update_counter, last_used_source->update_counter) < 0 ){ + last_used_source = &bass_sources[i]; + last_used_source_index = i; + } + } + return last_used_source_index; +} + +static bass_server_source_t * bass_find_empty_or_last_used_source(void){ + uint8_t last_used_source_index = bass_find_empty_or_last_used_source_index(); + if (last_used_source_index == BASS_INVALID_SOURCE_INDEX){ + return NULL; + } + return &bass_sources[last_used_source_index]; +} + +static bass_server_source_t * bass_find_receive_state_for_value_handle(uint16_t attribute_handle){ + uint16_t i; + for (i = 0; i < bass_sources_num; i++){ + if (attribute_handle == bass_sources[i].bass_receive_state_handle){ + return &bass_sources[i]; + } + } + return NULL; +} + +static bass_server_source_t * bass_find_receive_state_for_client_configuration_handle(uint16_t attribute_handle){ + if (attribute_handle == 0){ + return NULL; + } + uint8_t i; + for (i = 0; i < bass_sources_num; i++){ + if (bass_sources[i].bass_receive_state_client_configuration_handle == attribute_handle){ + return &bass_sources[i]; + } + } + return NULL; +} + +static bass_server_source_t * bass_find_source_for_source_id(uint8_t source_id){ + if (source_id < bass_sources_num){ + return &bass_sources[source_id]; + } + return NULL; +} + +static bass_remote_client_t * bass_find_client_for_con_handle(hci_con_handle_t con_handle){ + uint16_t i; + for (i = 0; i < bass_clients_num; i++){ + if (bass_clients[i].con_handle == con_handle) { + return &bass_clients[i]; + } + } + return NULL; +} + +static void bass_register_con_handle(hci_con_handle_t con_handle, uint16_t client_configuration){ + bass_remote_client_t * client = bass_find_client_for_con_handle(con_handle); + if (client == NULL){ + client = bass_find_client_for_con_handle(HCI_CON_HANDLE_INVALID); + if (client == NULL){ + return; + } + + } + client->con_handle = (client_configuration == 0) ? HCI_CON_HANDLE_INVALID : con_handle; +} + +static void bass_emit_remote_scan_stoped(hci_con_handle_t con_handle){ + btstack_assert(bass_event_callback != NULL); + + uint8_t event[5]; + uint8_t pos = 0; + event[pos++] = HCI_EVENT_GATTSERVICE_META; + event[pos++] = sizeof(event) - 2; + event[pos++] = GATTSERVICE_SUBEVENT_BASS_REMOTE_SCAN_STOPPED; + little_endian_store_16(event, pos, con_handle); + pos += 2; + (*bass_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); +} + +static void bass_emit_remote_scan_started(hci_con_handle_t con_handle){ + btstack_assert(bass_event_callback != NULL); + + uint8_t event[5]; + uint8_t pos = 0; + event[pos++] = HCI_EVENT_GATTSERVICE_META; + event[pos++] = sizeof(event) - 2; + event[pos++] = GATTSERVICE_SUBEVENT_BASS_REMOTE_SCAN_STARTED; + little_endian_store_16(event, pos, con_handle); + pos += 2; + (*bass_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); +} + + +static void bass_emit_source_state_changed(uint8_t subevent_id, hci_con_handle_t con_handle, uint8_t source_id, le_audio_pa_sync_t pa_sync){ + btstack_assert(bass_event_callback != NULL); + + uint8_t event[7]; + uint8_t pos = 0; + event[pos++] = HCI_EVENT_GATTSERVICE_META; + event[pos++] = sizeof(event) - 2; + event[pos++] = subevent_id; + little_endian_store_16(event, pos, con_handle); + pos += 2; + event[pos++] = source_id; + event[pos++] = (uint8_t)pa_sync; + (*bass_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); +} + +static void bass_emit_source_added(hci_con_handle_t con_handle, bass_server_source_t * source){ + bass_emit_source_state_changed(GATTSERVICE_SUBEVENT_BASS_SOURCE_ADDED, con_handle, source->source_id, source->pa_sync); +} + +static void bass_emit_source_modified(hci_con_handle_t con_handle, bass_server_source_t * source){ + bass_emit_source_state_changed(GATTSERVICE_SUBEVENT_BASS_SOURCE_MODIFIED, con_handle, source->source_id, source->pa_sync); +} + +static void bass_emit_source_deleted(hci_con_handle_t con_handle, bass_server_source_t * source){ + bass_emit_source_state_changed(GATTSERVICE_SUBEVENT_BASS_SOURCE_DELETED, con_handle, source->source_id, LE_AUDIO_PA_SYNC_DO_NOT_SYNCHRONIZE_TO_PA); +} + +static void bass_emit_broadcast_code(hci_con_handle_t con_handle, uint8_t source_id, uint8_t * broadcast_code){ + btstack_assert(bass_event_callback != NULL); + + uint8_t event[22]; + uint8_t pos = 0; + event[pos++] = HCI_EVENT_GATTSERVICE_META; + event[pos++] = sizeof(event) - 2; + event[pos++] = GATTSERVICE_SUBEVENT_BASS_BROADCAST_CODE; + little_endian_store_16(event, pos, con_handle); + pos += 2; + event[pos++] = source_id; + reverse_bytes(&broadcast_code[1], &event[pos], 16); + pos += 16; + (*bass_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); +} + +// offset gives position into fully serialized bass record +static uint16_t bass_server_copy_source_to_buffer(bass_server_source_t * source, uint16_t buffer_offset, uint8_t * buffer, uint16_t buffer_size){ + uint8_t field_data[16]; + uint16_t source_offset = 0; + uint16_t stored_bytes = 0; + memset(buffer, 0, buffer_size); + + if (!source->in_use){ + return 0; + } + field_data[0] = source->source_id; + stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset); + source_offset++; + + stored_bytes += bass_util_copy_source_common_data_to_buffer(&source->data, &source_offset, buffer_offset, buffer, buffer_size); + + field_data[0] = (uint8_t)source->data.pa_sync_state; + stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset); + source_offset++; + + field_data[0] = (uint8_t)source->big_encryption; + stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset); + source_offset++; + + if (source->big_encryption == LE_AUDIO_BIG_ENCRYPTION_BAD_CODE){ + reverse_128(source->bad_code, &field_data[0]); + stored_bytes += le_audio_virtual_memcpy_helper(field_data, 16, source_offset, buffer, buffer_size, buffer_offset); + source_offset += 16; + } + + stored_bytes += bass_util_copy_source_metadata_to_buffer(&source->data, &source_offset, buffer_offset, buffer, buffer_size); + return stored_bytes; +} + +static uint16_t broadcast_audio_scan_service_read_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size){ + UNUSED(con_handle); + + bass_server_source_t * source; + source = bass_find_receive_state_for_value_handle(attribute_handle); + if (source){ + return bass_server_copy_source_to_buffer(source, offset, buffer, buffer_size); + } + + source = bass_find_receive_state_for_client_configuration_handle(attribute_handle); + if (source){ + return att_read_callback_handle_little_endian_16(source->bass_receive_state_client_configuration, offset, buffer, buffer_size); + } + + return 0; +} + +static bool bass_source_remote_modify_source_buffer_valid(uint8_t *buffer, uint16_t buffer_size){ + if (buffer_size < 10){ + log_info("Modify Source opcode, buffer too small"); + return false; + } + + uint8_t pos = 1; // source_id + return bass_util_pa_sync_state_and_subgroups_in_valid_range(buffer+pos, buffer_size-pos); +} + +static void bass_server_add_source_from_buffer(uint8_t *buffer, uint16_t buffer_size, bass_server_source_t * source){ + UNUSED(buffer_size); + + source->update_counter = bass_get_next_update_counter(); + source->in_use = true; + + bass_util_get_source_from_buffer(buffer, buffer_size, &source->data); +} + +static bool bass_pa_synchronized(bass_server_source_t * source){ + return source->data.pa_sync_state == LE_AUDIO_PA_SYNC_STATE_SYNCHRONIZED_TO_PA; +} + + +static bool bass_bis_synchronized(bass_server_source_t * source){ + uint8_t i; + for (i = 0; i < source->data.subgroups_num; i++){ + if ((source->data.subgroups[i].bis_sync_state > 0) && (source->data.subgroups[i].bis_sync_state < 0xFFFFFFFF)){ + return true; + } + } + return false; +} + + +static void bass_reset_source(bass_server_source_t * source){ + source->in_use = false; + source->data.address_type = BD_ADDR_TYPE_LE_PUBLIC; + memset(source->data.address, 0, sizeof(source->data.address)); + source->data.adv_sid = 0; + source->data.broadcast_id = 0; + source->pa_sync = LE_AUDIO_PA_SYNC_DO_NOT_SYNCHRONIZE_TO_PA; + source->data.pa_sync_state = LE_AUDIO_PA_SYNC_STATE_NOT_SYNCHRONIZED_TO_PA; + source->big_encryption = LE_AUDIO_BIG_ENCRYPTION_NOT_ENCRYPTED; + memset(source->bad_code, 0, sizeof(source->bad_code)); + source->data.pa_interval = 0; + source->data.subgroups_num = 0; + memset(source->data.subgroups, 0, sizeof(source->data.subgroups)); +} + +static void bass_reset_client_long_write_buffer(bass_remote_client_t * client){ + memset(client->long_write_buffer, 0, sizeof(client->long_write_buffer)); + client->long_write_value_size = 0; +} + +static int broadcast_audio_scan_service_write_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size){ + // printf("broadcast_audio_scan_service_write_callback con_handle 0x%02x, attr_handle 0x%02x \n", con_handle, attribute_handle); + if (attribute_handle != 0 && attribute_handle != bass_audio_scan_control_point_handle){ + + bass_server_source_t * source = bass_find_receive_state_for_client_configuration_handle(attribute_handle); + if (source){ + source->bass_receive_state_client_configuration = little_endian_read_16(buffer, 0); + bass_register_con_handle(con_handle, source->bass_receive_state_client_configuration); + } + return 0; + } + + + bass_remote_client_t * client = bass_find_client_for_con_handle(con_handle); + if (client == NULL){ + return ATT_ERROR_WRITE_REQUEST_REJECTED; + } + + uint16_t total_value_len = buffer_size + offset; + + switch (transaction_mode){ + case ATT_TRANSACTION_MODE_NONE: + if (buffer_size > sizeof(client->long_write_buffer)){ + bass_reset_client_long_write_buffer(client); + return ATT_ERROR_WRITE_REQUEST_REJECTED; + } + memcpy(&client->long_write_buffer[0], buffer, buffer_size); + client->long_write_value_size = total_value_len; + break; + + case ATT_TRANSACTION_MODE_ACTIVE: + if (total_value_len > sizeof(client->long_write_buffer)){ + bass_reset_client_long_write_buffer(client); + return ATT_ERROR_WRITE_REQUEST_REJECTED; + } + // allow overlapped and/or mixed order chunks + client->long_write_attribute_handle = attribute_handle; + + memcpy(&client->long_write_buffer[offset], buffer, buffer_size); + if (total_value_len > client->long_write_value_size){ + client->long_write_value_size = total_value_len; + } + return 0; + + case ATT_TRANSACTION_MODE_VALIDATE: + return 0; + + case ATT_TRANSACTION_MODE_EXECUTE: + attribute_handle = client->long_write_attribute_handle; + break; + + default: + return 0; + } + + if (attribute_handle == bass_audio_scan_control_point_handle){ + if (client->long_write_value_size < 2){ + return ATT_ERROR_WRITE_REQUEST_REJECTED; + } + + bass_opcode_t opcode = (bass_opcode_t)client->long_write_buffer[0]; + uint8_t *remote_data = &client->long_write_buffer[1]; + uint16_t remote_data_size = client->long_write_value_size - 1; + + bass_server_source_t * source; + switch (opcode){ + case BASS_OPCODE_REMOTE_SCAN_STOPPED: + if (remote_data_size != 1){ + return ATT_ERROR_WRITE_REQUEST_REJECTED; + } + bass_emit_remote_scan_stoped(con_handle); + break; + + case BASS_OPCODE_REMOTE_SCAN_STARTED: + if (remote_data_size != 1){ + return ATT_ERROR_WRITE_REQUEST_REJECTED; + } + bass_emit_remote_scan_started(con_handle); + break; + + case BASS_OPCODE_ADD_SOURCE: + if (!bass_util_add_source_buffer_in_valid_range(remote_data, remote_data_size)){ + return ATT_ERROR_WRITE_REQUEST_REJECTED; + } + source = bass_find_empty_or_last_used_source(); + btstack_assert(source != NULL); + log_info("add source %d", source->source_id); + bass_server_add_source_from_buffer(remote_data, remote_data_size, source); + bass_emit_source_added(con_handle, source); + // server needs to trigger notification + break; + + case BASS_OPCODE_MODIFY_SOURCE: + if (!bass_source_remote_modify_source_buffer_valid(remote_data, remote_data_size)){ + return ATT_ERROR_WRITE_REQUEST_REJECTED; + } + + source = bass_find_source_for_source_id(remote_data[0]); + if (source == NULL){ + return BASS_ERROR_CODE_INVALID_SOURCE_ID; + } + bass_util_get_pa_info_and_subgroups_from_buffer(remote_data+1, remote_data_size-1, &source->data); + bass_emit_source_modified(con_handle, source); + // server needs to trigger notification + break; + + case BASS_OPCODE_SET_BROADCAST_CODE: + if (remote_data_size != 17){ + return ATT_ERROR_WRITE_REQUEST_REJECTED; + } + + source = bass_find_source_for_source_id(remote_data[0]); + if (source == NULL){ + return BASS_ERROR_CODE_INVALID_SOURCE_ID; + } + bass_emit_broadcast_code(con_handle, source->source_id, &remote_data[1]); + break; + + case BASS_OPCODE_REMOVE_SOURCE: + if (remote_data_size != 1){ + return ATT_ERROR_WRITE_REQUEST_REJECTED; + } + source = bass_find_source_for_source_id(remote_data[0]); + if (source == NULL){ + return BASS_ERROR_CODE_INVALID_SOURCE_ID; + } + + if (bass_pa_synchronized(source)){ + log_info("remove source %d rejected, PA synchronised", source->source_id); + return 0; + } + + if (bass_bis_synchronized(source)){ + log_info("remove source %d rejected, BIS synchronised", source->source_id); + return 0; + } + + bass_reset_source(source); + broadcast_audio_scan_service_server_set_pa_sync_state(source->source_id, LE_AUDIO_PA_SYNC_STATE_NOT_SYNCHRONIZED_TO_PA); + bass_emit_source_deleted(con_handle, source); + break; + + default: + bass_reset_client_long_write_buffer(client); + return BASS_ERROR_CODE_OPCODE_NOT_SUPPORTED; + } + bass_reset_client_long_write_buffer(client); + } + return 0; +} + +static void broadcast_audio_scan_service_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(channel); + UNUSED(packet); + UNUSED(size); + + if (packet_type != HCI_EVENT_PACKET){ + return; + } + + hci_con_handle_t con_handle; + bass_remote_client_t * client; + + switch (hci_event_packet_get_type(packet)) { + case HCI_EVENT_DISCONNECTION_COMPLETE: + con_handle = hci_event_disconnection_complete_get_connection_handle(packet); + + client = bass_find_client_for_con_handle(con_handle); + if (client == NULL){ + break; + } + + memset(client, 0, sizeof(bass_remote_client_t)); + client->con_handle = HCI_CON_HANDLE_INVALID; + break; + default: + break; + } +} + +void broadcast_audio_scan_service_server_init(const uint8_t sources_num, bass_server_source_t * sources, const uint8_t clients_num, bass_remote_client_t * clients){ + // get service handle range + btstack_assert(sources_num != 0); + btstack_assert(clients_num != 0); + + uint16_t start_handle = 0; + uint16_t end_handle = 0xffff; + int service_found = gatt_server_get_handle_range_for_service_with_uuid16(ORG_BLUETOOTH_SERVICE_BROADCAST_AUDIO_SCAN_SERVICE, &start_handle, &end_handle); + btstack_assert(service_found != 0); + + UNUSED(service_found); + + bass_audio_scan_control_point_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_AUDIO_SCAN_CONTROL_POINT); + bass_sources_num = 0; + bass_logic_time = 0; + bass_sources = sources; + +#ifdef ENABLE_TESTING_SUPPORT + printf("BASS 0x%02x - 0x%02x \n", start_handle, end_handle); +#endif + uint16_t start_chr_handle = start_handle; + while ( (start_chr_handle < end_handle) && (bass_sources_num < sources_num )) { + uint16_t chr_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_chr_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_RECEIVE_STATE); + uint16_t chr_client_configuration_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_chr_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_RECEIVE_STATE); + + if (chr_value_handle == 0){ + break; + } + bass_server_source_t * source = &bass_sources[bass_sources_num]; + bass_reset_source(source); + + source->source_id = bass_sources_num; + source->update_counter = bass_get_next_update_counter(); + source->bass_receive_state_client_configuration = 0; + + source->bass_receive_state_handle = chr_value_handle; + source->bass_receive_state_client_configuration_handle = chr_client_configuration_handle; + +#ifdef ENABLE_TESTING_SUPPORT + printf(" bass_receive_state_%d 0x%02x \n", bass_sources_num, source->bass_receive_state_handle); + printf(" bass_receive_state_%d CCC 0x%02x \n", bass_sources_num, source->bass_receive_state_client_configuration_handle); +#endif + + start_chr_handle = chr_client_configuration_handle + 1; + bass_sources_num++; + } + + bass_clients_num = clients_num; + bass_clients = clients; + memset(bass_clients, 0, sizeof(bass_remote_client_t) * bass_clients_num); + uint8_t i; + for (i = 0; i < bass_clients_num; i++){ + bass_clients[i].con_handle = HCI_CON_HANDLE_INVALID; + } + + log_info("Found BASS service 0x%02x-0x%02x (num sources %d)", start_handle, end_handle, bass_sources_num); + + // register service with ATT Server + broadcast_audio_scan_service.start_handle = start_handle; + broadcast_audio_scan_service.end_handle = end_handle; + broadcast_audio_scan_service.read_callback = &broadcast_audio_scan_service_read_callback; + broadcast_audio_scan_service.write_callback = &broadcast_audio_scan_service_write_callback; + broadcast_audio_scan_service.packet_handler = broadcast_audio_scan_service_packet_handler; + att_server_register_service_handler(&broadcast_audio_scan_service); +} + +void broadcast_audio_scan_service_server_register_packet_handler(btstack_packet_handler_t callback){ + btstack_assert(callback != NULL); + bass_event_callback = callback; +} + +static void bass_service_can_send_now(void * context){ + bass_remote_client_t * client = (bass_remote_client_t *) context; + btstack_assert(client != NULL); + + uint8_t source_index; + for (source_index = 0; source_index < bass_sources_num; source_index++){ + uint8_t task = (1 << source_index); + if ((client->sources_to_notify & task) != 0){ + client->sources_to_notify &= ~task; + uint8_t buffer[BASS_MAX_NOTIFY_BUFFER_SIZE]; + uint16_t bytes_copied = bass_server_copy_source_to_buffer(&bass_sources[source_index], 0, buffer, sizeof(buffer)); + att_server_notify(client->con_handle, bass_sources[source_index].bass_receive_state_handle, &buffer[0], bytes_copied); + return; + } + } + + uint8_t i; + for (i = 0; i < bass_clients_num; i++){ + client = &bass_clients[i]; + + if (client->sources_to_notify != 0){ + scheduled_tasks_callback.callback = &bass_service_can_send_now; + scheduled_tasks_callback.context = (void*) client; + att_server_register_can_send_now_callback(&scheduled_tasks_callback, client->con_handle); + return; + } + } +} + +static void bass_set_callback(uint8_t source_index){ + // there is only one type of task: notify on source state change + // as task we register which source is changed, and the change will be propagated to all clients + uint8_t i; + uint8_t task = (1 << source_index); + + uint8_t scheduled_tasks = 0; + + for (i = 0; i < bass_clients_num; i++){ + bass_remote_client_t * client = &bass_clients[i]; + + if (client->con_handle == HCI_CON_HANDLE_INVALID){ + client->sources_to_notify &= ~task; + return; + } + + scheduled_tasks |= client->sources_to_notify; + client->sources_to_notify |= task; + + if (scheduled_tasks == 0){ + scheduled_tasks_callback.callback = &bass_service_can_send_now; + scheduled_tasks_callback.context = (void*) client; + att_server_register_can_send_now_callback(&scheduled_tasks_callback, client->con_handle); + } + } +} + +void broadcast_audio_scan_service_server_set_pa_sync_state(uint8_t source_index, le_audio_pa_sync_state_t sync_state){ + btstack_assert(source_index < bass_sources_num); + + bass_server_source_t * source = &bass_sources[source_index]; + source->data.pa_sync_state = sync_state; + + if (source->bass_receive_state_client_configuration != 0){ + bass_set_callback(source_index); + } +} + +void broadcast_audio_scan_service_server_add_source(bass_source_data_t source_data, uint8_t * source_index){ + *source_index = bass_find_empty_or_last_used_source_index(); + if (*source_index == BASS_INVALID_SOURCE_INDEX){ + return; + } + bass_server_source_t * last_used_source = &bass_sources[*source_index]; + last_used_source->update_counter = bass_get_next_update_counter(); + last_used_source->in_use = true; + last_used_source->source_id = *source_index; + memcpy(&last_used_source->data, &source_data, sizeof(bass_source_data_t)); +} + +void broadcast_audio_scan_service_server_deinit(void){ +} diff --git a/src/ble/gatt-service/broadcast_audio_scan_service_server.h b/src/ble/gatt-service/broadcast_audio_scan_service_server.h new file mode 100644 index 000000000..9fd2c9ab1 --- /dev/null +++ b/src/ble/gatt-service/broadcast_audio_scan_service_server.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2022 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN + * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +/** + * @title Broadcast Audio Scan Service Server (BASS) + * + * @text The Broadcast Audio Scan Service is used by servers to expose their status with respect + * to synchronization to broadcast Audio Streams and associated data, including Broadcast_Codes + * used to decrypt encrypted broadcast Audio Streams. Clients can use the attributes exposed by + * servers to observe and/or request changes in server behavior. + * + * To use with your application, add `#import ` to your .gatt file. + */ + +#ifndef BROADCAST_AUDIO_SCAN_SERVICE_SERVER_H +#define BROADCAST_AUDIO_SCAN_SERVICE_SERVER_H + +#include +#include "le_audio.h" +#include "broadcast_audio_scan_service_util.h" + +#if defined __cplusplus +extern "C" { +#endif + +/* API_START */ +// memory for list of these structs is allocated by the application +typedef struct { + // assigned by client via control point + bass_source_data_t data; + + uint8_t update_counter; + uint8_t source_id; + bool in_use; + + le_audio_pa_sync_t pa_sync; + le_audio_big_encryption_t big_encryption; + uint8_t bad_code[16]; + + uint16_t bass_receive_state_handle; + uint16_t bass_receive_state_client_configuration_handle; + uint16_t bass_receive_state_client_configuration; +} bass_server_source_t; + +typedef struct { + hci_con_handle_t con_handle; + uint16_t sources_to_notify; + + // used for caching long write + uint8_t long_write_buffer[512]; + uint16_t long_write_value_size; + uint16_t long_write_attribute_handle; +} bass_remote_client_t; + +/** + * @brief Init Broadcast Audio Scan Service Server with ATT DB + * @param sources_num + * @param sources + * @param clients_num + * @param clients + */ +void broadcast_audio_scan_service_server_init(uint8_t const sources_num, bass_server_source_t * sources, uint8_t const clients_num, bass_remote_client_t * clients); + +/** + * @brief Register callback. + * @param callback + */ +void broadcast_audio_scan_service_server_register_packet_handler(btstack_packet_handler_t callback); + +/** + * @brief Set PA state of source. + * @param source_index + * @param sync_state + */ +void broadcast_audio_scan_service_server_set_pa_sync_state(uint8_t source_index, le_audio_pa_sync_state_t sync_state); + +/** + * @brief Register callback. + * @param source_data + * @param source_index + */ +void broadcast_audio_scan_service_server_add_source(bass_source_data_t source_data, uint8_t * source_index); + +/** + * @brief Deinit Broadcast Audio Scan Service Server + */ +void broadcast_audio_scan_service_server_deinit(void); + +/* API_END */ + +#if defined __cplusplus +} +#endif + +#endif + diff --git a/src/ble/gatt-service/broadcast_audio_scan_service_util.c b/src/ble/gatt-service/broadcast_audio_scan_service_util.c new file mode 100644 index 000000000..c9212e78f --- /dev/null +++ b/src/ble/gatt-service/broadcast_audio_scan_service_util.c @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2022 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN + * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +#define BTSTACK_FILE__ "broadcast_audio_scan_service_server.c" + +#include + +#include "ble/att_db.h" +#include "ble/att_server.h" +#include "bluetooth_gatt.h" +#include "btstack_debug.h" +#include "btstack_defines.h" +#include "btstack_event.h" +#include "btstack_util.h" + +#include "ble/gatt-service/broadcast_audio_scan_service_util.h" +#include "ble/gatt-service/le_audio.h" +#include "ble/gatt-service/le_audio_util.h" + +#ifdef ENABLE_TESTING_SUPPORT +#include +#endif + +// offset gives position into fully serialized bass record +uint16_t bass_util_copy_source_common_data_to_buffer(bass_source_data_t * data, uint16_t *source_offset, uint16_t buffer_offset, uint8_t * buffer, uint16_t buffer_size){ + uint16_t stored_bytes = 0; + uint8_t field_data[16]; + + field_data[0] = (uint8_t)data->address_type; + stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, *source_offset, buffer, buffer_size, buffer_offset); + (*source_offset)++; + + reverse_bd_addr(data->address, &field_data[0]); + stored_bytes += le_audio_virtual_memcpy_helper(field_data, 6, *source_offset, buffer, buffer_size, buffer_offset); + (*source_offset) += 6; + + field_data[0] = data->adv_sid; + stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, *source_offset, buffer, buffer_size, buffer_offset); + (*source_offset)++; + + little_endian_store_24(field_data, 0, data->broadcast_id); + stored_bytes += le_audio_virtual_memcpy_helper(field_data, 3, *source_offset, buffer, buffer_size, buffer_offset); + (*source_offset) += 3; + + return stored_bytes; +} + +// offset gives position into fully serialized bass record +uint16_t bass_util_copy_source_metadata_to_buffer(bass_source_data_t * data, uint16_t *source_offset, uint16_t buffer_offset, uint8_t * buffer, uint16_t buffer_size){ + uint16_t stored_bytes = 0; + uint8_t field_data[16]; + + field_data[0] = (uint8_t)data->subgroups_num; + stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, *source_offset, buffer, buffer_size, buffer_offset); + (*source_offset)++; + + if (data->subgroups_num == 0){ + return stored_bytes; + } + + uint8_t i; + for (i = 0; i < data->subgroups_num; i++){ + bass_subgroup_t subgroup = data->subgroups[i]; + + little_endian_store_32(field_data, 0, subgroup.bis_sync_state); + + stored_bytes += le_audio_virtual_memcpy_helper(field_data, 4, *source_offset, buffer, buffer_size, buffer_offset); + (*source_offset) += 4; + stored_bytes += le_audio_virtual_memcpy_metadata(&subgroup.metadata, subgroup.metadata_length, source_offset, buffer, buffer_size, buffer_offset); + } + return stored_bytes; +} + +bool bass_util_pa_sync_state_and_subgroups_in_valid_range(uint8_t *buffer, uint16_t buffer_size){ + uint8_t pos = 0; + // pa_sync_state + uint8_t pa_sync_state = buffer[pos++]; + if (pa_sync_state >= (uint8_t)LE_AUDIO_PA_SYNC_STATE_RFU){ + log_info("Unexpected pa_sync_state 0x%02X", pa_sync_state); + return false; + } + + // pa_interval + pos += 2; + uint8_t num_subgroups = buffer[pos++]; + if (num_subgroups > BASS_SUBGROUPS_MAX_NUM){ + log_info("Number of subgroups %u exceedes maximum %u", num_subgroups, BASS_SUBGROUPS_MAX_NUM); + return false; + } + + // If a BIS_Sync parameter value is not 0xFFFFFFFF for a subgroup, + // and if a BIS_index value written by a client is set to a value of 0b1 in more than one subgroup, + // the server shall ignore the operation. + uint8_t i; + uint32_t mask_total = 0; + for (i = 0; i < num_subgroups; i++){ + // bis_sync + uint32_t bis_sync = little_endian_read_32(buffer, pos); + pos += 4; + + if (bis_sync != 0xFFFFFFFF){ + uint32_t mask_add = mask_total + ~bis_sync; + uint32_t mask_or = mask_total | ~bis_sync; + if (mask_add != mask_or){ + // not disjunct + return false; + } + mask_total = mask_add; + } + + // check if we can read metadata_length + if (pos >= buffer_size){ + log_info("Metadata length not specified, subgroup %u", i); + return false; + } + + uint8_t metadata_length = buffer[pos++]; + + if ((buffer_size - pos) < metadata_length){ + log_info("Metadata length %u exceedes remaining buffer %u", metadata_length, buffer_size - pos); + return false; + } + // metadata + pos += metadata_length; + } + + return (pos == buffer_size); +} + +bool bass_util_add_source_buffer_in_valid_range(uint8_t *buffer, uint16_t buffer_size){ + if (buffer_size < 15){ + log_info("Add Source opcode, buffer too small"); + return false; + } + + uint8_t pos = 0; + // addr type + uint8_t adv_type = buffer[pos++]; + if (adv_type > (uint8_t)BD_ADDR_TYPE_LE_RANDOM){ + log_info("Unexpected adv_type 0x%02X", adv_type); + return false; + } + + // address + pos += 6; + + // advertising_sid Range: 0x00-0x0F + uint8_t advertising_sid = buffer[pos++]; + if (advertising_sid > 0x0F){ + log_info("Advertising sid out of range 0x%02X", advertising_sid); + return false; + } + + // broadcast_id + pos += 3; + return bass_util_pa_sync_state_and_subgroups_in_valid_range(buffer+pos, buffer_size-pos); +} + +void bass_util_get_pa_info_and_subgroups_from_buffer(uint8_t *buffer, uint16_t buffer_size, bass_source_data_t * source_data){ + UNUSED(buffer_size); + uint8_t pos = 0; + source_data->pa_sync_state = (le_audio_pa_sync_state_t)buffer[pos++]; + source_data->pa_interval = little_endian_read_16(buffer, pos); + pos += 2; + + source_data->subgroups_num = buffer[pos++]; + uint8_t i; + for (i = 0; i < source_data->subgroups_num; i++){ + // bis_sync + source_data->subgroups[i].bis_sync_state = little_endian_read_32(buffer, pos); + pos += 4; + + uint8_t metadata_bytes_read = le_audio_metadata_parse_tlv(&buffer[pos], buffer_size - pos, &source_data->subgroups[i].metadata); + source_data->subgroups[i].metadata_length = metadata_bytes_read - 1; + pos += metadata_bytes_read; + } +} + +void bass_util_get_source_from_buffer(uint8_t *buffer, uint16_t buffer_size, bass_source_data_t * source_data){ + UNUSED(buffer_size); + uint8_t pos = 0; + + source_data->address_type = (bd_addr_type_t)buffer[pos++]; + + reverse_bd_addr(&buffer[pos], source_data->address); + pos += 6; + + source_data->adv_sid = buffer[pos++]; + + source_data->broadcast_id = little_endian_read_24(buffer, pos); + pos += 3; + + bass_util_get_pa_info_and_subgroups_from_buffer(buffer+pos, buffer_size-pos, source_data); +} \ No newline at end of file diff --git a/src/ble/gatt-service/broadcast_audio_scan_service_util.h b/src/ble/gatt-service/broadcast_audio_scan_service_util.h new file mode 100644 index 000000000..c49f42c0f --- /dev/null +++ b/src/ble/gatt-service/broadcast_audio_scan_service_util.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2022 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN + * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + + +#ifndef BROADCAST_AUDIO_SCAN_SERVICE_UTIL_H +#define BROADCAST_AUDIO_SCAN_SERVICE_UTIL_H + +#include +#include "le_audio.h" + +#if defined __cplusplus +extern "C" { +#endif + +#define BASS_ERROR_CODE_OPCODE_NOT_SUPPORTED 0x80 +#define BASS_ERROR_CODE_INVALID_SOURCE_ID 0x81 +#define BASS_SUBGROUPS_MAX_NUM 10 + +#define BASS_MAX_NOTIFY_BUFFER_SIZE 200 +#define BASS_INVALID_SOURCE_INDEX 0xFF + +/* API_START */ + +typedef enum { + BASS_OPCODE_REMOTE_SCAN_STOPPED = 0x00, + BASS_OPCODE_REMOTE_SCAN_STARTED, + BASS_OPCODE_ADD_SOURCE, + BASS_OPCODE_MODIFY_SOURCE, + BASS_OPCODE_SET_BROADCAST_CODE, + BASS_OPCODE_REMOVE_SOURCE, + BASS_OPCODE_RFU +} bass_opcode_t; + +typedef enum { + BASS_ROLE_CLIENT = 0, + BASS_ROLE_SERVER +} bass_role_t; + +typedef struct { + // 4-octet bitfield. Shall not exist if num_subgroups = 0 + // Bit 0-30 = BIS_index[1-31] + // 0x00000000: 0 = Not synchronized to BIS_index[x] + // 0xxxxxxxxx: 1 = Synchronized to BIS_index[x] + // 0xFFFFFFFF: Failed to sync to BIG + uint32_t bis_sync_state; + + uint8_t metadata_length; + le_audio_metadata_t metadata; +} bass_subgroup_t; + +typedef struct { + // assigned by client via control point + bd_addr_type_t address_type; + bd_addr_t address; + uint8_t adv_sid; + uint32_t broadcast_id; + le_audio_pa_sync_state_t pa_sync_state; + le_audio_pa_sync_t pa_sync; + uint16_t pa_interval; + + uint8_t subgroups_num; + // Shall not exist if num_subgroups = 0 + bass_subgroup_t subgroups[BASS_SUBGROUPS_MAX_NUM]; +} bass_source_data_t; + +// offset gives position into fully serialized BASS record +uint16_t bass_util_copy_source_common_data_to_buffer(bass_source_data_t * data, uint16_t *source_offset, uint16_t buffer_offset, uint8_t * buffer, uint16_t buffer_size); + +uint16_t bass_util_copy_source_metadata_to_buffer(bass_source_data_t * data, uint16_t *source_offset, uint16_t buffer_offset, uint8_t * buffer, uint16_t buffer_size); + +bool bass_util_pa_sync_state_and_subgroups_in_valid_range(uint8_t *buffer, uint16_t buffer_size); + +bool bass_util_add_source_buffer_in_valid_range(uint8_t *buffer, uint16_t buffer_size); + +void bass_util_get_source_from_buffer(uint8_t *buffer, uint16_t buffer_size, bass_source_data_t * source_data); + +void bass_util_get_pa_info_and_subgroups_from_buffer(uint8_t *buffer, uint16_t buffer_size, bass_source_data_t * source_data); + +/* API_END */ + +#if defined __cplusplus +} +#endif + +#endif + diff --git a/src/btstack.h b/src/btstack.h index 2af73445b..c96cadd5c 100644 --- a/src/btstack.h +++ b/src/btstack.h @@ -87,6 +87,8 @@ #include "ble/gatt-service/battery_service_client.h" #include "ble/gatt-service/battery_service_server.h" #include "ble/gatt-service/bond_management_service_server.h" +#include "ble/gatt-service/broadcast_audio_scan_service_client.h" +#include "ble/gatt-service/broadcast_audio_scan_service_server.h" #include "ble/gatt-service/cycling_power_service_server.h" #include "ble/gatt-service/cycling_speed_and_cadence_service_server.h" #include "ble/gatt-service/device_information_service_client.h"