1 /* 2 * Copyright (C) 2022 BlueKitchen GmbH 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of the copyright holders nor the names of 14 * contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 4. Any redistribution, use, or modification is done solely for 17 * personal benefit and not for any commercial purpose or for 18 * monetary gain. 19 * 20 * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS 21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN 24 * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 27 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 28 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 30 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 * 33 * Please inquire about commercial licensing options at 34 * [email protected] 35 * 36 */ 37 38 #define BTSTACK_FILE__ "broadcast_audio_scan_service_client.c" 39 40 #include "ble/att_db.h" 41 #include "ble/att_server.h" 42 #include "bluetooth_gatt.h" 43 #include "btstack_debug.h" 44 #include "btstack_defines.h" 45 #include "btstack_event.h" 46 #include "btstack_util.h" 47 #include "btstack_memory.h" 48 49 #include "le-audio/le_audio_util.h" 50 #include "le-audio/gatt-service/broadcast_audio_scan_service_client.h" 51 52 #ifdef ENABLE_TESTING_SUPPORT 53 #include <stdio.h> 54 #endif 55 56 static btstack_linked_list_t bass_client_connections; 57 58 static uint16_t bass_client_cid_counter = 0; 59 static btstack_packet_handler_t bass_client_event_callback; 60 61 static void bass_client_handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); 62 63 static uint16_t bass_client_get_next_cid(void){ 64 bass_client_cid_counter = btstack_next_cid_ignoring_zero(bass_client_cid_counter); 65 return bass_client_cid_counter; 66 } 67 68 static void bass_client_finalize_connection(bass_client_connection_t * connection){ 69 btstack_linked_list_remove(&bass_client_connections, (btstack_linked_item_t*) connection); 70 } 71 72 static bass_client_connection_t * bass_client_get_connection_for_con_handle(hci_con_handle_t con_handle){ 73 btstack_linked_list_iterator_t it; 74 btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &bass_client_connections); 75 while (btstack_linked_list_iterator_has_next(&it)){ 76 bass_client_connection_t * connection = (bass_client_connection_t *)btstack_linked_list_iterator_next(&it); 77 if (connection->con_handle != con_handle) continue; 78 return connection; 79 } 80 return NULL; 81 } 82 83 static bass_client_connection_t * bass_client_get_connection_for_cid(uint16_t bass_cid){ 84 btstack_linked_list_iterator_t it; 85 btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &bass_client_connections); 86 while (btstack_linked_list_iterator_has_next(&it)){ 87 bass_client_connection_t * connection = (bass_client_connection_t *)btstack_linked_list_iterator_next(&it); 88 if (connection->cid != bass_cid) continue; 89 return connection; 90 } 91 return NULL; 92 } 93 94 static bass_client_source_t * bass_client_get_receive_state_for_value_handle(bass_client_connection_t * connection, uint16_t value_handle){ 95 uint8_t i; 96 for (i = 0; i < connection->receive_states_instances_num; i++){ 97 if (connection->receive_states[i].receive_state_value_handle == value_handle){ 98 return &connection->receive_states[i]; 99 } 100 } 101 return NULL; 102 } 103 104 static bass_client_source_t * bass_client_get_source_for_source_id(bass_client_connection_t * connection, uint8_t source_id){ 105 uint8_t i; 106 for (i = 0; i < connection->receive_states_instances_num; i++){ 107 if (connection->receive_states[i].source_id == source_id){ 108 return &connection->receive_states[i]; 109 } 110 } 111 return NULL; 112 } 113 114 static void bass_client_reset_source(bass_client_source_t * source){ 115 if (source == NULL){ 116 return; 117 } 118 source->source_id = BASS_INVALID_SOURCE_INDEX; 119 source->in_use = false; 120 memset(&source->data, 0, sizeof(bass_source_data_t)); 121 } 122 123 static void bass_client_emit_connection_established(bass_client_connection_t * connection, uint8_t status){ 124 btstack_assert(bass_client_event_callback != NULL); 125 126 uint8_t event[8]; 127 uint16_t pos = 0; 128 event[pos++] = HCI_EVENT_LEAUDIO_META; 129 event[pos++] = sizeof(event) - 2; 130 event[pos++] = LEAUDIO_SUBEVENT_BASS_CLIENT_CONNECTED; 131 little_endian_store_16(event, pos, connection->con_handle); 132 pos += 2; 133 little_endian_store_16(event, pos, connection->cid); 134 pos += 2; 135 event[pos++] = status; 136 (*bass_client_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); 137 } 138 139 static void bass_client_connected(bass_client_connection_t *connection, uint8_t status) { 140 if (status == ERROR_CODE_SUCCESS){ 141 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY; 142 bass_client_emit_connection_established(connection, status); 143 } else { 144 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_IDLE; 145 bass_client_emit_connection_established(connection, status); 146 bass_client_finalize_connection(connection); 147 } 148 } 149 150 static void bass_client_emit_scan_operation_complete(bass_client_connection_t * connection, uint8_t status, bass_opcode_t opcode){ 151 btstack_assert(bass_client_event_callback != NULL); 152 uint8_t event[7]; 153 uint16_t pos = 0; 154 event[pos++] = HCI_EVENT_LEAUDIO_META; 155 event[pos++] = sizeof(event) - 2; 156 event[pos++] = LEAUDIO_SUBEVENT_BASS_CLIENT_SCAN_OPERATION_COMPLETE; 157 little_endian_store_16(event, pos, connection->cid); 158 pos += 2; 159 event[pos++] = status; 160 event[pos++] = (uint8_t)opcode; 161 (*bass_client_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); 162 } 163 164 static void bass_client_emit_source_operation_complete(bass_client_connection_t * connection, uint8_t status, bass_opcode_t opcode, uint8_t source_id){ 165 btstack_assert(bass_client_event_callback != NULL); 166 uint8_t event[8]; 167 uint16_t pos = 0; 168 event[pos++] = HCI_EVENT_LEAUDIO_META; 169 event[pos++] = sizeof(event) - 2; 170 event[pos++] = LEAUDIO_SUBEVENT_BASS_CLIENT_SOURCE_OPERATION_COMPLETE; 171 little_endian_store_16(event, pos, connection->cid); 172 pos += 2; 173 event[pos++] = status; 174 event[pos++] = (uint8_t)opcode; 175 event[pos++] = source_id; 176 (*bass_client_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); 177 } 178 179 static void bass_client_emit_receive_state(bass_client_connection_t * connection, uint8_t source_id){ 180 btstack_assert(bass_client_event_callback != NULL); 181 uint8_t pos = 0; 182 uint8_t event[7]; 183 event[pos++] = HCI_EVENT_LEAUDIO_META; 184 event[pos++] = sizeof(event) - 2; 185 event[pos++] = LEAUDIO_SUBEVENT_BASS_CLIENT_NOTIFICATION_COMPLETE; 186 little_endian_store_16(event, pos, connection->cid); 187 pos += 2; 188 event[pos++] = source_id; 189 (*bass_client_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); 190 } 191 192 static bool bass_client_remote_broadcast_receive_state_buffer_valid(uint8_t *buffer, uint16_t buffer_size){ 193 // minimal with zero subgroups 194 if (buffer_size < 15){ 195 return false; 196 } 197 198 uint16_t pos = 0; 199 200 // source_id 201 pos++; 202 203 // addr type 204 uint8_t adv_type = buffer[pos++]; 205 if (adv_type > (uint8_t)BD_ADDR_TYPE_LE_RANDOM){ 206 log_info("Unexpected adv_type 0x%02X", adv_type); 207 return false; 208 } 209 210 // address 211 pos += 6; 212 213 // advertising_sid Range: 0x00-0x0F 214 uint8_t advertising_sid = buffer[pos++]; 215 if (advertising_sid > 0x0F){ 216 log_info("Advertising sid out of range 0x%02X", advertising_sid); 217 return false; 218 } 219 220 // broadcast_id 221 pos += 3; 222 223 // pa_sync_state 224 uint8_t pa_sync_state = buffer[pos++]; 225 if (pa_sync_state >= (uint8_t)LE_AUDIO_PA_SYNC_STATE_RFU){ 226 log_info("Unexpected pa_sync_state 0x%02X", pa_sync_state); 227 return false; 228 } 229 230 // big_encryption 231 le_audio_big_encryption_t big_encryption = (le_audio_big_encryption_t) buffer[pos++]; 232 if (big_encryption == LE_AUDIO_BIG_ENCRYPTION_BAD_CODE){ 233 // Bad Code 234 pos += 16; 235 } 236 237 // num subgroups 238 uint8_t num_subgroups = buffer[pos++]; 239 if (num_subgroups > BASS_SUBGROUPS_MAX_NUM){ 240 log_info("Number of subgroups %u exceedes maximum %u", num_subgroups, BASS_SUBGROUPS_MAX_NUM); 241 return false; 242 } 243 244 uint8_t i; 245 for (i = 0; i < num_subgroups; i++) { 246 // check if we can read bis_sync_state + meta_data_length 247 // bis_sync_state 248 pos += 4; 249 // meta_data_length 250 if (pos >= buffer_size){ 251 return false; 252 } 253 uint8_t metadata_length = buffer[pos++]; 254 if ((pos + metadata_length) > buffer_size){ 255 return false; 256 } 257 // metadata 258 pos += metadata_length; 259 } 260 return true; 261 } 262 263 static void bass_client_handle_gatt_server_notification(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ 264 UNUSED(packet_type); 265 UNUSED(channel); 266 UNUSED(size); 267 268 if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION){ 269 return; 270 } 271 bass_client_connection_t * connection = bass_client_get_connection_for_con_handle(gatt_event_notification_get_handle(packet)); 272 if (connection == NULL){ 273 return; 274 } 275 276 uint16_t value_handle = gatt_event_notification_get_value_handle(packet); 277 uint16_t value_length = gatt_event_notification_get_value_length(packet); 278 uint8_t * value = (uint8_t *)gatt_event_notification_get_value(packet); 279 280 if (bass_client_remote_broadcast_receive_state_buffer_valid(value, value_length)){ 281 bass_client_source_t * source = bass_client_get_receive_state_for_value_handle(connection, value_handle); 282 if (source == NULL){ 283 return; 284 } 285 source->source_id = value[0]; 286 bass_util_source_data_parse(&value[1], value_length - 1, &source->data, true); 287 288 // get big encryption + bad code 289 source->big_encryption = (le_audio_big_encryption_t) value[13]; 290 if (source->big_encryption == LE_AUDIO_BIG_ENCRYPTION_BAD_CODE){ 291 reverse_128(&value[14], source->bad_code); 292 } else { 293 memset(source->bad_code, 0, 16); 294 } 295 296 bass_client_emit_receive_state(connection, source->source_id); 297 } 298 } 299 300 static bool bass_client_register_notification(bass_client_connection_t * connection){ 301 bass_client_source_t * receive_state = &connection->receive_states[connection->receive_states_index]; 302 if (receive_state == NULL){ 303 return false; 304 } 305 306 gatt_client_characteristic_t characteristic; 307 characteristic.value_handle = receive_state->receive_state_value_handle; 308 characteristic.properties = receive_state->receive_state_properties; 309 characteristic.end_handle = receive_state->receive_state_end_handle; 310 311 uint8_t status = gatt_client_write_client_characteristic_configuration( 312 &bass_client_handle_gatt_client_event, 313 connection->con_handle, 314 &characteristic, 315 GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION); 316 317 // notification supported, register for value updates 318 if (status == ERROR_CODE_SUCCESS){ 319 gatt_client_listen_for_characteristic_value_updates( 320 &connection->notification_listener, 321 &bass_client_handle_gatt_server_notification, 322 connection->con_handle, &characteristic); 323 } 324 return status; 325 } 326 327 static uint16_t bass_client_prepare_add_source_buffer(bass_client_connection_t * connection){ 328 const bass_source_data_t * receive_state = connection->control_point_operation_data; 329 330 uint16_t buffer_offset = connection->buffer_offset; 331 uint8_t * buffer = connection->buffer; 332 uint16_t buffer_size = btstack_min(sizeof(connection->buffer), connection->mtu); 333 334 btstack_assert(buffer_offset == 0); 335 336 uint8_t field_data[6]; 337 uint16_t source_offset = 0; 338 uint16_t stored_bytes = 0; 339 memset(buffer, 0, buffer_size); 340 341 field_data[0] = (uint8_t)BASS_OPCODE_ADD_SOURCE; 342 stored_bytes += le_audio_util_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, 343 buffer_offset); 344 source_offset++; 345 346 stored_bytes += bass_util_source_data_header_virtual_memcpy(receive_state, &source_offset, buffer_offset, buffer, 347 buffer_size); 348 349 field_data[0] = (uint8_t)receive_state->pa_sync; 350 stored_bytes += le_audio_util_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, 351 buffer_offset); 352 source_offset++; 353 354 little_endian_store_16(field_data, 0, receive_state->pa_interval); 355 stored_bytes += le_audio_util_virtual_memcpy_helper(field_data, 2, source_offset, buffer, buffer_size, 356 buffer_offset); 357 source_offset += 2; 358 359 stored_bytes += bass_util_source_data_subgroups_virtual_memcpy(receive_state, false, &source_offset, buffer_offset, 360 buffer, 361 buffer_size); 362 363 return stored_bytes; 364 } 365 366 static uint16_t bass_client_prepare_modify_source_buffer(bass_client_connection_t * connection){ 367 const bass_source_data_t * receive_state = connection->control_point_operation_data; 368 369 uint16_t buffer_offset = connection->buffer_offset; 370 uint8_t * buffer = connection->buffer; 371 uint16_t buffer_size = btstack_min(sizeof(connection->buffer), connection->mtu); 372 373 btstack_assert(buffer_offset == 0); 374 375 uint8_t field_data[6]; 376 uint16_t source_offset = 0; 377 uint16_t stored_bytes = 0; 378 memset(buffer, 0, buffer_size); 379 380 field_data[0] = (uint8_t)BASS_OPCODE_MODIFY_SOURCE; 381 stored_bytes += le_audio_util_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, 382 buffer_offset); 383 source_offset++; 384 385 field_data[0] = connection->control_point_operation_source_id; 386 stored_bytes += le_audio_util_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, 387 buffer_offset); 388 source_offset++; 389 390 field_data[0] = (uint8_t)receive_state->pa_sync; 391 stored_bytes += le_audio_util_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, 392 buffer_offset); 393 source_offset++; 394 395 little_endian_store_16(field_data, 0, receive_state->pa_interval); 396 stored_bytes += le_audio_util_virtual_memcpy_helper(field_data, 2, source_offset, buffer, buffer_size, 397 buffer_offset); 398 source_offset += 2; 399 400 stored_bytes += bass_util_source_data_subgroups_virtual_memcpy(receive_state, false, &source_offset, buffer_offset, 401 buffer, 402 buffer_size); 403 return stored_bytes; 404 } 405 406 static uint16_t bass_client_receive_state_len(const bass_source_data_t * source_data){ 407 uint16_t source_len = 0; 408 // opcode(1), address_type(1), address(6), adv_sid(1), broadcast_id(3), pa_sync(1), subgroups_num(1) 409 source_len = 1 + 1 + 6 + 1 + 3 + 1 + 1; 410 411 uint8_t i; 412 for (i = 0; i < source_data->subgroups_num; i++){ 413 bass_subgroup_t subgroup = source_data->subgroups[i]; 414 // bis_sync(4), metadata_length(1), metadata_length 415 source_len += 4 + 1 + subgroup.metadata_length; 416 } 417 return source_len; 418 } 419 420 static void bass_client_run_for_connection(bass_client_connection_t * connection){ 421 uint8_t status; 422 gatt_client_characteristic_t characteristic; 423 gatt_client_service_t service; 424 425 uint8_t value[18]; 426 uint16_t stored_bytes; 427 428 switch (connection->state){ 429 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY: 430 break; 431 432 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_START_SCAN: 433 #ifdef ENABLE_TESTING_SUPPORT 434 printf(" Write START SCAN [0x%04X]:\n", 435 connection->control_point_value_handle); 436 #endif 437 438 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_START_SCAN; 439 value[0] = BASS_OPCODE_REMOTE_SCAN_STARTED; 440 // see GATT_EVENT_QUERY_COMPLETE for end of write 441 status = gatt_client_write_value_of_characteristic( 442 &bass_client_handle_gatt_client_event, connection->con_handle, 443 connection->control_point_value_handle, 1, &value[0]); 444 UNUSED(status); 445 break; 446 447 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_STOP_SCAN: 448 #ifdef ENABLE_TESTING_SUPPORT 449 printf(" Write START SCAN [0x%04X]:\n", 450 connection->control_point_value_handle); 451 #endif 452 453 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_STOP_SCAN; 454 value[0] = BASS_OPCODE_REMOTE_SCAN_STOPPED; 455 // see GATT_EVENT_QUERY_COMPLETE for end of write 456 status = gatt_client_write_value_of_characteristic( 457 &bass_client_handle_gatt_client_event, connection->con_handle, 458 connection->control_point_value_handle, 1, &value[0]); 459 UNUSED(status); 460 break; 461 462 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_ADD_SOURCE: 463 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_ADD_SOURCE; 464 #ifdef ENABLE_TESTING_SUPPORT 465 printf(" ADD SOURCE [0x%04X]:\n", connection->control_point_value_handle); 466 #endif 467 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_ADD_SOURCE; 468 stored_bytes = bass_client_prepare_add_source_buffer(connection); 469 connection->buffer_offset += stored_bytes; 470 471 status = gatt_client_write_long_value_of_characteristic(&bass_client_handle_gatt_client_event, connection->con_handle, 472 connection->control_point_value_handle, stored_bytes, connection->buffer); 473 UNUSED(status); 474 break; 475 476 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_MODIFY_SOURCE: 477 #ifdef ENABLE_TESTING_SUPPORT 478 printf(" MODIFY SOURCE [%d]:\n", connection->control_point_operation_source_id); 479 #endif 480 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_MODIFY_SOURCE; 481 stored_bytes = bass_client_prepare_modify_source_buffer(connection); 482 connection->buffer_offset += stored_bytes; 483 484 status = gatt_client_write_long_value_of_characteristic(&bass_client_handle_gatt_client_event, connection->con_handle, 485 connection->control_point_value_handle, stored_bytes, connection->buffer); 486 UNUSED(status); 487 break; 488 489 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_REMOVE_SOURCE: 490 #ifdef ENABLE_TESTING_SUPPORT 491 printf(" REMOVE SOURCE [%d]:\n", connection->control_point_operation_source_id); 492 #endif 493 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_REMOVE_SOURCE; 494 value[0] = BASS_OPCODE_REMOVE_SOURCE; 495 value[1] = connection->control_point_operation_source_id; 496 // see GATT_EVENT_QUERY_COMPLETE for end of write 497 status = gatt_client_write_value_of_characteristic( 498 &bass_client_handle_gatt_client_event, connection->con_handle, 499 connection->control_point_value_handle, 2, &value[0]); 500 UNUSED(status); 501 break; 502 503 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_SET_BROADCAST_CODE: 504 #ifdef ENABLE_TESTING_SUPPORT 505 printf(" SET BROADCAST CODE [%d]:\n", connection->control_point_operation_source_id); 506 #endif 507 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_SET_BROADCAST_CODE; 508 value[0] = BASS_OPCODE_SET_BROADCAST_CODE; 509 value[1] = connection->control_point_operation_source_id; 510 reverse_128(connection->broadcast_code, &value[2]); 511 512 // see GATT_EVENT_QUERY_COMPLETE for end of write 513 status = gatt_client_write_value_of_characteristic( 514 &bass_client_handle_gatt_client_event, connection->con_handle, 515 connection->control_point_value_handle, 18, &value[0]); 516 UNUSED(status); 517 break; 518 519 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_SERVICE: 520 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_SERVICE_RESULT; 521 gatt_client_discover_primary_services_by_uuid16(&bass_client_handle_gatt_client_event, connection->con_handle, ORG_BLUETOOTH_SERVICE_BROADCAST_AUDIO_SCAN_SERVICE); 522 break; 523 524 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTICS: 525 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_RESULT; 526 527 service.start_group_handle = connection->start_handle; 528 service.end_group_handle = connection->end_handle; 529 service.uuid16 = ORG_BLUETOOTH_SERVICE_BROADCAST_AUDIO_SCAN_SERVICE; 530 531 status = gatt_client_discover_characteristics_for_service( 532 &bass_client_handle_gatt_client_event, 533 connection->con_handle, 534 &service); 535 UNUSED(status); 536 break; 537 538 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTIC_DESCRIPTORS: 539 #ifdef ENABLE_TESTING_SUPPORT 540 printf("Read client characteristic descriptors [index %d]:\n", connection->receive_states_index); 541 #endif 542 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_DESCRIPTORS_RESULT; 543 characteristic.value_handle = connection->receive_states[connection->receive_states_index].receive_state_value_handle; 544 characteristic.properties = connection->receive_states[connection->receive_states_index].receive_state_properties; 545 characteristic.end_handle = connection->receive_states[connection->receive_states_index].receive_state_end_handle; 546 547 (void) gatt_client_discover_characteristic_descriptors(&bass_client_handle_gatt_client_event, connection->con_handle, &characteristic); 548 break; 549 550 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_READ_CHARACTERISTIC_CONFIGURATION: 551 #ifdef ENABLE_TESTING_SUPPORT 552 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); 553 #endif 554 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT; 555 556 // result in GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT 557 (void) gatt_client_read_characteristic_descriptor_using_descriptor_handle( 558 &bass_client_handle_gatt_client_event, 559 connection->con_handle, 560 connection->receive_states[connection->receive_states_index].receive_state_ccc_handle); 561 break; 562 563 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION: 564 #ifdef ENABLE_TESTING_SUPPORT 565 printf("Register notification [index %d, handle 0x%04X]:\n", connection->receive_states_index, connection->receive_states[connection->receive_states_index].receive_state_ccc_handle); 566 #endif 567 568 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_NOTIFICATION_REGISTERED; 569 570 status = bass_client_register_notification(connection); 571 if (status == ERROR_CODE_SUCCESS) return; 572 573 if (connection->receive_states[connection->receive_states_index].receive_state_ccc_handle != 0){ 574 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_READ_CHARACTERISTIC_CONFIGURATION; 575 break; 576 } 577 578 bass_client_connected(connection, ERROR_CODE_SUCCESS); 579 break; 580 default: 581 break; 582 } 583 } 584 // @return true if client valid / run function should be called 585 static bool bass_client_handle_query_complete(bass_client_connection_t * connection, uint8_t status){ 586 switch (connection->state){ 587 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_SERVICE_RESULT: 588 switch (status){ 589 case ATT_ERROR_SUCCESS: 590 break; 591 case ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LENGTH: 592 bass_client_connected(connection, ERROR_CODE_PARAMETER_OUT_OF_MANDATORY_RANGE); 593 return false; 594 default: 595 bass_client_connected(connection, ERROR_CODE_UNSPECIFIED_ERROR); 596 return false; 597 } 598 599 if (connection->service_instances_num == 0){ 600 bass_client_connected(connection, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE); 601 return false; 602 } 603 connection->receive_states_index = 0; 604 connection->receive_states_instances_num = 0; 605 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTICS; 606 break; 607 608 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_RESULT: 609 if (status != ATT_ERROR_SUCCESS){ 610 bass_client_connected(connection, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE); 611 return false; 612 } 613 614 connection->receive_states_index = 0; 615 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTIC_DESCRIPTORS; 616 break; 617 618 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_DESCRIPTORS_RESULT: 619 if (connection->receive_states_index < (connection->receive_states_instances_num - 1)){ 620 connection->receive_states_index++; 621 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTIC_DESCRIPTORS; 622 break; 623 } 624 connection->receive_states_index = 0; 625 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION; 626 break; 627 628 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_NOTIFICATION_REGISTERED: 629 if (connection->receive_states_index < (connection->receive_states_instances_num - 1)){ 630 connection->receive_states_index++; 631 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION; 632 break; 633 } 634 635 bass_client_connected(connection, ERROR_CODE_SUCCESS); 636 break; 637 638 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT: 639 bass_client_connected(connection, ERROR_CODE_SUCCESS); 640 break; 641 642 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_START_SCAN: 643 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY; 644 bass_client_emit_scan_operation_complete(connection, status, BASS_OPCODE_REMOTE_SCAN_STARTED); 645 break; 646 647 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_STOP_SCAN: 648 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY; 649 bass_client_emit_scan_operation_complete(connection, status, BASS_OPCODE_REMOTE_SCAN_STOPPED); 650 break; 651 652 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_ADD_SOURCE: 653 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY; 654 bass_client_emit_source_operation_complete(connection, status, BASS_OPCODE_ADD_SOURCE, BASS_INVALID_SOURCE_INDEX); 655 break; 656 657 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_MODIFY_SOURCE: 658 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY; 659 bass_client_emit_source_operation_complete(connection, status, BASS_OPCODE_MODIFY_SOURCE, connection->control_point_operation_source_id); 660 break; 661 662 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_REMOVE_SOURCE: 663 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY; 664 bass_client_reset_source( 665 bass_client_get_source_for_source_id(connection, connection->control_point_operation_source_id)); 666 bass_client_emit_source_operation_complete(connection, status, BASS_OPCODE_REMOVE_SOURCE, connection->control_point_operation_source_id); 667 break; 668 669 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_SET_BROADCAST_CODE: 670 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY; 671 bass_client_emit_source_operation_complete(connection, status, BASS_OPCODE_SET_BROADCAST_CODE, connection->control_point_operation_source_id); 672 break; 673 674 case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY: 675 break; 676 677 default: 678 break; 679 680 } 681 return true; 682 } 683 684 static void bass_client_handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ 685 UNUSED(packet_type); 686 UNUSED(channel); 687 UNUSED(size); 688 689 bass_client_connection_t * connection = NULL; 690 gatt_client_service_t service; 691 gatt_client_characteristic_t characteristic; 692 gatt_client_characteristic_descriptor_t characteristic_descriptor; 693 694 bool call_run = true; 695 696 switch(hci_event_packet_get_type(packet)){ 697 case GATT_EVENT_MTU: 698 connection = bass_client_get_connection_for_con_handle(gatt_event_mtu_get_handle(packet)); 699 btstack_assert(connection != NULL); 700 connection->mtu = gatt_event_mtu_get_MTU(packet); 701 break; 702 703 case GATT_EVENT_SERVICE_QUERY_RESULT: 704 connection = bass_client_get_connection_for_con_handle(gatt_event_service_query_result_get_handle(packet)); 705 btstack_assert(connection != NULL); 706 707 if (connection->service_instances_num < 1){ 708 gatt_event_service_query_result_get_service(packet, &service); 709 connection->start_handle = service.start_group_handle; 710 connection->end_handle = service.end_group_handle; 711 712 #ifdef ENABLE_TESTING_SUPPORT 713 printf("BASS Service: start handle 0x%04X, end handle 0x%04X\n", connection->start_handle, connection->end_handle); 714 #endif 715 connection->service_instances_num++; 716 } else { 717 log_info("Found more then one BASS Service instance. "); 718 } 719 break; 720 721 case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT: 722 connection = bass_client_get_connection_for_con_handle(gatt_event_characteristic_query_result_get_handle(packet)); 723 btstack_assert(connection != NULL); 724 725 gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic); 726 727 switch (characteristic.uuid16){ 728 case ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_AUDIO_SCAN_CONTROL_POINT: 729 connection->control_point_value_handle = characteristic.value_handle; 730 break; 731 case ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_RECEIVE_STATE: 732 if (connection->receive_states_instances_num < connection->max_receive_states_num){ 733 connection->receive_states[connection->receive_states_instances_num].receive_state_value_handle = characteristic.value_handle; 734 connection->receive_states[connection->receive_states_instances_num].receive_state_properties = characteristic.properties; 735 connection->receive_states[connection->receive_states_index].receive_state_end_handle = characteristic.end_handle; 736 connection->receive_states_instances_num++; 737 } else { 738 log_info("Found more BASS receive_states chrs then it can be stored. "); 739 } 740 break; 741 default: 742 btstack_assert(false); 743 return; 744 } 745 746 #ifdef ENABLE_TESTING_SUPPORT 747 printf("BASS Characteristic:\n Attribute Handle 0x%04X, Properties 0x%02X, Handle 0x%04X, UUID 0x%04X, %s\n", 748 characteristic.start_handle, 749 characteristic.properties, 750 characteristic.value_handle, characteristic.uuid16, 751 characteristic.uuid16 == ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_RECEIVE_STATE ? "RECEIVE_STATE" : "CONTROL_POINT"); 752 #endif 753 break; 754 755 756 case GATT_EVENT_ALL_CHARACTERISTIC_DESCRIPTORS_QUERY_RESULT: 757 connection = bass_client_get_connection_for_con_handle(gatt_event_all_characteristic_descriptors_query_result_get_handle(packet)); 758 btstack_assert(connection != NULL); 759 gatt_event_all_characteristic_descriptors_query_result_get_characteristic_descriptor(packet, &characteristic_descriptor); 760 761 if (characteristic_descriptor.uuid16 == ORG_BLUETOOTH_DESCRIPTOR_GATT_CLIENT_CHARACTERISTIC_CONFIGURATION){ 762 connection->receive_states[connection->receive_states_index].receive_state_ccc_handle = characteristic_descriptor.handle; 763 764 #ifdef ENABLE_TESTING_SUPPORT 765 printf(" BASS Characteristic Configuration Descriptor: Handle 0x%04X, UUID 0x%04X\n", 766 characteristic_descriptor.handle, 767 characteristic_descriptor.uuid16); 768 #endif 769 } 770 break; 771 772 773 case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT: 774 connection = bass_client_get_connection_for_con_handle(gatt_event_characteristic_value_query_result_get_handle(packet)); 775 btstack_assert(connection != NULL); 776 777 if (connection->state == BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT){ 778 #ifdef ENABLE_TESTING_SUPPORT 779 printf(" Received CCC value: "); 780 printf_hexdump(gatt_event_characteristic_value_query_result_get_value(packet), gatt_event_characteristic_value_query_result_get_value_length(packet)); 781 #endif 782 break; 783 } 784 785 if (gatt_event_characteristic_value_query_result_get_value_length(packet) == 0 ){ 786 break; 787 } 788 789 // TODO 790 // bass_client_emit_receive_state(connection, 791 // gatt_event_characteristic_value_query_result_get_value_handle(packet), 792 // ATT_ERROR_SUCCESS, 793 // gatt_event_notification_get_value(packet), 794 // gatt_event_notification_get_value_length(packet)); 795 break; 796 797 case GATT_EVENT_QUERY_COMPLETE: 798 connection = bass_client_get_connection_for_con_handle(gatt_event_query_complete_get_handle(packet)); 799 btstack_assert(connection != NULL); 800 call_run = bass_client_handle_query_complete(connection, gatt_event_query_complete_get_att_status(packet)); 801 break; 802 803 default: 804 break; 805 } 806 807 if (call_run && (connection != NULL)){ 808 bass_client_run_for_connection(connection); 809 } 810 } 811 812 void broadcast_audio_scan_service_client_init(btstack_packet_handler_t packet_handler){ 813 btstack_assert(packet_handler != NULL); 814 bass_client_event_callback = packet_handler; 815 } 816 817 uint8_t broadcast_audio_scan_service_client_connect(bass_client_connection_t * connection, 818 bass_client_source_t * receive_states, uint8_t receive_states_num, 819 hci_con_handle_t con_handle, uint16_t * bass_cid){ 820 821 btstack_assert(receive_states != NULL); 822 btstack_assert(receive_states_num > 0); 823 824 if (bass_client_get_connection_for_con_handle(con_handle) != NULL){ 825 return ERROR_CODE_COMMAND_DISALLOWED; 826 } 827 828 uint16_t cid = bass_client_get_next_cid(); 829 if (bass_cid != NULL) { 830 *bass_cid = cid; 831 } 832 833 connection->cid = cid; 834 connection->con_handle = con_handle; 835 connection->mtu = ATT_DEFAULT_MTU; 836 837 connection->max_receive_states_num = receive_states_num; 838 connection->receive_states = receive_states; 839 840 uint8_t i; 841 for (i = 0; i < connection->max_receive_states_num; i++){ 842 connection->receive_states[i].in_use = false; 843 connection->receive_states[i].source_id = BASS_INVALID_SOURCE_INDEX; 844 } 845 846 connection->service_instances_num = 0; 847 connection->receive_states_instances_num = 0; 848 connection->receive_states_index = 0; 849 850 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_SERVICE; 851 btstack_linked_list_add(&bass_client_connections, (btstack_linked_item_t *) connection); 852 853 bass_client_run_for_connection(connection); 854 return ERROR_CODE_SUCCESS; 855 } 856 857 uint8_t broadcast_audio_scan_service_client_scanning_started(uint16_t bass_cid){ 858 bass_client_connection_t * connection = bass_client_get_connection_for_cid(bass_cid); 859 if (connection == NULL){ 860 return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; 861 } 862 if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY){ 863 return ERROR_CODE_COMMAND_DISALLOWED; 864 } 865 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_START_SCAN; 866 bass_client_run_for_connection(connection); 867 return ERROR_CODE_SUCCESS; 868 } 869 870 uint8_t broadcast_audio_scan_service_client_scanning_stopped(uint16_t bass_cid){ 871 bass_client_connection_t * connection = bass_client_get_connection_for_cid(bass_cid); 872 if (connection == NULL){ 873 return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; 874 } 875 if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY){ 876 return ERROR_CODE_COMMAND_DISALLOWED; 877 } 878 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_STOP_SCAN; 879 bass_client_run_for_connection(connection); 880 return ERROR_CODE_SUCCESS; 881 } 882 883 uint8_t broadcast_audio_scan_service_client_add_source(uint16_t bass_cid, const bass_source_data_t * add_source_data){ 884 bass_client_connection_t * connection = bass_client_get_connection_for_cid(bass_cid); 885 if (connection == NULL){ 886 return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; 887 } 888 if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY){ 889 return ERROR_CODE_COMMAND_DISALLOWED; 890 } 891 892 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_ADD_SOURCE; 893 connection->control_point_operation_data = add_source_data; 894 connection->buffer_offset = 0; 895 connection->data_size = bass_client_receive_state_len(add_source_data); 896 897 bass_client_run_for_connection(connection); 898 return ERROR_CODE_SUCCESS; 899 } 900 901 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){ 902 bass_client_connection_t * connection = bass_client_get_connection_for_cid(bass_cid); 903 if (connection == NULL){ 904 return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; 905 } 906 if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY){ 907 return ERROR_CODE_COMMAND_DISALLOWED; 908 } 909 910 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_MODIFY_SOURCE; 911 connection->control_point_operation_data = modify_source_data; 912 connection->control_point_operation_source_id = source_id; 913 connection->buffer_offset = 0; 914 connection->data_size = bass_client_receive_state_len(modify_source_data); 915 916 bass_client_run_for_connection(connection); 917 return ERROR_CODE_SUCCESS; 918 } 919 920 uint8_t broadcast_audio_scan_service_client_remove_source(uint16_t bass_cid, uint8_t source_id){ 921 bass_client_connection_t * connection = bass_client_get_connection_for_cid(bass_cid); 922 if (connection == NULL){ 923 return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; 924 } 925 if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY){ 926 return ERROR_CODE_COMMAND_DISALLOWED; 927 } 928 929 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_REMOVE_SOURCE; 930 connection->control_point_operation_source_id = source_id; 931 932 bass_client_run_for_connection(connection); 933 return ERROR_CODE_SUCCESS; 934 } 935 936 uint8_t broadcast_audio_scan_service_client_set_broadcast_code(uint16_t bass_cid, uint8_t source_id, const uint8_t * broadcast_code){ 937 bass_client_connection_t * connection = bass_client_get_connection_for_cid(bass_cid); 938 if (connection == NULL){ 939 return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; 940 } 941 if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY){ 942 return ERROR_CODE_COMMAND_DISALLOWED; 943 } 944 945 connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_SET_BROADCAST_CODE; 946 connection->control_point_operation_source_id = source_id; 947 connection->broadcast_code = broadcast_code; 948 949 bass_client_run_for_connection(connection); 950 return ERROR_CODE_SUCCESS; 951 } 952 953 const bass_source_data_t * broadcast_audio_scan_service_client_get_source_data(uint16_t bass_cid, uint8_t source_id){ 954 bass_client_connection_t * connection = bass_client_get_connection_for_cid(bass_cid); 955 if (connection == NULL){ 956 return NULL; 957 } 958 if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY){ 959 return NULL; 960 } 961 return (const bass_source_data_t *) &bass_client_get_source_for_source_id(connection, source_id)->data; 962 } 963 964 uint8_t broadcast_audio_scan_service_client_get_encryption_state(uint16_t bass_cid, uint8_t source_id, 965 le_audio_big_encryption_t * out_big_encryption, uint8_t * out_bad_code){ 966 btstack_assert(out_big_encryption != NULL); 967 btstack_assert(out_bad_code != NULL); 968 969 bass_client_connection_t * connection = bass_client_get_connection_for_cid(bass_cid); 970 if (connection == NULL){ 971 return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; 972 } 973 if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_READY){ 974 return ERROR_CODE_COMMAND_DISALLOWED; 975 } 976 bass_client_source_t * source = bass_client_get_source_for_source_id(connection, source_id); 977 if (source == NULL){ 978 return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; 979 } 980 *out_big_encryption = source->big_encryption; 981 memcpy(out_bad_code, source->bad_code, 16); 982 return ERROR_CODE_SUCCESS; 983 } 984 985 void broadcast_audio_scan_service_client_deinit(uint16_t bass_cid){ 986 bass_client_event_callback = NULL; 987 bass_client_connection_t * connection = bass_client_get_connection_for_cid(bass_cid); 988 if (connection == NULL){ 989 return; 990 } 991 // finalize connections 992 bass_client_finalize_connection(connection); 993 } 994 995