1 /* 2 * Copyright (C) 2016 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 MATTHIAS 24 * RINGWALD 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__ "avrcp_browsing_controller.c" 39 40 #include <stdint.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 45 #include "btstack.h" 46 #include "classic/avrcp.h" 47 #include "classic/avrcp_browsing_controller.h" 48 49 static avrcp_context_t avrcp_browsing_controller_context; 50 static void avrcp_handle_sdp_client_query_result(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); 51 static uint16_t avrcp_cid_counter = 0; 52 53 static avrcp_context_t * sdp_query_context; 54 static uint8_t attribute_value[1000]; 55 static const unsigned int attribute_value_buffer_size = sizeof(attribute_value); 56 57 void avrcp_browser_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size, avrcp_context_t * context); 58 59 static uint16_t avrcp_get_next_cid(void){ 60 avrcp_cid_counter++; 61 if (avrcp_cid_counter == 0){ 62 avrcp_cid_counter = 1; 63 } 64 return avrcp_cid_counter; 65 } 66 67 static avrcp_browsing_connection_t * get_avrcp_browsing_connection_for_l2cap_cid(uint16_t l2cap_cid, avrcp_context_t * context){ 68 btstack_linked_list_iterator_t it; 69 btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &context->connections); 70 while (btstack_linked_list_iterator_has_next(&it)){ 71 avrcp_browsing_connection_t * connection = (avrcp_browsing_connection_t *)btstack_linked_list_iterator_next(&it); 72 if (connection->l2cap_browsing_cid != l2cap_cid) continue; 73 return connection; 74 } 75 return NULL; 76 } 77 78 static avrcp_browsing_connection_t * get_avrcp_browsing_connection_for_cid(uint16_t avrcp_cid, avrcp_context_t * context){ 79 btstack_linked_list_iterator_t it; 80 btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &context->connections); 81 while (btstack_linked_list_iterator_has_next(&it)){ 82 avrcp_browsing_connection_t * connection = (avrcp_browsing_connection_t *)btstack_linked_list_iterator_next(&it); 83 if (connection->browsing_cid != avrcp_cid) continue; 84 return connection; 85 } 86 return NULL; 87 } 88 89 static avrcp_browsing_connection_t * get_avrcp_browsing_connection_for_bd_addr(bd_addr_t addr, avrcp_context_t * context){ 90 btstack_linked_list_iterator_t it; 91 btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &context->connections); 92 while (btstack_linked_list_iterator_has_next(&it)){ 93 avrcp_browsing_connection_t * connection = (avrcp_browsing_connection_t *)btstack_linked_list_iterator_next(&it); 94 if (memcmp(addr, connection->remote_addr, 6) != 0) continue; 95 return connection; 96 } 97 return NULL; 98 } 99 100 static void avrcp_emit_browsing_connection_established(btstack_packet_handler_t callback, uint16_t avrcp_cid, bd_addr_t addr, uint8_t status){ 101 if (!callback) return; 102 uint8_t event[12]; 103 int pos = 0; 104 event[pos++] = HCI_EVENT_AVRCP_META; 105 event[pos++] = sizeof(event) - 2; 106 event[pos++] = AVRCP_SUBEVENT_BROWSING_CONNECTION_ESTABLISHED; 107 event[pos++] = status; 108 reverse_bd_addr(addr,&event[pos]); 109 pos += 6; 110 little_endian_store_16(event, pos, avrcp_cid); 111 pos += 2; 112 (*callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); 113 } 114 115 static void avrcp_emit_browsing_connection_closed(btstack_packet_handler_t callback, uint16_t avrcp_cid){ 116 if (!callback) return; 117 uint8_t event[5]; 118 int pos = 0; 119 event[pos++] = HCI_EVENT_AVRCP_META; 120 event[pos++] = sizeof(event) - 2; 121 event[pos++] = AVRCP_SUBEVENT_BROWSING_CONNECTION_RELEASED; 122 little_endian_store_16(event, pos, avrcp_cid); 123 pos += 2; 124 (*callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); 125 } 126 127 static void avrcp_handle_sdp_client_query_result(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ 128 avrcp_browsing_connection_t * connection = get_avrcp_browsing_connection_for_cid(sdp_query_context->avrcp_cid, sdp_query_context); 129 if (!connection) return; 130 if (connection->state != AVCTP_CONNECTION_W4_SDP_QUERY_COMPLETE) return; 131 UNUSED(packet_type); 132 UNUSED(channel); 133 UNUSED(size); 134 uint8_t status; 135 des_iterator_t des_list_it; 136 des_iterator_t prot_it; 137 // uint32_t avdtp_remote_uuid = 0; 138 139 switch (hci_event_packet_get_type(packet)){ 140 case SDP_EVENT_QUERY_ATTRIBUTE_VALUE: 141 // Handle new SDP record 142 if (sdp_event_query_attribute_byte_get_record_id(packet) != sdp_query_context->record_id) { 143 sdp_query_context->record_id = sdp_event_query_attribute_byte_get_record_id(packet); 144 sdp_query_context->parse_sdp_record = 0; 145 printf("SDP Record: Nr: %d\n", sdp_query_context->record_id); 146 } 147 148 if (sdp_event_query_attribute_byte_get_attribute_length(packet) <= attribute_value_buffer_size) { 149 attribute_value[sdp_event_query_attribute_byte_get_data_offset(packet)] = sdp_event_query_attribute_byte_get_data(packet); 150 151 if ((uint16_t)(sdp_event_query_attribute_byte_get_data_offset(packet)+1) == sdp_event_query_attribute_byte_get_attribute_length(packet)) { 152 switch(sdp_event_query_attribute_byte_get_attribute_id(packet)) { 153 case BLUETOOTH_ATTRIBUTE_SERVICE_CLASS_ID_LIST: 154 if (de_get_element_type(attribute_value) != DE_DES) break; 155 for (des_iterator_init(&des_list_it, attribute_value); des_iterator_has_more(&des_list_it); des_iterator_next(&des_list_it)) { 156 uint8_t * element = des_iterator_get_element(&des_list_it); 157 if (de_get_element_type(element) != DE_UUID) continue; 158 uint32_t uuid = de_get_uuid32(element); 159 switch (uuid){ 160 case BLUETOOTH_SERVICE_CLASS_AV_REMOTE_CONTROL_TARGET: 161 if (sdp_query_context->role == AVRCP_CONTROLLER) { 162 sdp_query_context->parse_sdp_record = 1; 163 printf(" Controller \n"); 164 break; 165 } 166 break; 167 case BLUETOOTH_SERVICE_CLASS_AV_REMOTE_CONTROL: 168 case BLUETOOTH_SERVICE_CLASS_AV_REMOTE_CONTROL_CONTROLLER: 169 if (sdp_query_context->role == AVRCP_TARGET) { 170 printf(" Target \n"); 171 sdp_query_context->parse_sdp_record = 1; 172 break; 173 } 174 break; 175 default: 176 printf(" not found\n"); 177 break; 178 } 179 } 180 break; 181 182 case BLUETOOTH_ATTRIBUTE_PROTOCOL_DESCRIPTOR_LIST: { 183 if (!sdp_query_context->parse_sdp_record) break; 184 // log_info("SDP Attribute: 0x%04x", sdp_event_query_attribute_byte_get_attribute_id(packet)); 185 for (des_iterator_init(&des_list_it, attribute_value); des_iterator_has_more(&des_list_it); des_iterator_next(&des_list_it)) { 186 uint8_t *des_element; 187 uint8_t *element; 188 uint32_t uuid; 189 190 if (des_iterator_get_type(&des_list_it) != DE_DES) continue; 191 192 des_element = des_iterator_get_element(&des_list_it); 193 des_iterator_init(&prot_it, des_element); 194 element = des_iterator_get_element(&prot_it); 195 196 if (de_get_element_type(element) != DE_UUID) continue; 197 198 uuid = de_get_uuid32(element); 199 switch (uuid){ 200 case BLUETOOTH_PROTOCOL_L2CAP: 201 if (!des_iterator_has_more(&prot_it)) continue; 202 des_iterator_next(&prot_it); 203 de_element_get_uint16(des_iterator_get_element(&prot_it), &sdp_query_context->avrcp_l2cap_psm); 204 printf(" found signaling PSM: 0x%02x\n", sdp_query_context->avrcp_l2cap_psm); 205 break; 206 case BLUETOOTH_PROTOCOL_AVCTP: 207 if (!des_iterator_has_more(&prot_it)) continue; 208 des_iterator_next(&prot_it); 209 de_element_get_uint16(des_iterator_get_element(&prot_it), &sdp_query_context->avrcp_version); 210 break; 211 default: 212 break; 213 } 214 } 215 } 216 break; 217 case BLUETOOTH_ATTRIBUTE_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS: { 218 // log_info("SDP Attribute: 0x%04x", sdp_event_query_attribute_byte_get_attribute_id(packet)); 219 if (!sdp_query_context->parse_sdp_record) break; 220 if (de_get_element_type(attribute_value) != DE_DES) break; 221 222 des_iterator_t des_list_0_it; 223 uint8_t *element_0; 224 225 des_iterator_init(&des_list_0_it, attribute_value); 226 element_0 = des_iterator_get_element(&des_list_0_it); 227 228 for (des_iterator_init(&des_list_it, element_0); des_iterator_has_more(&des_list_it); des_iterator_next(&des_list_it)) { 229 uint8_t *des_element; 230 uint8_t *element; 231 uint32_t uuid; 232 233 if (des_iterator_get_type(&des_list_it) != DE_DES) continue; 234 235 des_element = des_iterator_get_element(&des_list_it); 236 des_iterator_init(&prot_it, des_element); 237 element = des_iterator_get_element(&prot_it); 238 239 if (de_get_element_type(element) != DE_UUID) continue; 240 241 uuid = de_get_uuid32(element); 242 switch (uuid){ 243 case BLUETOOTH_PROTOCOL_L2CAP: 244 if (!des_iterator_has_more(&prot_it)) continue; 245 des_iterator_next(&prot_it); 246 de_element_get_uint16(des_iterator_get_element(&prot_it), &sdp_query_context->avrcp_browsing_l2cap_psm); 247 printf(" found browsing PSM: 0x%02x\n", sdp_query_context->avrcp_browsing_l2cap_psm); 248 break; 249 case BLUETOOTH_PROTOCOL_AVCTP: 250 if (!des_iterator_has_more(&prot_it)) continue; 251 des_iterator_next(&prot_it); 252 de_element_get_uint16(des_iterator_get_element(&prot_it), &sdp_query_context->avrcp_browsing_version); 253 break; 254 default: 255 break; 256 } 257 } 258 } 259 break; 260 default: 261 break; 262 } 263 } 264 } else { 265 log_error("SDP attribute value buffer size exceeded: available %d, required %d", attribute_value_buffer_size, sdp_event_query_attribute_byte_get_attribute_length(packet)); 266 } 267 break; 268 269 case SDP_EVENT_QUERY_COMPLETE: 270 status = sdp_event_query_complete_get_status(packet); 271 if (status != ERROR_CODE_SUCCESS){ 272 avrcp_emit_browsing_connection_established(sdp_query_context->avrcp_callback, connection->browsing_cid, connection->remote_addr, status); 273 btstack_linked_list_remove(&sdp_query_context->connections, (btstack_linked_item_t*) connection); 274 btstack_memory_avrcp_browsing_connection_free(connection); 275 log_info("AVRCP: SDP query failed with status 0x%02x.", status); 276 break; 277 } 278 279 if (!sdp_query_context->parse_sdp_record || !sdp_query_context->avrcp_browsing_l2cap_psm){ 280 connection->state = AVCTP_CONNECTION_IDLE; 281 avrcp_emit_browsing_connection_established(sdp_query_context->avrcp_callback, connection->browsing_cid, connection->remote_addr, SDP_SERVICE_NOT_FOUND); 282 btstack_linked_list_remove(&sdp_query_context->connections, (btstack_linked_item_t*) connection); 283 btstack_memory_avrcp_browsing_connection_free(connection); 284 break; 285 } 286 // log_info("AVRCP Control PSM 0x%02x, Browsing PSM 0x%02x", sdp_query_context->avrcp_l2cap_psm, sdp_query_context->avrcp_browsing_l2cap_psm); 287 connection->state = AVCTP_CONNECTION_W4_L2CAP_CONNECTED; 288 289 l2cap_create_ertm_channel(sdp_query_context->packet_handler, connection->remote_addr, sdp_query_context->avrcp_browsing_l2cap_psm, 290 &connection->ertm_config, connection->ertm_buffer, connection->ertm_buffer_size, NULL); 291 292 // l2cap_create_channel(sdp_query_context->packet_handler, connection->remote_addr, sdp_query_context->avrcp_l2cap_psm, l2cap_max_mtu(), NULL); 293 break; 294 } 295 } 296 297 298 static avrcp_browsing_connection_t * avrcp_browsing_create_connection(bd_addr_t remote_addr, avrcp_context_t * context){ 299 avrcp_browsing_connection_t * connection = btstack_memory_avrcp_browsing_connection_get(); 300 if (!connection){ 301 log_error("avrcp: not enough memory to create connection"); 302 return NULL; 303 } 304 memset(connection, 0, sizeof(avrcp_browsing_connection_t)); 305 connection->state = AVCTP_CONNECTION_IDLE; 306 connection->transaction_label = 0xFF; 307 connection->browsing_cid = avrcp_get_next_cid(); 308 memcpy(connection->remote_addr, remote_addr, 6); 309 btstack_linked_list_add(&context->connections, (btstack_linked_item_t *) connection); 310 return connection; 311 } 312 313 static uint8_t avrcp_browsing_connect(bd_addr_t bd_addr, avrcp_context_t * context, uint8_t * ertm_buffer, uint32_t size, l2cap_ertm_config_t * ertm_config, uint16_t * avrcp_cid){ 314 avrcp_browsing_connection_t * connection = get_avrcp_browsing_connection_for_bd_addr(bd_addr, context); 315 if (connection){ 316 return ERROR_CODE_COMMAND_DISALLOWED; 317 } 318 connection = avrcp_browsing_create_connection(bd_addr, context); 319 if (!connection){ 320 printf("avrcp: could not allocate connection struct."); 321 return BTSTACK_MEMORY_ALLOC_FAILED; 322 } 323 324 // if (!avrcp_cid) return L2CAP_LOCAL_CID_DOES_NOT_EXIST; 325 326 *avrcp_cid = connection->browsing_cid; 327 connection->state = AVCTP_CONNECTION_W4_SDP_QUERY_COMPLETE; 328 connection->ertm_buffer = ertm_buffer; 329 connection->ertm_buffer_size = size; 330 memcpy(&connection->ertm_config, ertm_config, sizeof(l2cap_ertm_config_t)); 331 332 context->parse_sdp_record = 0; 333 context->record_id = 0; 334 context->avrcp_l2cap_psm = 0; 335 context->avrcp_version = 0; 336 context->avrcp_browsing_l2cap_psm = 0; 337 context->avrcp_browsing_version = 0; 338 339 context->avrcp_cid = connection->browsing_cid; 340 sdp_query_context = context; 341 printf(" start SDP query\n"); 342 return sdp_client_query_uuid16(&avrcp_handle_sdp_client_query_result, bd_addr, BLUETOOTH_PROTOCOL_AVCTP); 343 } 344 345 void avrcp_browser_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size, avrcp_context_t * context){ 346 UNUSED(channel); 347 UNUSED(size); 348 bd_addr_t event_addr; 349 uint16_t local_cid; 350 uint8_t status; 351 avrcp_browsing_connection_t * connection = NULL; 352 353 if (packet_type != HCI_EVENT_PACKET) return; 354 355 switch (hci_event_packet_get_type(packet)) { 356 case HCI_EVENT_DISCONNECTION_COMPLETE: 357 avrcp_emit_browsing_connection_closed(context->avrcp_callback, 0); 358 break; 359 case L2CAP_EVENT_INCOMING_CONNECTION: 360 l2cap_event_incoming_connection_get_address(packet, event_addr); 361 local_cid = l2cap_event_incoming_connection_get_local_cid(packet); 362 connection = avrcp_browsing_create_connection(event_addr, context); 363 if (!connection) { 364 log_error("Failed to alloc connection structure"); 365 l2cap_decline_connection(local_cid); 366 break; 367 } 368 connection->state = AVCTP_CONNECTION_W4_L2CAP_CONNECTED; 369 connection->l2cap_browsing_cid = local_cid; 370 log_info("L2CAP_EVENT_INCOMING_CONNECTION avrcp_cid 0x%02x, l2cap_signaling_cid 0x%02x", connection->browsing_cid, connection->l2cap_browsing_cid); 371 // l2cap_accept_connection(local_cid); 372 printf("L2CAP Accepting incoming connection request in ERTM\n"); 373 l2cap_accept_ertm_connection(local_cid, &connection->ertm_config, connection->ertm_buffer, connection->ertm_buffer_size); 374 break; 375 376 case L2CAP_EVENT_CHANNEL_OPENED: 377 l2cap_event_channel_opened_get_address(packet, event_addr); 378 status = l2cap_event_channel_opened_get_status(packet); 379 local_cid = l2cap_event_channel_opened_get_local_cid(packet); 380 381 connection = get_avrcp_browsing_connection_for_bd_addr(event_addr, context); 382 if (!connection){ 383 log_error("Failed to alloc AVRCP connection structure"); 384 avrcp_emit_browsing_connection_established(context->avrcp_callback, connection->browsing_cid, event_addr, BTSTACK_MEMORY_ALLOC_FAILED); 385 l2cap_disconnect(local_cid, 0); // reason isn't used 386 break; 387 } 388 if (status != ERROR_CODE_SUCCESS){ 389 log_info("L2CAP connection to connection %s failed. status code 0x%02x", bd_addr_to_str(event_addr), status); 390 avrcp_emit_browsing_connection_established(context->avrcp_callback, connection->browsing_cid, event_addr, status); 391 btstack_linked_list_remove(&context->connections, (btstack_linked_item_t*) connection); 392 btstack_memory_avrcp_browsing_connection_free(connection); 393 break; 394 } 395 connection->l2cap_browsing_cid = local_cid; 396 397 log_info("L2CAP_EVENT_CHANNEL_OPENED avrcp_cid 0x%02x, l2cap_signaling_cid 0x%02x", connection->browsing_cid, connection->l2cap_browsing_cid); 398 connection->state = AVCTP_CONNECTION_OPENED; 399 avrcp_emit_browsing_connection_established(context->avrcp_callback, connection->browsing_cid, event_addr, ERROR_CODE_SUCCESS); 400 break; 401 402 case L2CAP_EVENT_CHANNEL_CLOSED: 403 // data: event (8), len(8), channel (16) 404 local_cid = l2cap_event_channel_closed_get_local_cid(packet); 405 connection = get_avrcp_browsing_connection_for_l2cap_cid(local_cid, context); 406 if (connection){ 407 avrcp_emit_browsing_connection_closed(context->avrcp_callback, connection->browsing_cid); 408 // free connection 409 btstack_linked_list_remove(&context->connections, (btstack_linked_item_t*) connection); 410 btstack_memory_avrcp_browsing_connection_free(connection); 411 break; 412 } 413 break; 414 default: 415 break; 416 } 417 } 418 419 // static void avrcp_handle_l2cap_data_packet_for_browsing_connection(avrcp_browsing_connection_t * connection, uint8_t *packet, uint16_t size){ 420 421 // } 422 423 // static void avrcp_browsing_controller_handle_can_send_now(avrcp_browsing_connection_t * connection){ 424 // int i; 425 // switch (connection->state){ 426 // case AVCTP_CONNECTION_OPENED: 427 // return; 428 // default: 429 // return; 430 // } 431 // } 432 433 static void avrcp_browsing_controller_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ 434 avrcp_browsing_connection_t * connection; 435 436 switch (packet_type) { 437 case L2CAP_DATA_PACKET: 438 connection = get_avrcp_browsing_connection_for_l2cap_cid(channel, &avrcp_browsing_controller_context); 439 if (!connection) break; 440 // avrcp_handle_l2cap_data_packet_for_browsing_connection(connection, packet, size); 441 break; 442 case HCI_EVENT_PACKET: 443 switch (hci_event_packet_get_type(packet)){ 444 case L2CAP_EVENT_CAN_SEND_NOW: 445 connection = get_avrcp_browsing_connection_for_l2cap_cid(channel, &avrcp_browsing_controller_context); 446 if (!connection) break; 447 // avrcp_browsing_controller_handle_can_send_now(connection); 448 break; 449 default: 450 avrcp_browser_packet_handler(packet_type, channel, packet, size, &avrcp_browsing_controller_context); 451 break; 452 } 453 default: 454 break; 455 } 456 } 457 458 void avrcp_browsing_controller_init(void){ 459 avrcp_browsing_controller_context.role = AVRCP_CONTROLLER; 460 avrcp_browsing_controller_context.connections = NULL; 461 avrcp_browsing_controller_context.packet_handler = avrcp_browsing_controller_packet_handler; 462 l2cap_register_service(&avrcp_browsing_controller_packet_handler, BLUETOOTH_PROTOCOL_AVCTP, 0xffff, LEVEL_0); 463 } 464 465 void avrcp_browsing_controller_register_packet_handler(btstack_packet_handler_t callback){ 466 if (callback == NULL){ 467 log_error("avrcp_browsing_controller_register_packet_handler called with NULL callback"); 468 return; 469 } 470 avrcp_browsing_controller_context.avrcp_callback = callback; 471 } 472 473 uint8_t avrcp_browsing_controller_connect(bd_addr_t bd_addr, uint8_t * ertm_buffer, uint32_t size, l2cap_ertm_config_t * ertm_config, uint16_t * avrcp_browsing_cid){ 474 return avrcp_browsing_connect(bd_addr, &avrcp_browsing_controller_context, ertm_buffer, size, ertm_config, avrcp_browsing_cid); 475 } 476 477 uint8_t avrcp_browsing_controller_disconnect(uint16_t avrcp_browsing_cid){ 478 avrcp_browsing_connection_t * connection = get_avrcp_browsing_connection_for_cid(avrcp_browsing_cid, &avrcp_browsing_controller_context); 479 if (!connection){ 480 log_error("avrcp_browsing_controller_disconnect: could not find a connection."); 481 return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; 482 } 483 if (connection->state != AVCTP_CONNECTION_OPENED) return ERROR_CODE_COMMAND_DISALLOWED; 484 l2cap_disconnect(connection->l2cap_browsing_cid, 0); 485 return ERROR_CODE_SUCCESS; 486 } 487