xref: /btstack/src/classic/pbap_client.c (revision a06bcae0f7f63d9d69b2f846d04300ea7058ea66)
1 /*
2  * Copyright (C) 2014 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__ "pbap_client.c"
39 
40 // *****************************************************************************
41 //
42 #if 0
43     0x0000 = uint32(65542),
44     // BLUETOOTH_SERVICE_CLASS_PHONEBOOK_ACCESS_PSE
45     0x0001 = { uuid16(11 2f) },
46     // BLUETOOTH_PROTOCOL_L2CAP, BLUETOOTH_PROTOCOL_RFCOMM, BLUETOOTH_PROTOCOL_OBEX
47     0x0004 = { { uuid16(01 00) }, { uuid16(00 03), uint8(19) }, { uuid16(00 08) } }
48     0x0005 = { uuid16(10 02) },
49     // BLUETOOTH_SERVICE_CLASS_PHONEBOOK_ACCESS, v1.01 = 0x101
50     0x0009 = { { uuid16(11 30), uint16(257) } },
51     0x0100 = string(OBEX Phonebook Access Server
52     // BLUETOOTH_ATTRIBUTE_SUPPORTED_FEATURES -- should be 0x317 BLUETOOTH_ATTRIBUTE_PBAP_SUPPORTED_FEATURES?
53     0x0311 = uint8(3),
54     // BLUETOOTH_ATTRIBUTE_SUPPORTED_REPOSITORIES
55     0x0314 = uint8(1),
56 #endif
57 //
58 // *****************************************************************************
59 
60 #include "btstack_config.h"
61 
62 #include <stdint.h>
63 #include <stdio.h>
64 #include <stdlib.h>
65 #include <string.h>
66 
67 #include "hci_cmd.h"
68 #include "btstack_run_loop.h"
69 #include "btstack_debug.h"
70 #include "hci.h"
71 #include "btstack_memory.h"
72 #include "hci_dump.h"
73 #include "l2cap.h"
74 #include "bluetooth_sdp.h"
75 #include "classic/sdp_client_rfcomm.h"
76 #include "btstack_event.h"
77 
78 #include "classic/obex.h"
79 #include "classic/obex_iterator.h"
80 #include "classic/goep_client.h"
81 #include "classic/pbap_client.h"
82 
83 // 796135f0-f0c5-11d8-0966- 0800200c9a66
84 uint8_t pbap_uuid[] = { 0x79, 0x61, 0x35, 0xf0, 0xf0, 0xc5, 0x11, 0xd8, 0x09, 0x66, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66};
85 const char * pbap_type = "x-bt/phonebook";
86 const char * pbap_name = "pb.vcf";
87 
88 typedef enum {
89     PBAP_INIT = 0,
90     PBAP_W4_GOEP_CONNECTION,
91     PBAP_W2_SEND_CONNECT_REQUEST,
92     PBAP_W4_CONNECT_RESPONSE,
93     PBAP_CONNECT_RESPONSE_RECEIVED,
94     PBAP_CONNECTED,
95     //
96     PBAP_W2_PULL_PHONE_BOOK,
97     PBAP_W4_PHONE_BOOK,
98     PBAP_W2_SET_PATH_ROOT,
99     PBAP_W4_SET_PATH_ROOT_COMPLETE,
100     PBAP_W2_SET_PATH_ELEMENT,
101     PBAP_W4_SET_PATH_ELEMENT_COMPLETE,
102 } pbap_state_t;
103 
104 typedef struct pbap_client {
105     pbap_state_t state;
106     uint16_t  cid;
107     bd_addr_t bd_addr;
108     hci_con_handle_t con_handle;
109     uint8_t   incoming;
110     uint16_t  goep_cid;
111     btstack_packet_handler_t client_handler;
112     const char * current_folder;
113     uint16_t set_path_offset;
114 } pbap_client_t;
115 
116 static pbap_client_t _pbap_client;
117 static pbap_client_t * pbap_client = &_pbap_client;
118 
119 static inline void pbap_client_emit_connected_event(pbap_client_t * context, uint8_t status){
120     uint8_t event[15];
121     int pos = 0;
122     event[pos++] = HCI_EVENT_PBAP_META;
123     pos++;  // skip len
124     event[pos++] = PBAP_SUBEVENT_CONNECTION_OPENED;
125     little_endian_store_16(event,pos,context->cid);
126     pos+=2;
127     event[pos++] = status;
128     memcpy(&event[pos], context->bd_addr, 6);
129     pos += 6;
130     little_endian_store_16(event,pos,context->con_handle);
131     pos += 2;
132     event[pos++] = context->incoming;
133     event[1] = pos - 2;
134     if (pos != sizeof(event)) log_error("goep_client_emit_connected_event size %u", pos);
135     context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos);
136 }
137 
138 static inline void pbap_client_emit_connection_closed_event(pbap_client_t * context){
139     uint8_t event[5];
140     int pos = 0;
141     event[pos++] = HCI_EVENT_PBAP_META;
142     pos++;  // skip len
143     event[pos++] = PBAP_SUBEVENT_CONNECTION_CLOSED;
144     little_endian_store_16(event,pos,context->cid);
145     pos+=2;
146     event[1] = pos - 2;
147     if (pos != sizeof(event)) log_error("pbap_client_emit_connection_closed_event size %u", pos);
148     context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos);
149 }
150 
151 static inline void pbap_client_emit_operation_complete_event(pbap_client_t * context, uint8_t status){
152     uint8_t event[6];
153     int pos = 0;
154     event[pos++] = HCI_EVENT_PBAP_META;
155     pos++;  // skip len
156     event[pos++] = PBAP_SUBEVENT_OPERATION_COMPLETED;
157     little_endian_store_16(event,pos,context->cid);
158     pos+=2;
159     event[pos++]= status;
160     event[1] = pos - 2;
161     if (pos != sizeof(event)) log_error("pbap_client_emit_can_send_now_event size %u", pos);
162     context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos);
163 }
164 
165 static void pbap_handle_can_send_now(void){
166     uint8_t  path_element[20];
167     uint16_t path_element_start;
168     uint16_t path_element_len;
169 
170     switch (pbap_client->state){
171         case PBAP_W2_SEND_CONNECT_REQUEST:
172             goep_client_create_connect_request(pbap_client->goep_cid, OBEX_VERSION, 0, OBEX_MAX_PACKETLEN_DEFAULT);
173             goep_client_add_header_target(pbap_client->goep_cid, 16, pbap_uuid);
174             // state
175             pbap_client->state = PBAP_W4_CONNECT_RESPONSE;
176             // send packet
177             goep_client_execute(pbap_client->goep_cid);
178             return;
179         case PBAP_W2_PULL_PHONE_BOOK:
180             goep_client_create_get_request(pbap_client->goep_cid);
181             goep_client_add_header_type(pbap_client->goep_cid, pbap_type);
182             goep_client_add_header_name(pbap_client->goep_cid, pbap_name);
183             // state
184             pbap_client->state = PBAP_W4_PHONE_BOOK;
185             // send packet
186             goep_client_execute(pbap_client->goep_cid);
187             break;
188         case PBAP_W2_SET_PATH_ROOT:
189             goep_client_create_set_path_request(pbap_client->goep_cid, 1 << 1); // Don’t create directory
190             // On Android 4.2 Cyanogenmod, using "" as path fails
191             // goep_client_add_header_name(pbap_client->goep_cid, "");     // empty == /
192             // state
193             pbap_client->state = PBAP_W4_SET_PATH_ROOT_COMPLETE;
194             // send packet
195             goep_client_execute(pbap_client->goep_cid);
196             break;
197         case PBAP_W2_SET_PATH_ELEMENT:
198             // find '/' or '\0'
199             path_element_start = pbap_client->set_path_offset;
200             while (pbap_client->current_folder[pbap_client->set_path_offset] != '\0' &&
201                 pbap_client->current_folder[pbap_client->set_path_offset] != '/'){
202                 pbap_client->set_path_offset++;
203             }
204             // skip /
205             if (pbap_client->current_folder[pbap_client->set_path_offset] == '/'){
206                 pbap_client->set_path_offset++;
207             }
208             path_element_len = pbap_client->set_path_offset-path_element_start;
209             memcpy(path_element, &pbap_client->current_folder[path_element_start], path_element_len);
210             path_element[path_element_len] = 0;
211 
212             goep_client_create_set_path_request(pbap_client->goep_cid, 1 << 1); // Don’t create directory
213             goep_client_add_header_name(pbap_client->goep_cid, (const char *) path_element); // next element
214             // state
215             pbap_client->state = PBAP_W4_SET_PATH_ELEMENT_COMPLETE;
216             // send packet
217             goep_client_execute(pbap_client->goep_cid);
218             break;
219         default:
220             break;
221     }
222 }
223 
224 static void pbap_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
225     UNUSED(channel);
226     UNUSED(size);
227     obex_iterator_t it;
228     uint8_t status;
229     switch (packet_type){
230         case HCI_EVENT_PACKET:
231             switch (hci_event_packet_get_type(packet)) {
232                 case HCI_EVENT_GOEP_META:
233                     switch (hci_event_goep_meta_get_subevent_code(packet)){
234                         case GOEP_SUBEVENT_CONNECTION_OPENED:
235                             status = goep_subevent_connection_opened_get_status(packet);
236                             pbap_client->con_handle = goep_subevent_connection_opened_get_con_handle(packet);
237                             pbap_client->incoming = goep_subevent_connection_opened_get_incoming(packet);
238                             goep_subevent_connection_opened_get_bd_addr(packet, pbap_client->bd_addr);
239                             if (status){
240                                 log_info("pbap: connection failed %u", status);
241                                 pbap_client->state = PBAP_INIT;
242                                 pbap_client_emit_connected_event(pbap_client, status);
243                             } else {
244                                 log_info("pbap: connection established");
245                                 pbap_client->goep_cid = goep_subevent_connection_opened_get_goep_cid(packet);
246                                 pbap_client->state = PBAP_W2_SEND_CONNECT_REQUEST;
247                                 goep_client_request_can_send_now(pbap_client->goep_cid);
248                             }
249                             break;
250                         case GOEP_SUBEVENT_CONNECTION_CLOSED:
251                             if (pbap_client->state != PBAP_CONNECTED){
252                                 pbap_client_emit_operation_complete_event(pbap_client, OBEX_DISCONNECTED);
253                             }
254                             pbap_client->state = PBAP_INIT;
255                             pbap_client_emit_connection_closed_event(pbap_client);
256                             break;
257                         case GOEP_SUBEVENT_CAN_SEND_NOW:
258                             pbap_handle_can_send_now();
259                             break;
260                     }
261                     break;
262                 default:
263                     break;
264             }
265             break;
266         case GOEP_DATA_PACKET:
267             // TODO: handle chunked data
268 #if 0
269             obex_dump_packet(goep_client_get_request_opcode(pbap_client->goep_cid), packet, size);
270 #endif
271             switch (pbap_client->state){
272                 case PBAP_W4_CONNECT_RESPONSE:
273                     for (obex_iterator_init_with_response_packet(&it, goep_client_get_request_opcode(pbap_client->goep_cid), packet, size); obex_iterator_has_more(&it) ; obex_iterator_next(&it)){
274                         uint8_t hi = obex_iterator_get_hi(&it);
275                         if (hi == OBEX_HEADER_CONNECTION_ID){
276                             goep_client_set_connection_id(pbap_client->goep_cid, obex_iterator_get_data_32(&it));
277                         }
278                     }
279                     if (packet[0] == OBEX_RESP_SUCCESS){
280                         pbap_client->state = PBAP_CONNECTED;
281                         pbap_client_emit_connected_event(pbap_client, 0);
282                     } else {
283                         log_info("pbap: obex connect failed, result 0x%02x", packet[0]);
284                         pbap_client->state = PBAP_INIT;
285                         pbap_client_emit_connected_event(pbap_client, OBEX_CONNECT_FAILED);
286                     }
287                     break;
288                 case PBAP_W4_SET_PATH_ROOT_COMPLETE:
289                 case PBAP_W4_SET_PATH_ELEMENT_COMPLETE:
290                     if (packet[0] == OBEX_RESP_SUCCESS){
291                         if (pbap_client->current_folder){
292                             pbap_client->state = PBAP_W2_SET_PATH_ELEMENT;
293                             goep_client_request_can_send_now(pbap_client->goep_cid);
294                         } else {
295                             pbap_client_emit_operation_complete_event(pbap_client, 0);
296                         }
297                     } else if (packet[0] == OBEX_RESP_NOT_FOUND){
298                         pbap_client->state = PBAP_CONNECTED;
299                         pbap_client_emit_operation_complete_event(pbap_client, OBEX_NOT_FOUND);
300                     } else {
301                         pbap_client->state = PBAP_CONNECTED;
302                         pbap_client_emit_operation_complete_event(pbap_client, OBEX_UNKNOWN_ERROR);
303                     }
304                     break;
305                 case PBAP_W4_PHONE_BOOK:
306                     for (obex_iterator_init_with_response_packet(&it, goep_client_get_request_opcode(pbap_client->goep_cid), packet, size); obex_iterator_has_more(&it) ; obex_iterator_next(&it)){
307                         uint8_t hi = obex_iterator_get_hi(&it);
308                         if (hi == OBEX_HEADER_BODY || hi == OBEX_HEADER_END_OF_BODY){
309                             uint16_t     data_len = obex_iterator_get_data_len(&it);
310                             const uint8_t  * data =  obex_iterator_get_data(&it);
311                             pbap_client->client_handler(PBAP_DATA_PACKET, pbap_client->cid, (uint8_t *) data, data_len);
312                         }
313                     }
314                     if (packet[0] == OBEX_RESP_CONTINUE){
315                         pbap_client->state = PBAP_W2_PULL_PHONE_BOOK;
316                         goep_client_request_can_send_now(pbap_client->goep_cid);
317                     } else if (packet[0] == OBEX_RESP_SUCCESS){
318                         pbap_client->state = PBAP_CONNECTED;
319                         pbap_client_emit_operation_complete_event(pbap_client, 0);
320                     } else {
321                         pbap_client->state = PBAP_CONNECTED;
322                         pbap_client_emit_operation_complete_event(pbap_client, OBEX_UNKNOWN_ERROR);
323                     }
324                     break;
325                 default:
326                     break;
327             }
328             break;
329         default:
330             break;
331     }
332 }
333 
334 void pbap_client_init(void){
335     memset(pbap_client, 0, sizeof(pbap_client_t));
336     pbap_client->state = PBAP_INIT;
337     pbap_client->cid = 1;
338 }
339 
340 uint8_t pbap_connect(btstack_packet_handler_t handler, bd_addr_t addr, uint16_t * out_cid){
341     if (pbap_client->state != PBAP_INIT) return BTSTACK_MEMORY_ALLOC_FAILED;
342     pbap_client->state = PBAP_W4_GOEP_CONNECTION;
343     pbap_client->client_handler = handler;
344     uint8_t err = goep_client_create_connection(&pbap_packet_handler, addr, BLUETOOTH_SERVICE_CLASS_PHONEBOOK_ACCESS_PSE, &pbap_client->goep_cid);
345     *out_cid = pbap_client->cid;
346     if (err) return err;
347     return 0;
348 }
349 
350 uint8_t pbap_disconnect(uint16_t pbap_cid){
351     UNUSED(pbap_cid);
352     if (pbap_client->state != PBAP_CONNECTED) return BTSTACK_BUSY;
353     goep_client_disconnect(pbap_client->goep_cid);
354     return 0;
355 }
356 
357 uint8_t pbap_pull_phonebook(uint16_t pbap_cid){
358     UNUSED(pbap_cid);
359     if (pbap_client->state != PBAP_CONNECTED) return BTSTACK_BUSY;
360     pbap_client->state = PBAP_W2_PULL_PHONE_BOOK;
361     goep_client_request_can_send_now(pbap_client->goep_cid);
362     return 0;
363 }
364 
365 uint8_t pbap_set_phonebook(uint16_t pbap_cid, const char * path){
366     UNUSED(pbap_cid);
367     if (pbap_client->state != PBAP_CONNECTED) return BTSTACK_BUSY;
368     pbap_client->state = PBAP_W2_SET_PATH_ROOT;
369     pbap_client->current_folder = path;
370     pbap_client->set_path_offset = 0;
371     goep_client_request_can_send_now(pbap_client->goep_cid);
372     return 0;
373 }
374