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