xref: /btstack/src/classic/avrcp_cover_art_client.c (revision 26c447f6532db1fac97a31f7ee907145bb770321)
1 /*
2  * Copyright (C) 2023 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__ "avrcp_cover_art_client.c"
39 
40 #include <string.h>
41 
42 #include "btstack_debug.h"
43 #include "btstack_event.h"
44 #include "classic/avrcp.h"
45 #include "classic/avrcp_cover_art_client.h"
46 #include "classic/obex.h"
47 #include "hci_event.h"
48 
49 #ifdef ENABLE_AVRCP_COVER_ART
50 
51 static const hci_event_t avrcp_cover_art_client_connected = {
52     .event_code = HCI_EVENT_AVRCP_META,
53     .subevent_code = AVRCP_SUBEVENT_COVER_ART_CONNECTION_ESTABLISHED,
54     .format = "1B22"
55 };
56 
57 static const hci_event_t avrcp_cover_art_client_disconnected = {
58     .event_code = HCI_EVENT_AVRCP_META,
59     .subevent_code = AVRCP_SUBEVENT_COVER_ART_CONNECTION_RELEASED,
60     .format = "2"
61 };
62 
63 static const hci_event_t avrcp_cover_art_client_operation_complete = {
64         .event_code = HCI_EVENT_AVRCP_META,
65         .subevent_code = AVRCP_SUBEVENT_COVER_ART_OPERATION_COMPLETE,
66         .format = "21"
67 };
68 
69 // 7163DD54-4A7E-11E2-B47C-0050C2490048
70 static const uint8_t avrcp_cover_art_uuid[] = { 0x71, 0x63, 0xDD, 0x54, 0x4A, 0x7E, 0x11, 0xE2, 0xB4, 0x7C, 0x00, 0x50, 0xC2, 0x49, 0x00, 0x48 };
71 
72 // OBEX types
73 const char * avrcp_cover_art_image_properties_type     = "x-bt/img-properties";
74 const char * avrcp_cover_art_image_type               = "x-bt/img-img";
75 const char * avrcp_cover_art_linked_thumbnail_type     = "x-bt/img-thm";
76 
77 static btstack_linked_list_t avrcp_cover_art_client_connections;
78 
79 static void avrcp_browsing_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
80 
81 static uint16_t avrcp_cover_art_client_next_cid() {
82     static uint16_t cid = 0;
83     cid++;
84     if (cid == 0){
85         cid = 1;
86     }
87     return cid;
88 }
89 
90 static avrcp_cover_art_client_t * avrcp_cover_art_client_for_goep_cid(uint16_t goep_cid){
91     btstack_linked_list_iterator_t it;
92     btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &avrcp_cover_art_client_connections);
93     while (btstack_linked_list_iterator_has_next(&it)){
94         avrcp_cover_art_client_t * connection = (avrcp_cover_art_client_t *)btstack_linked_list_iterator_next(&it);
95         if (connection->goep_cid == goep_cid) {
96             return connection;
97         };
98     }
99     return NULL;
100 }
101 
102 static avrcp_cover_art_client_t * avrcp_cover_art_client_for_avrcp_cid(uint16_t avrcp_cid){
103     btstack_linked_list_iterator_t it;
104     btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &avrcp_cover_art_client_connections);
105     while (btstack_linked_list_iterator_has_next(&it)){
106         avrcp_cover_art_client_t * connection = (avrcp_cover_art_client_t *)btstack_linked_list_iterator_next(&it);
107         if (connection->avrcp_cid == avrcp_cid) {
108             return connection;
109         };
110     }
111     return NULL;
112 }
113 
114 static avrcp_cover_art_client_t * avrcp_cover_art_client_for_cover_art_cid(uint16_t cover_art_cid){
115     btstack_linked_list_iterator_t it;
116     btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &avrcp_cover_art_client_connections);
117     while (btstack_linked_list_iterator_has_next(&it)){
118         avrcp_cover_art_client_t * connection = (avrcp_cover_art_client_t *)btstack_linked_list_iterator_next(&it);
119         if (connection->cover_art_cid == cover_art_cid) {
120             return connection;
121         };
122     }
123     return NULL;
124 }
125 
126 static void avrcp_cover_art_client_emit_connection_established(btstack_packet_handler_t packet_handler, uint8_t status,
127                                                                bd_addr_t addr, uint16_t avrcp_cid,
128                                                                uint16_t cover_art_cid) {
129     uint8_t buffer[20];
130     uint16_t size = hci_event_create_from_template_and_arguments(buffer, sizeof(buffer), &avrcp_cover_art_client_connected, status, addr, avrcp_cid, cover_art_cid);
131     (*packet_handler)(HCI_EVENT_PACKET, 0, buffer, size);
132 }
133 
134 static void cover_art_client_emit_operation_complete_event(avrcp_cover_art_client_t * cover_art_client, uint8_t status) {
135     uint8_t buffer[20];
136     uint16_t size = hci_event_create_from_template_and_arguments(buffer, sizeof(buffer), &avrcp_cover_art_client_operation_complete, cover_art_client->cover_art_cid, status);
137     (*cover_art_client->packet_handler)(HCI_EVENT_PACKET, 0, buffer, size);
138 }
139 
140 static void avrcp_cover_art_client_emit_connection_released(btstack_packet_handler_t packet_handler, uint16_t cover_art_cid) {
141     uint8_t buffer[20];
142     uint16_t size = hci_event_create_from_template_and_arguments(buffer, sizeof(buffer), &avrcp_cover_art_client_disconnected, cover_art_cid);
143     (*packet_handler)(HCI_EVENT_PACKET, 0, buffer, size);
144 }
145 
146 static void avrcp_cover_art_finalize_connection(avrcp_cover_art_client_t *cover_art_client) {
147     btstack_assert(cover_art_client != NULL);
148     memset(cover_art_client, 0, sizeof(avrcp_cover_art_client_t));
149     btstack_linked_list_remove(&avrcp_cover_art_client_connections, (btstack_linked_item_t *) cover_art_client);
150 }
151 
152 static void avrcp_cover_art_client_parser_callback_connect(void * user_data, uint8_t header_id, uint16_t total_len, uint16_t data_offset, const uint8_t * data_buffer, uint16_t data_len){
153     avrcp_cover_art_client_t * client = (avrcp_cover_art_client_t *) user_data;
154     switch (header_id){
155         case OBEX_HEADER_CONNECTION_ID:
156             if (obex_parser_header_store(client->obex_header_buffer, sizeof(client->obex_header_buffer), total_len, data_offset, data_buffer, data_len) == OBEX_PARSER_HEADER_COMPLETE){
157                 goep_client_set_connection_id(client->goep_cid, big_endian_read_32(client->obex_header_buffer, 0));
158             }
159             break;
160         default:
161             break;
162     }
163 }
164 
165 static void avrcp_cover_art_client_prepare_srm_header(avrcp_cover_art_client_t * cover_art_client){
166     if (cover_art_client->flow_control_enabled == false){
167         goep_client_header_add_srm_enable(cover_art_client->goep_cid);
168         cover_art_client->srm_state = SRM_W4_CONFIRM;
169     }
170 }
171 
172 static void obex_srm_init(avrcp_cover_art_obex_srm_t * obex_srm){
173     obex_srm->srm_value = OBEX_SRM_DISABLE;
174     obex_srm->srmp_value = OBEX_SRMP_NEXT;
175 }
176 
177 static void avrcp_cover_art_client_handle_srm_headers(avrcp_cover_art_client_t *context) {
178     const avrcp_cover_art_obex_srm_t * obex_srm = &context->obex_srm;
179     // Update SRM state based on SRM headers
180     switch (context->srm_state){
181         case SRM_W4_CONFIRM:
182             switch (obex_srm->srm_value){
183                 case OBEX_SRM_ENABLE:
184                     switch (obex_srm->srmp_value){
185                         case OBEX_SRMP_WAIT:
186                             context->srm_state = SRM_ENABLED_BUT_WAITING;
187                             break;
188                         default:
189                             context->srm_state = SRM_ENABLED;
190                             break;
191                     }
192                     break;
193                 default:
194                     context->srm_state = SRM_DISABLED;
195                     break;
196             }
197             break;
198         case SRM_ENABLED_BUT_WAITING:
199             switch (obex_srm->srmp_value){
200                 case OBEX_SRMP_WAIT:
201                     context->srm_state = SRM_ENABLED_BUT_WAITING;
202                     break;
203                 default:
204                     context->srm_state = SRM_ENABLED;
205                     break;
206             }
207             break;
208         default:
209             break;
210     }
211     log_info("SRM state %u", context->srm_state);
212 }
213 
214 static void avrcp_cover_art_client_parser_callback_get_operation(void * user_data, uint8_t header_id, uint16_t total_len, uint16_t data_offset, const uint8_t * data_buffer, uint16_t data_len){
215     avrcp_cover_art_client_t *client = (avrcp_cover_art_client_t *) user_data;
216     switch (header_id) {
217         case OBEX_HEADER_SINGLE_RESPONSE_MODE:
218             obex_parser_header_store(&client->obex_srm.srm_value, 1, total_len, data_offset, data_buffer, data_len);
219             break;
220         case OBEX_HEADER_SINGLE_RESPONSE_MODE_PARAMETER:
221             obex_parser_header_store(&client->obex_srm.srmp_value, 1, total_len, data_offset, data_buffer, data_len);
222             break;
223         case OBEX_HEADER_BODY:
224         case OBEX_HEADER_END_OF_BODY:
225             switch(client->state){
226                 case AVRCP_COVER_ART_W4_OBJECT:
227                     client->packet_handler(BIP_DATA_PACKET, client->cover_art_cid, (uint8_t *) data_buffer, data_len);
228                     if (data_offset + data_len == total_len){
229                         client->flow_wait_for_user = true;
230                     }
231                     break;
232                 default:
233                     btstack_unreachable();
234                     break;
235             }
236             break;
237         default:
238             // ignore other headers
239             break;
240     }
241 }
242 
243 static void avrcp_cover_art_client_prepare_get_operation(avrcp_cover_art_client_t * cover_art_client){
244     obex_parser_init_for_response(&cover_art_client->obex_parser, OBEX_OPCODE_GET, avrcp_cover_art_client_parser_callback_get_operation, cover_art_client);
245     obex_srm_init(&cover_art_client->obex_srm);
246     cover_art_client->obex_parser_waiting_for_response = true;
247 }
248 
249 static void avrcp_cover_art_client_handle_can_send_now(avrcp_cover_art_client_t * cover_art_client){
250     switch (cover_art_client->state) {
251         case AVRCP_COVER_ART_W2_SEND_CONNECT_REQUEST:
252             // prepare request
253             goep_client_request_create_connect(cover_art_client->goep_cid, OBEX_VERSION, 0, OBEX_MAX_PACKETLEN_DEFAULT);
254             goep_client_header_add_target(cover_art_client->goep_cid, avrcp_cover_art_uuid, 16);
255             // state
256             cover_art_client->state = AVRCP_COVER_ART_W4_CONNECT_RESPONSE;
257             // prepare response
258             obex_parser_init_for_response(&cover_art_client->obex_parser, OBEX_OPCODE_CONNECT,
259                                           avrcp_cover_art_client_parser_callback_connect, cover_art_client);
260             obex_srm_init(&cover_art_client->obex_srm);
261             cover_art_client->obex_parser_waiting_for_response = true;
262             // send packet
263             goep_client_execute(cover_art_client->goep_cid);
264             break;
265         case AVRCP_COVER_ART_W2_SEND_GET_OBJECT:
266             goep_client_request_create_get(cover_art_client->goep_cid);
267             if (cover_art_client->first_request){
268                 cover_art_client->first_request = false;
269                 avrcp_cover_art_client_prepare_srm_header(cover_art_client);
270                 goep_client_header_add_type(cover_art_client->goep_cid, cover_art_client->object_type);
271                 if (cover_art_client->image_descriptor != NULL){
272                     goep_client_header_add_variable(cover_art_client->goep_cid, OBEX_HEADER_IMG_DESCRIPTOR, (const uint8_t *) cover_art_client->image_descriptor, strlen(cover_art_client->image_descriptor));
273                 }
274                 uint8_t image_handle_len = btstack_max(7, strlen(cover_art_client->image_handle));
275                 goep_client_header_add_unicode_prefix(cover_art_client->goep_cid, OBEX_HEADER_IMG_HANDLE, cover_art_client->image_handle, image_handle_len);
276             }
277             // state
278             cover_art_client->state = AVRCP_COVER_ART_W4_OBJECT;
279             cover_art_client->flow_next_triggered = 0;
280             cover_art_client->flow_wait_for_user = 0;
281             // prepare response
282             avrcp_cover_art_client_prepare_get_operation(cover_art_client);
283             // send packet
284             goep_client_execute(cover_art_client->goep_cid);
285             break;
286         case AVRCP_COVER_ART_W2_SEND_DISCONNECT_REQUEST:
287             // prepare request
288             goep_client_request_create_disconnect(cover_art_client->goep_cid);
289             // state
290             cover_art_client->state = AVRCP_COVER_ART_W4_DISCONNECT_RESPONSE;
291             // prepare response
292             obex_parser_init_for_response(&cover_art_client->obex_parser, OBEX_OPCODE_DISCONNECT, NULL, cover_art_client);
293             cover_art_client->obex_parser_waiting_for_response = true;
294             // send packet
295             goep_client_execute(cover_art_client->goep_cid);
296             return;
297         default:
298             break;
299     }
300 }
301 
302 static void avrcp_cover_art_goep_event_handler(const uint8_t *packet, uint16_t size) {
303     UNUSED(size);
304     uint8_t status;
305     avrcp_cover_art_client_t * cover_art_client;
306     btstack_packet_handler_t packet_handler;
307     uint16_t cover_art_cid;
308 
309     switch (hci_event_packet_get_type(packet)) {
310         case HCI_EVENT_GOEP_META:
311             switch (hci_event_goep_meta_get_subevent_code(packet)){
312                 case GOEP_SUBEVENT_CONNECTION_OPENED:
313                     cover_art_client = avrcp_cover_art_client_for_goep_cid(goep_subevent_connection_opened_get_goep_cid(packet));
314                     btstack_assert(cover_art_client != NULL);
315                     status = goep_subevent_connection_opened_get_status(packet);
316                     if (status){
317                         log_info("connection failed %u", status);
318                         avrcp_cover_art_finalize_connection(cover_art_client);
319                         avrcp_cover_art_client_emit_connection_established(cover_art_client->packet_handler, status,
320                                                                            cover_art_client->addr,
321                                                                            cover_art_client->avrcp_cid,
322                                                                            cover_art_client->cover_art_cid);
323                     } else {
324                         log_info("connection established");
325                         cover_art_client->state = AVRCP_COVER_ART_W2_SEND_CONNECT_REQUEST;
326                         goep_client_request_can_send_now(cover_art_client->goep_cid);
327                     }
328                     break;
329                 case GOEP_SUBEVENT_CONNECTION_CLOSED:
330                     cover_art_client = avrcp_cover_art_client_for_goep_cid(goep_subevent_connection_opened_get_goep_cid(packet));
331                     btstack_assert(cover_art_client != NULL);
332                     if (cover_art_client->state > AVRCP_COVER_ART_CONNECTED){
333                         cover_art_client_emit_operation_complete_event(cover_art_client, OBEX_DISCONNECTED);
334                     }
335                     cover_art_cid = cover_art_client->cover_art_cid;
336                     packet_handler = cover_art_client->packet_handler;
337                     avrcp_cover_art_finalize_connection(cover_art_client);
338                     avrcp_cover_art_client_emit_connection_released(packet_handler, cover_art_cid);
339                     break;
340                 case GOEP_SUBEVENT_CAN_SEND_NOW:
341                     cover_art_client = avrcp_cover_art_client_for_goep_cid(goep_subevent_can_send_now_get_goep_cid(packet));
342                     btstack_assert(cover_art_client != NULL);
343                     avrcp_cover_art_client_handle_can_send_now(cover_art_client);
344                     break;
345                 default:
346                     break;
347             }
348             break;
349         default:
350             break;
351     }
352 }
353 
354 static void avrcp_cover_art_client_goep_data_handler(avrcp_cover_art_client_t * cover_art_client, uint8_t *packet, uint16_t size){
355     btstack_assert(cover_art_client->obex_parser_waiting_for_response);
356 
357     obex_parser_object_state_t parser_state;
358     parser_state = obex_parser_process_data(&cover_art_client->obex_parser, packet, size);
359     if (parser_state == OBEX_PARSER_OBJECT_STATE_COMPLETE){
360         cover_art_client->obex_parser_waiting_for_response = false;
361         obex_parser_operation_info_t op_info;
362         obex_parser_get_operation_info(&cover_art_client->obex_parser, &op_info);
363         switch (cover_art_client->state){
364             case AVRCP_COVER_ART_W4_CONNECT_RESPONSE:
365                 switch (op_info.response_code) {
366                     case OBEX_RESP_SUCCESS:
367                         cover_art_client->state = AVRCP_COVER_ART_CONNECTED;
368                         avrcp_cover_art_client_emit_connection_established(cover_art_client->packet_handler,
369                                                                            ERROR_CODE_SUCCESS,
370                                                                            cover_art_client->addr,
371                                                                            cover_art_client->avrcp_cid,
372                                                                            cover_art_client->cover_art_cid);
373                         break;
374                     default:
375                         log_info("pbap: obex connect failed, result 0x%02x", packet[0]);
376                         cover_art_client->state = AVRCP_COVER_ART_INIT;
377                         avrcp_cover_art_client_emit_connection_established(cover_art_client->packet_handler,
378                                                                            OBEX_CONNECT_FAILED,
379                                                                            cover_art_client->addr,
380                                                                            cover_art_client->avrcp_cid,
381                                                                            cover_art_client->cover_art_cid);
382                         break;
383                 }
384                 break;
385             case AVRCP_COVER_ART_W4_OBJECT:
386                 switch (op_info.response_code) {
387                     case OBEX_RESP_CONTINUE:
388                         avrcp_cover_art_client_handle_srm_headers(cover_art_client);
389                         if (cover_art_client->srm_state == SRM_ENABLED) {
390                             // prepare response
391                             avrcp_cover_art_client_prepare_get_operation(cover_art_client);
392                             break;
393                         }
394                         cover_art_client->state = AVRCP_COVER_ART_W2_SEND_GET_OBJECT;
395                         if (!cover_art_client->flow_control_enabled || !cover_art_client->flow_wait_for_user ||
396                             cover_art_client->flow_next_triggered) {
397                             goep_client_request_can_send_now(cover_art_client->goep_cid);
398                         }
399                         break;
400                     case OBEX_RESP_SUCCESS:
401                         cover_art_client->state = AVRCP_COVER_ART_CONNECTED;
402                         cover_art_client_emit_operation_complete_event(cover_art_client, ERROR_CODE_SUCCESS);
403                         break;
404                     default:
405                         log_info("unexpected response 0x%02x", packet[0]);
406                         cover_art_client->state = AVRCP_COVER_ART_CONNECTED;
407                         cover_art_client_emit_operation_complete_event(cover_art_client, OBEX_UNKNOWN_ERROR);
408                         break;
409                 }
410                 break;
411             case AVRCP_COVER_ART_W4_DISCONNECT_RESPONSE:
412                 goep_client_disconnect(cover_art_client->goep_cid);
413                 break;
414             default:
415                 btstack_unreachable();
416                 break;
417         }
418     }
419 }
420 
421 static void avrcp_cover_art_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
422     UNUSED(channel); // ok: there is no channel
423     UNUSED(size);    // ok: handling own goep events
424     avrcp_cover_art_client_t * cover_art_client;
425     switch (packet_type){
426         case HCI_EVENT_PACKET:
427             avrcp_cover_art_goep_event_handler(packet, size);
428             break;
429         case GOEP_DATA_PACKET:
430             cover_art_client = avrcp_cover_art_client_for_goep_cid(channel);
431             btstack_assert(cover_art_client != NULL);
432             avrcp_cover_art_client_goep_data_handler(cover_art_client, packet, size);
433             break;
434         default:
435             break;
436     }
437 }
438 
439 static uint8_t avrcp_cover_art_client_setup_connection(avrcp_cover_art_client_t * cover_art_client, uint16_t l2cap_psm){
440     cover_art_client->state = AVRCP_COVER_ART_W4_GOEP_CONNECTION;
441     return goep_client_connect_l2cap(&cover_art_client->goep_client,
442                                      (l2cap_ertm_config_t *) cover_art_client->ertm_config,
443                                      cover_art_client->ertm_buffer,
444                                      cover_art_client->ertm_buffer_size,
445                                      &avrcp_cover_art_packet_handler,
446                                      cover_art_client->addr,
447                                      l2cap_psm,
448                                      &cover_art_client->goep_cid);
449 }
450 
451 static void avrcp_cover_art_handle_sdp_query_complete(avrcp_connection_t * connection, uint8_t status){
452     avrcp_cover_art_client_t * cover_art_client = avrcp_cover_art_client_for_avrcp_cid(connection->avrcp_cid);
453 
454     if (cover_art_client == NULL) {
455         return;
456     }
457     if (cover_art_client->state != AVRCP_COVER_ART_W4_SDP_QUERY_COMPLETE){
458         return;
459     }
460 
461     // l2cap available?
462     if (status == ERROR_CODE_SUCCESS){
463         if (connection->cover_art_psm == 0){
464             status = SDP_SERVICE_NOT_FOUND;
465         }
466     }
467 
468     if (status == ERROR_CODE_SUCCESS) {
469         // ready to connect
470         cover_art_client->state = AVRCP_COVER_ART_W2_GOEP_CONNECT;
471         avrcp_cover_art_client_setup_connection(cover_art_client, connection->cover_art_psm);
472     } else {
473         btstack_packet_handler_t packet_handler = cover_art_client->packet_handler;
474         uint16_t cover_art_cid = cover_art_client->cover_art_cid;
475         uint16_t avrcp_cid =  cover_art_client->avrcp_cid;
476         avrcp_cover_art_finalize_connection(cover_art_client);
477         avrcp_cover_art_client_emit_connection_established(packet_handler, status, connection->remote_addr,
478                                                            avrcp_cid, cover_art_cid);
479     }
480 }
481 
482 void avrcp_cover_art_client_init(void){
483     avrcp_register_cover_art_sdp_query_complete_handler(&avrcp_cover_art_handle_sdp_query_complete);
484 }
485 
486 uint8_t
487 avrcp_cover_art_client_connect(avrcp_cover_art_client_t *cover_art_client, btstack_packet_handler_t packet_handler,
488                                bd_addr_t remote_addr, uint8_t *ertm_buffer, uint32_t ertm_buffer_size,
489                                const l2cap_ertm_config_t *ertm_config, uint16_t *avrcp_cover_art_cid) {
490 
491     avrcp_connection_t * connection_controller = avrcp_get_connection_for_bd_addr_for_role(AVRCP_CONTROLLER, remote_addr);
492     avrcp_connection_t * connection_target = avrcp_get_connection_for_bd_addr_for_role(AVRCP_TARGET, remote_addr);
493     if ((connection_target == NULL) || (connection_controller == NULL)){
494         return ERROR_CODE_COMMAND_DISALLOWED;
495     }
496 
497     cover_art_client->cover_art_cid = avrcp_cover_art_client_next_cid();
498     memcpy(cover_art_client->addr, remote_addr, 6);
499     cover_art_client->avrcp_cid = connection_controller->avrcp_cid;
500     cover_art_client->packet_handler = packet_handler;
501     cover_art_client->flow_control_enabled = false;
502 
503     // store ERTM config
504     cover_art_client->ertm_config = ertm_config;
505     cover_art_client->ertm_buffer = ertm_buffer;
506     cover_art_client->ertm_buffer_size = ertm_buffer_size;
507 
508     if (avrcp_cover_art_cid != NULL){
509         *avrcp_cover_art_cid = cover_art_client->cover_art_cid;
510     }
511 
512     btstack_linked_list_add(&avrcp_cover_art_client_connections, (btstack_linked_item_t *) cover_art_client);
513 
514     if (connection_controller->cover_art_psm == 0){
515         cover_art_client->state = AVRCP_COVER_ART_W4_SDP_QUERY_COMPLETE;
516         avrcp_trigger_sdp_query(connection_controller, connection_controller);
517         return ERROR_CODE_SUCCESS;
518     } else {
519         return avrcp_cover_art_client_setup_connection(cover_art_client, connection_controller->cover_art_psm);
520     }
521 }
522 
523 static uint8_t avrcp_cover_art_client_get_object(uint16_t avrcp_cover_art_cid, const char * object_type, const char * image_handle, const char * image_descriptor){
524     avrcp_cover_art_client_t * cover_art_client = avrcp_cover_art_client_for_cover_art_cid(avrcp_cover_art_cid);
525     if (cover_art_client == NULL){
526         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
527     }
528     if (cover_art_client->state != AVRCP_COVER_ART_CONNECTED){
529         return ERROR_CODE_COMMAND_DISALLOWED;
530     }
531     cover_art_client->state = AVRCP_COVER_ART_W2_SEND_GET_OBJECT;
532     cover_art_client->first_request = true;
533     cover_art_client->image_handle = image_handle;
534     cover_art_client->image_descriptor = image_descriptor;
535     cover_art_client->object_type = object_type;
536     goep_client_request_can_send_now(cover_art_client->goep_cid);
537     return ERROR_CODE_SUCCESS;
538 }
539 
540 uint8_t avrcp_cover_art_client_get_linked_thumbnail(uint16_t avrcp_cover_art_cid, const char * image_handle){
541     return avrcp_cover_art_client_get_object(avrcp_cover_art_cid,
542                                              avrcp_cover_art_linked_thumbnail_type,
543                                              image_handle,
544                                              NULL);
545 }
546 
547 uint8_t avrcp_cover_art_client_get_image(uint16_t avrcp_cover_art_cid, const char * image_handle, const char * image_descriptor){
548     return avrcp_cover_art_client_get_object(avrcp_cover_art_cid,
549                                              avrcp_cover_art_image_type,
550                                              image_handle,
551                                              image_descriptor);
552 }
553 
554 uint8_t avrcp_cover_art_client_get_image_properties(uint16_t avrcp_cover_art_cid, const char * image_handle){
555     return avrcp_cover_art_client_get_object(avrcp_cover_art_cid,
556                                              avrcp_cover_art_image_properties_type,
557                                              image_handle,
558                                              NULL);
559 }
560 
561 uint8_t avrcp_cover_art_client_disconnect(uint16_t avrcp_cover_art_cid){
562     avrcp_cover_art_client_t * cover_art_client = avrcp_cover_art_client_for_cover_art_cid(avrcp_cover_art_cid);
563     if (cover_art_client == NULL){
564         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
565     }
566     if (cover_art_client->state < AVRCP_COVER_ART_CONNECTED){
567         return ERROR_CODE_COMMAND_DISALLOWED;
568     }
569     cover_art_client->state = AVRCP_COVER_ART_W2_SEND_DISCONNECT_REQUEST;
570     goep_client_request_can_send_now(cover_art_client->goep_cid);
571     return ERROR_CODE_SUCCESS;
572 }
573 
574 void avrcp_cover_art_client_deinit(void){
575     avrcp_cover_art_client_connections = NULL;
576 }
577 
578 #endif /* ENABLE_AVRCP_COVER_ART */
579