xref: /btstack/src/classic/avrcp_cover_art_client.c (revision f05358500e612946f8c340b1f11e5703dafd65f8)
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 uint16_t avrcp_cover_art_client_next_cid(void) {
80     static uint16_t cid = 0;
81     cid++;
82     if (cid == 0){
83         cid = 1;
84     }
85     return cid;
86 }
87 
88 static avrcp_cover_art_client_t * avrcp_cover_art_client_for_goep_cid(uint16_t goep_cid){
89     btstack_linked_list_iterator_t it;
90     btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &avrcp_cover_art_client_connections);
91     while (btstack_linked_list_iterator_has_next(&it)){
92         avrcp_cover_art_client_t * connection = (avrcp_cover_art_client_t *)btstack_linked_list_iterator_next(&it);
93         if (connection->goep_cid == goep_cid) {
94             return connection;
95         };
96     }
97     return NULL;
98 }
99 
100 static avrcp_cover_art_client_t * avrcp_cover_art_client_for_avrcp_cid(uint16_t avrcp_cid){
101     btstack_linked_list_iterator_t it;
102     btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &avrcp_cover_art_client_connections);
103     while (btstack_linked_list_iterator_has_next(&it)){
104         avrcp_cover_art_client_t * connection = (avrcp_cover_art_client_t *)btstack_linked_list_iterator_next(&it);
105         if (connection->avrcp_cid == avrcp_cid) {
106             return connection;
107         };
108     }
109     return NULL;
110 }
111 
112 static avrcp_cover_art_client_t * avrcp_cover_art_client_for_cover_art_cid(uint16_t cover_art_cid){
113     btstack_linked_list_iterator_t it;
114     btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &avrcp_cover_art_client_connections);
115     while (btstack_linked_list_iterator_has_next(&it)){
116         avrcp_cover_art_client_t * connection = (avrcp_cover_art_client_t *)btstack_linked_list_iterator_next(&it);
117         if (connection->cover_art_cid == cover_art_cid) {
118             return connection;
119         };
120     }
121     return NULL;
122 }
123 
124 static void avrcp_cover_art_client_emit_connection_established(btstack_packet_handler_t packet_handler, uint8_t status,
125                                                                bd_addr_t addr, uint16_t avrcp_cid,
126                                                                uint16_t cover_art_cid) {
127     uint8_t buffer[20];
128     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);
129     (*packet_handler)(HCI_EVENT_PACKET, 0, buffer, size);
130 }
131 
132 static void cover_art_client_emit_operation_complete_event(avrcp_cover_art_client_t * cover_art_client, uint8_t status) {
133     uint8_t buffer[20];
134     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);
135     (*cover_art_client->packet_handler)(HCI_EVENT_PACKET, 0, buffer, size);
136 }
137 
138 static void avrcp_cover_art_client_emit_connection_released(btstack_packet_handler_t packet_handler, uint16_t cover_art_cid) {
139     uint8_t buffer[20];
140     uint16_t size = hci_event_create_from_template_and_arguments(buffer, sizeof(buffer), &avrcp_cover_art_client_disconnected, cover_art_cid);
141     (*packet_handler)(HCI_EVENT_PACKET, 0, buffer, size);
142 }
143 
144 static void avrcp_cover_art_finalize_connection(avrcp_cover_art_client_t *cover_art_client) {
145     btstack_assert(cover_art_client != NULL);
146     memset(cover_art_client, 0, sizeof(avrcp_cover_art_client_t));
147     btstack_linked_list_remove(&avrcp_cover_art_client_connections, (btstack_linked_item_t *) cover_art_client);
148 }
149 
150 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){
151     avrcp_cover_art_client_t * client = (avrcp_cover_art_client_t *) user_data;
152     switch (header_id){
153         case OBEX_HEADER_CONNECTION_ID:
154             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){
155                 goep_client_set_connection_id(client->goep_cid, big_endian_read_32(client->obex_header_buffer, 0));
156             }
157             break;
158         default:
159             break;
160     }
161 }
162 
163 static void avrcp_cover_art_client_prepare_srm_header(avrcp_cover_art_client_t * cover_art_client){
164     if (cover_art_client->flow_control_enabled == false){
165         goep_client_header_add_srm_enable(cover_art_client->goep_cid);
166         cover_art_client->srm_state = SRM_W4_CONFIRM;
167     }
168 }
169 
170 static void obex_srm_init(avrcp_cover_art_obex_srm_t * obex_srm){
171     obex_srm->srm_value = OBEX_SRM_DISABLE;
172     obex_srm->srmp_value = OBEX_SRMP_NEXT;
173 }
174 
175 static void avrcp_cover_art_client_handle_srm_headers(avrcp_cover_art_client_t *context) {
176     const avrcp_cover_art_obex_srm_t * obex_srm = &context->obex_srm;
177     // Update SRM state based on SRM headers
178     switch (context->srm_state){
179         case SRM_W4_CONFIRM:
180             switch (obex_srm->srm_value){
181                 case OBEX_SRM_ENABLE:
182                     switch (obex_srm->srmp_value){
183                         case OBEX_SRMP_WAIT:
184                             context->srm_state = SRM_ENABLED_BUT_WAITING;
185                             break;
186                         default:
187                             context->srm_state = SRM_ENABLED;
188                             break;
189                     }
190                     break;
191                 default:
192                     context->srm_state = SRM_DISABLED;
193                     break;
194             }
195             break;
196         case SRM_ENABLED_BUT_WAITING:
197             switch (obex_srm->srmp_value){
198                 case OBEX_SRMP_WAIT:
199                     context->srm_state = SRM_ENABLED_BUT_WAITING;
200                     break;
201                 default:
202                     context->srm_state = SRM_ENABLED;
203                     break;
204             }
205             break;
206         default:
207             break;
208     }
209     log_info("SRM state %u", context->srm_state);
210 }
211 
212 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){
213     avrcp_cover_art_client_t *client = (avrcp_cover_art_client_t *) user_data;
214     switch (header_id) {
215         case OBEX_HEADER_SINGLE_RESPONSE_MODE:
216             obex_parser_header_store(&client->obex_srm.srm_value, 1, total_len, data_offset, data_buffer, data_len);
217             break;
218         case OBEX_HEADER_SINGLE_RESPONSE_MODE_PARAMETER:
219             obex_parser_header_store(&client->obex_srm.srmp_value, 1, total_len, data_offset, data_buffer, data_len);
220             break;
221         case OBEX_HEADER_BODY:
222         case OBEX_HEADER_END_OF_BODY:
223             switch(client->state){
224                 case AVRCP_COVER_ART_W4_OBJECT:
225                     client->packet_handler(BIP_DATA_PACKET, client->cover_art_cid, (uint8_t *) data_buffer, data_len);
226                     if (data_offset + data_len == total_len){
227                         client->flow_wait_for_user = true;
228                     }
229                     break;
230                 default:
231                     btstack_unreachable();
232                     break;
233             }
234             break;
235         default:
236             // ignore other headers
237             break;
238     }
239 }
240 
241 static void avrcp_cover_art_client_prepare_get_operation(avrcp_cover_art_client_t * cover_art_client){
242     obex_parser_init_for_response(&cover_art_client->obex_parser, OBEX_OPCODE_GET, avrcp_cover_art_client_parser_callback_get_operation, cover_art_client);
243     obex_srm_init(&cover_art_client->obex_srm);
244     cover_art_client->obex_parser_waiting_for_response = true;
245 }
246 
247 static void avrcp_cover_art_client_handle_can_send_now(avrcp_cover_art_client_t * cover_art_client){
248     switch (cover_art_client->state) {
249         case AVRCP_COVER_ART_W2_SEND_CONNECT_REQUEST:
250             // prepare request
251             goep_client_request_create_connect(cover_art_client->goep_cid, OBEX_VERSION, 0, OBEX_MAX_PACKETLEN_DEFAULT);
252             goep_client_header_add_target(cover_art_client->goep_cid, avrcp_cover_art_uuid, 16);
253             // state
254             cover_art_client->state = AVRCP_COVER_ART_W4_CONNECT_RESPONSE;
255             // prepare response
256             obex_parser_init_for_response(&cover_art_client->obex_parser, OBEX_OPCODE_CONNECT,
257                                           avrcp_cover_art_client_parser_callback_connect, cover_art_client);
258             obex_srm_init(&cover_art_client->obex_srm);
259             cover_art_client->obex_parser_waiting_for_response = true;
260             // send packet
261             goep_client_execute(cover_art_client->goep_cid);
262             break;
263         case AVRCP_COVER_ART_W2_SEND_GET_OBJECT:
264             goep_client_request_create_get(cover_art_client->goep_cid);
265             if (cover_art_client->first_request){
266                 cover_art_client->first_request = false;
267                 avrcp_cover_art_client_prepare_srm_header(cover_art_client);
268                 goep_client_header_add_type(cover_art_client->goep_cid, cover_art_client->object_type);
269                 if (cover_art_client->image_descriptor != NULL){
270                     goep_client_header_add_variable(cover_art_client->goep_cid, OBEX_HEADER_IMG_DESCRIPTOR, (const uint8_t *) cover_art_client->image_descriptor, (uint16_t) strlen(cover_art_client->image_descriptor));
271                 }
272                 uint8_t image_handle_len = btstack_max(7, (uint16_t) strlen(cover_art_client->image_handle));
273                 goep_client_header_add_unicode_prefix(cover_art_client->goep_cid, OBEX_HEADER_IMG_HANDLE, cover_art_client->image_handle, image_handle_len);
274             }
275             // state
276             cover_art_client->state = AVRCP_COVER_ART_W4_OBJECT;
277             cover_art_client->flow_next_triggered = 0;
278             cover_art_client->flow_wait_for_user = 0;
279             // prepare response
280             avrcp_cover_art_client_prepare_get_operation(cover_art_client);
281             // send packet
282             goep_client_execute(cover_art_client->goep_cid);
283             break;
284         case AVRCP_COVER_ART_W2_SEND_DISCONNECT_REQUEST:
285             // prepare request
286             goep_client_request_create_disconnect(cover_art_client->goep_cid);
287             // state
288             cover_art_client->state = AVRCP_COVER_ART_W4_DISCONNECT_RESPONSE;
289             // prepare response
290             obex_parser_init_for_response(&cover_art_client->obex_parser, OBEX_OPCODE_DISCONNECT, NULL, cover_art_client);
291             cover_art_client->obex_parser_waiting_for_response = true;
292             // send packet
293             goep_client_execute(cover_art_client->goep_cid);
294             return;
295         default:
296             break;
297     }
298 }
299 
300 static void avrcp_cover_art_goep_event_handler(const uint8_t *packet, uint16_t size) {
301     UNUSED(size);
302     uint8_t status;
303     avrcp_cover_art_client_t * cover_art_client;
304     btstack_packet_handler_t packet_handler;
305     uint16_t cover_art_cid;
306 
307     switch (hci_event_packet_get_type(packet)) {
308         case HCI_EVENT_GOEP_META:
309             switch (hci_event_goep_meta_get_subevent_code(packet)){
310                 case GOEP_SUBEVENT_CONNECTION_OPENED:
311                     cover_art_client = avrcp_cover_art_client_for_goep_cid(goep_subevent_connection_opened_get_goep_cid(packet));
312                     btstack_assert(cover_art_client != NULL);
313                     status = goep_subevent_connection_opened_get_status(packet);
314                     if (status){
315                         log_info("connection failed %u", status);
316                         avrcp_cover_art_finalize_connection(cover_art_client);
317                         avrcp_cover_art_client_emit_connection_established(cover_art_client->packet_handler, status,
318                                                                            cover_art_client->addr,
319                                                                            cover_art_client->avrcp_cid,
320                                                                            cover_art_client->cover_art_cid);
321                     } else {
322                         log_info("connection established");
323                         cover_art_client->state = AVRCP_COVER_ART_W2_SEND_CONNECT_REQUEST;
324                         goep_client_request_can_send_now(cover_art_client->goep_cid);
325                     }
326                     break;
327                 case GOEP_SUBEVENT_CONNECTION_CLOSED:
328                     cover_art_client = avrcp_cover_art_client_for_goep_cid(goep_subevent_connection_opened_get_goep_cid(packet));
329                     btstack_assert(cover_art_client != NULL);
330                     if (cover_art_client->state > AVRCP_COVER_ART_CONNECTED){
331                         cover_art_client_emit_operation_complete_event(cover_art_client, OBEX_DISCONNECTED);
332                     }
333                     cover_art_cid = cover_art_client->cover_art_cid;
334                     packet_handler = cover_art_client->packet_handler;
335                     avrcp_cover_art_finalize_connection(cover_art_client);
336                     avrcp_cover_art_client_emit_connection_released(packet_handler, cover_art_cid);
337                     break;
338                 case GOEP_SUBEVENT_CAN_SEND_NOW:
339                     cover_art_client = avrcp_cover_art_client_for_goep_cid(goep_subevent_can_send_now_get_goep_cid(packet));
340                     btstack_assert(cover_art_client != NULL);
341                     avrcp_cover_art_client_handle_can_send_now(cover_art_client);
342                     break;
343                 default:
344                     break;
345             }
346             break;
347         default:
348             break;
349     }
350 }
351 
352 static void avrcp_cover_art_client_goep_data_handler(avrcp_cover_art_client_t * cover_art_client, uint8_t *packet, uint16_t size){
353     btstack_assert(cover_art_client->obex_parser_waiting_for_response);
354 
355     obex_parser_object_state_t parser_state;
356     parser_state = obex_parser_process_data(&cover_art_client->obex_parser, packet, size);
357     if (parser_state == OBEX_PARSER_OBJECT_STATE_COMPLETE){
358         cover_art_client->obex_parser_waiting_for_response = false;
359         obex_parser_operation_info_t op_info;
360         obex_parser_get_operation_info(&cover_art_client->obex_parser, &op_info);
361         switch (cover_art_client->state){
362             case AVRCP_COVER_ART_W4_CONNECT_RESPONSE:
363                 switch (op_info.response_code) {
364                     case OBEX_RESP_SUCCESS:
365                         cover_art_client->state = AVRCP_COVER_ART_CONNECTED;
366                         avrcp_cover_art_client_emit_connection_established(cover_art_client->packet_handler,
367                                                                            ERROR_CODE_SUCCESS,
368                                                                            cover_art_client->addr,
369                                                                            cover_art_client->avrcp_cid,
370                                                                            cover_art_client->cover_art_cid);
371                         break;
372                     default:
373                         log_info("pbap: obex connect failed, result 0x%02x", packet[0]);
374                         cover_art_client->state = AVRCP_COVER_ART_INIT;
375                         avrcp_cover_art_client_emit_connection_established(cover_art_client->packet_handler,
376                                                                            OBEX_CONNECT_FAILED,
377                                                                            cover_art_client->addr,
378                                                                            cover_art_client->avrcp_cid,
379                                                                            cover_art_client->cover_art_cid);
380                         break;
381                 }
382                 break;
383             case AVRCP_COVER_ART_W4_OBJECT:
384                 switch (op_info.response_code) {
385                     case OBEX_RESP_CONTINUE:
386                         avrcp_cover_art_client_handle_srm_headers(cover_art_client);
387                         if (cover_art_client->srm_state == SRM_ENABLED) {
388                             // prepare response
389                             avrcp_cover_art_client_prepare_get_operation(cover_art_client);
390                             break;
391                         }
392                         cover_art_client->state = AVRCP_COVER_ART_W2_SEND_GET_OBJECT;
393                         if (!cover_art_client->flow_control_enabled || !cover_art_client->flow_wait_for_user ||
394                             cover_art_client->flow_next_triggered) {
395                             goep_client_request_can_send_now(cover_art_client->goep_cid);
396                         }
397                         break;
398                     case OBEX_RESP_SUCCESS:
399                         cover_art_client->state = AVRCP_COVER_ART_CONNECTED;
400                         cover_art_client_emit_operation_complete_event(cover_art_client, ERROR_CODE_SUCCESS);
401                         break;
402                     default:
403                         log_info("unexpected response 0x%02x", packet[0]);
404                         cover_art_client->state = AVRCP_COVER_ART_CONNECTED;
405                         cover_art_client_emit_operation_complete_event(cover_art_client, OBEX_UNKNOWN_ERROR);
406                         break;
407                 }
408                 break;
409             case AVRCP_COVER_ART_W4_DISCONNECT_RESPONSE:
410                 goep_client_disconnect(cover_art_client->goep_cid);
411                 break;
412             default:
413                 btstack_unreachable();
414                 break;
415         }
416     }
417 }
418 
419 static void avrcp_cover_art_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
420     UNUSED(channel); // ok: there is no channel
421     UNUSED(size);    // ok: handling own goep events
422     avrcp_cover_art_client_t * cover_art_client;
423     switch (packet_type){
424         case HCI_EVENT_PACKET:
425             avrcp_cover_art_goep_event_handler(packet, size);
426             break;
427         case GOEP_DATA_PACKET:
428             cover_art_client = avrcp_cover_art_client_for_goep_cid(channel);
429             btstack_assert(cover_art_client != NULL);
430             avrcp_cover_art_client_goep_data_handler(cover_art_client, packet, size);
431             break;
432         default:
433             break;
434     }
435 }
436 
437 static uint8_t avrcp_cover_art_client_setup_connection(avrcp_cover_art_client_t * cover_art_client, uint16_t l2cap_psm){
438     cover_art_client->state = AVRCP_COVER_ART_W4_GOEP_CONNECTION;
439     return goep_client_connect_l2cap(&cover_art_client->goep_client,
440                                      (l2cap_ertm_config_t *) cover_art_client->ertm_config,
441                                      cover_art_client->ertm_buffer,
442                                      cover_art_client->ertm_buffer_size,
443                                      &avrcp_cover_art_packet_handler,
444                                      cover_art_client->addr,
445                                      l2cap_psm,
446                                      &cover_art_client->goep_cid);
447 }
448 
449 static void avrcp_cover_art_handle_sdp_query_complete(avrcp_connection_t * connection, uint8_t status){
450     avrcp_cover_art_client_t * cover_art_client = avrcp_cover_art_client_for_avrcp_cid(connection->avrcp_cid);
451 
452     if (cover_art_client == NULL) {
453         return;
454     }
455     if (cover_art_client->state != AVRCP_COVER_ART_W4_SDP_QUERY_COMPLETE){
456         return;
457     }
458 
459     // l2cap available?
460     if (status == ERROR_CODE_SUCCESS){
461         if (connection->cover_art_psm == 0){
462             status = SDP_SERVICE_NOT_FOUND;
463         }
464     }
465 
466     if (status == ERROR_CODE_SUCCESS) {
467         // ready to connect
468         cover_art_client->state = AVRCP_COVER_ART_W2_GOEP_CONNECT;
469         status = avrcp_cover_art_client_setup_connection(cover_art_client, connection->cover_art_psm);
470     }
471 
472     if (status != ERROR_CODE_SUCCESS){
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