/* * 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 "le-audio/le_audio_util.h" #include "le-audio/gatt-service/broadcast_audio_scan_service_client.h" #ifdef ENABLE_TESTING_SUPPORT #include #endif static btstack_linked_list_t bass_client_connections; static uint16_t bass_client_cid_counter = 0; static btstack_packet_handler_t bass_client_event_callback; static void bass_client_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_client_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_client_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_client_get_connection_for_cid(uint16_t bass_cid){ btstack_linked_list_iterator_t it; btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &bass_client_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_client_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_client_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){ btstack_assert(bass_client_event_callback != NULL); uint8_t event[8]; uint16_t pos = 0; event[pos++] = HCI_EVENT_LEAUDIO_META; event[pos++] = sizeof(event) - 2; event[pos++] = LEAUDIO_SUBEVENT_BASS_CLIENT_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_client_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); } static void bass_client_connected(bass_client_connection_t *connection, uint8_t status) { if (status == ERROR_CODE_SUCCESS){ connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY; bass_client_emit_connection_established(connection, status); } else { connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_IDLE; bass_client_emit_connection_established(connection, status); bass_client_finalize_connection(connection); } } static void bass_client_emit_scan_operation_complete(bass_client_connection_t * connection, uint8_t status, bass_opcode_t opcode){ btstack_assert(bass_client_event_callback != NULL); uint8_t event[7]; uint16_t pos = 0; event[pos++] = HCI_EVENT_LEAUDIO_META; event[pos++] = sizeof(event) - 2; event[pos++] = LEAUDIO_SUBEVENT_BASS_CLIENT_SCAN_OPERATION_COMPLETE; little_endian_store_16(event, pos, connection->cid); pos += 2; event[pos++] = status; event[pos++] = (uint8_t)opcode; (*bass_client_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){ btstack_assert(bass_client_event_callback != NULL); uint8_t event[8]; uint16_t pos = 0; event[pos++] = HCI_EVENT_LEAUDIO_META; event[pos++] = sizeof(event) - 2; event[pos++] = LEAUDIO_SUBEVENT_BASS_CLIENT_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_client_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){ btstack_assert(bass_client_event_callback != NULL); uint8_t pos = 0; uint8_t event[7]; event[pos++] = HCI_EVENT_LEAUDIO_META; event[pos++] = sizeof(event) - 2; event[pos++] = LEAUDIO_SUBEVENT_BASS_CLIENT_NOTIFICATION_COMPLETE; little_endian_store_16(event, pos, connection->cid); pos += 2; event[pos++] = source_id; (*bass_client_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){ // minimal with zero subgroups if (buffer_size < 15){ return false; } uint16_t pos = 0; // source_id pos++; // 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; // 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; } // big_encryption le_audio_big_encryption_t big_encryption = (le_audio_big_encryption_t) buffer[pos++]; if (big_encryption == LE_AUDIO_BIG_ENCRYPTION_BAD_CODE){ // Bad Code pos += 16; } // num subgroups 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; } uint8_t i; for (i = 0; i < num_subgroups; i++) { // check if we can read bis_sync_state + meta_data_length // bis_sync_state pos += 4; // meta_data_length if (pos >= buffer_size){ return false; } uint8_t metadata_length = buffer[pos++]; if ((pos + metadata_length) > buffer_size){ return false; } // metadata pos += metadata_length; } return true; } static void bass_client_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(gatt_event_notification_get_handle(packet)); 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_client_get_receive_state_for_value_handle(connection, value_handle); if (source == NULL){ return; } source->source_id = value[0]; bass_util_source_data_parse(&value[1], value_length - 1, &source->data, true); // get big encryption + bad code source->big_encryption = (le_audio_big_encryption_t) value[13]; if (source->big_encryption == LE_AUDIO_BIG_ENCRYPTION_BAD_CODE){ reverse_128(&value[14], source->bad_code); } else { memset(source->bad_code, 0, 16); } 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 = receive_state->receive_state_end_handle; uint8_t status = gatt_client_write_client_characteristic_configuration( &bass_client_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, &bass_client_handle_gatt_server_notification, connection->con_handle, &characteristic); } return status; } static uint16_t bass_client_prepare_add_source_buffer(bass_client_connection_t * connection){ const bass_source_data_t * receive_state = connection->control_point_operation_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_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(receive_state, &source_offset, buffer_offset, buffer, buffer_size); field_data[0] = (uint8_t)receive_state->pa_sync; stored_bytes += le_audio_util_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_util_virtual_memcpy_helper(field_data, 2, source_offset, buffer, buffer_size, buffer_offset); source_offset += 2; stored_bytes += bass_util_source_data_subgroups_virtual_memcpy(receive_state, false, &source_offset, buffer_offset, buffer, buffer_size); return stored_bytes; } static uint16_t bass_client_prepare_modify_source_buffer(bass_client_connection_t * connection){ const bass_source_data_t * receive_state = connection->control_point_operation_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_util_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset); source_offset++; field_data[0] = connection->control_point_operation_source_id; 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)receive_state->pa_sync; stored_bytes += le_audio_util_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_util_virtual_memcpy_helper(field_data, 2, source_offset, buffer, buffer_size, buffer_offset); source_offset += 2; stored_bytes += bass_util_source_data_subgroups_virtual_memcpy(receive_state, false, &source_offset, buffer_offset, buffer, buffer_size); return stored_bytes; } static uint16_t bass_client_receive_state_len(const 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_READY: 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( &bass_client_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( &bass_client_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; status = gatt_client_write_long_value_of_characteristic(&bass_client_handle_gatt_client_event, connection->con_handle, connection->control_point_value_handle, stored_bytes, connection->buffer); UNUSED(status); break; case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_MODIFY_SOURCE: #ifdef ENABLE_TESTING_SUPPORT printf(" MODIFY SOURCE [%d]:\n", connection->control_point_operation_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; status = gatt_client_write_long_value_of_characteristic(&bass_client_handle_gatt_client_event, connection->con_handle, connection->control_point_value_handle, stored_bytes, connection->buffer); UNUSED(status); break; case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_REMOVE_SOURCE: #ifdef ENABLE_TESTING_SUPPORT printf(" REMOVE SOURCE [%d]:\n", connection->control_point_operation_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->control_point_operation_source_id; // see GATT_EVENT_QUERY_COMPLETE for end of write status = gatt_client_write_value_of_characteristic( &bass_client_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->control_point_operation_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->control_point_operation_source_id; reverse_128(connection->broadcast_code, &value[2]); // see GATT_EVENT_QUERY_COMPLETE for end of write status = gatt_client_write_value_of_characteristic( &bass_client_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(&bass_client_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; status = gatt_client_discover_characteristics_for_service( &bass_client_handle_gatt_client_event, connection->con_handle, &service); UNUSED(status); 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->receive_states[connection->receive_states_index].receive_state_end_handle; (void) gatt_client_discover_characteristic_descriptors(&bass_client_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( &bass_client_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; } bass_client_connected(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: switch (status){ case ATT_ERROR_SUCCESS: break; case ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LENGTH: bass_client_connected(connection, ERROR_CODE_PARAMETER_OUT_OF_MANDATORY_RANGE); return false; default: bass_client_connected(connection, ERROR_CODE_UNSPECIFIED_ERROR); return false; } if (connection->service_instances_num == 0){ bass_client_connected(connection, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE); 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_connected(connection, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE); 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; } bass_client_connected(connection, ERROR_CODE_SUCCESS); break; case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT: bass_client_connected(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_READY; 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_READY; 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_READY; 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_READY; bass_client_emit_source_operation_complete(connection, status, BASS_OPCODE_MODIFY_SOURCE, connection->control_point_operation_source_id); break; case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_REMOVE_SOURCE: connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY; bass_client_reset_source( bass_client_get_source_for_source_id(connection, connection->control_point_operation_source_id)); bass_client_emit_source_operation_complete(connection, status, BASS_OPCODE_REMOVE_SOURCE, connection->control_point_operation_source_id); break; case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_SET_BROADCAST_CODE: connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY; bass_client_emit_source_operation_complete(connection, status, BASS_OPCODE_SET_BROADCAST_CODE, connection->control_point_operation_source_id); break; case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY: break; default: break; } return true; } static void bass_client_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; connection->receive_states[connection->receive_states_index].receive_state_end_handle = characteristic.end_handle; connection->receive_states_instances_num++; } 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_client_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_client_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_client_get_connection_for_cid(bass_cid); if (connection == NULL){ return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; } if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY){ 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_client_get_connection_for_cid(bass_cid); if (connection == NULL){ return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; } if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY){ 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, const bass_source_data_t * add_source_data){ bass_client_connection_t * connection = bass_client_get_connection_for_cid(bass_cid); if (connection == NULL){ return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; } if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY){ return ERROR_CODE_COMMAND_DISALLOWED; } connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_ADD_SOURCE; connection->control_point_operation_data = add_source_data; connection->buffer_offset = 0; connection->data_size = bass_client_receive_state_len(add_source_data); 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, const bass_source_data_t * modify_source_data){ bass_client_connection_t * connection = bass_client_get_connection_for_cid(bass_cid); if (connection == NULL){ return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; } if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY){ return ERROR_CODE_COMMAND_DISALLOWED; } connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_MODIFY_SOURCE; connection->control_point_operation_data = modify_source_data; connection->control_point_operation_source_id = source_id; connection->buffer_offset = 0; connection->data_size = bass_client_receive_state_len(modify_source_data); bass_client_run_for_connection(connection); return ERROR_CODE_SUCCESS; } uint8_t broadcast_audio_scan_service_client_remove_source(uint16_t bass_cid, uint8_t source_id){ bass_client_connection_t * connection = bass_client_get_connection_for_cid(bass_cid); if (connection == NULL){ return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; } if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY){ return ERROR_CODE_COMMAND_DISALLOWED; } connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_REMOVE_SOURCE; connection->control_point_operation_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, const uint8_t * broadcast_code){ bass_client_connection_t * connection = bass_client_get_connection_for_cid(bass_cid); if (connection == NULL){ return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; } if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY){ return ERROR_CODE_COMMAND_DISALLOWED; } connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_SET_BROADCAST_CODE; connection->control_point_operation_source_id = source_id; connection->broadcast_code = broadcast_code; bass_client_run_for_connection(connection); return ERROR_CODE_SUCCESS; } const bass_source_data_t * broadcast_audio_scan_service_client_get_source_data(uint16_t bass_cid, uint8_t source_id){ bass_client_connection_t * connection = bass_client_get_connection_for_cid(bass_cid); if (connection == NULL){ return NULL; } if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY){ return NULL; } return (const bass_source_data_t *) &bass_client_get_source_for_source_id(connection, source_id)->data; } uint8_t broadcast_audio_scan_service_client_get_encryption_state(uint16_t bass_cid, uint8_t source_id, le_audio_big_encryption_t * out_big_encryption, uint8_t * out_bad_code){ btstack_assert(out_big_encryption != NULL); btstack_assert(out_bad_code != NULL); bass_client_connection_t * connection = bass_client_get_connection_for_cid(bass_cid); if (connection == NULL){ return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; } if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY){ return ERROR_CODE_COMMAND_DISALLOWED; } bass_client_source_t * source = bass_client_get_source_for_source_id(connection, source_id); if (source == NULL){ return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; } *out_big_encryption = source->big_encryption; memcpy(out_bad_code, source->bad_code, 16); return ERROR_CODE_SUCCESS; } void broadcast_audio_scan_service_client_deinit(uint16_t bass_cid){ bass_client_event_callback = NULL; bass_client_connection_t * connection = bass_client_get_connection_for_cid(bass_cid); if (connection == NULL){ return; } // finalize connections bass_client_finalize_connection(connection); }