gatt-service: add broadcast_audio_scan_service_[server|client|util]

This commit is contained in:
Matthias Ringwald 2022-09-20 11:28:03 +02:00
parent 841e7c8278
commit b661adb482
10 changed files with 2214 additions and 0 deletions

View File

@ -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

View File

@ -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 \

View File

@ -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,

View File

@ -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 <stdio.h>
#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);
}

View File

@ -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 <stdint.h>
#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

View File

@ -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 <stdio.h>
#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 <stdio.h>
#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){
}

View File

@ -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 <broadcast_audio_scan_service.gatt>` to your .gatt file.
*/
#ifndef BROADCAST_AUDIO_SCAN_SERVICE_SERVER_H
#define BROADCAST_AUDIO_SCAN_SERVICE_SERVER_H
#include <stdint.h>
#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

View File

@ -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 <stdio.h>
#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 <stdio.h>
#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);
}

View File

@ -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 <stdint.h>
#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

View File

@ -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"