/* * 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 "le-audio/gatt-service/broadcast_audio_scan_service_server.h" #include "le-audio/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->data.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->data.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, const 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_128(broadcast_code, &event[pos]); 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_util_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset); source_offset++; stored_bytes += bass_util_source_data_header_virtual_memcpy(&source->data, &source_offset, buffer_offset, buffer, buffer_size); field_data[0] = (uint8_t)source->data.pa_sync_state; stored_bytes += le_audio_util_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_util_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_util_virtual_memcpy_helper(field_data, 16, source_offset, buffer, buffer_size, buffer_offset); source_offset += 16; } stored_bytes += bass_util_source_data_subgroups_virtual_memcpy(&source->data, true, &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_source_data_parse(buffer, buffer_size, &source->data, false); } 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->data.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; uint8_t broadcast_code[16]; 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_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_pa_info_and_subgroups_parse(remote_data + 1, remote_data_size - 1, &source->data, false); 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; } reverse_128(&remote_data[1], broadcast_code); bass_emit_broadcast_code(con_handle, source->source_id, broadcast_code); 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; bool 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); 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){ }