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 #include "md5.h" 78 #include "yxml.h" 79 80 #include "classic/obex.h" 81 #include "classic/obex_iterator.h" 82 #include "classic/goep_client.h" 83 #include "classic/pbap_client.h" 84 85 // 796135f0-f0c5-11d8-0966- 0800200c9a66 86 uint8_t pbap_uuid[] = { 0x79, 0x61, 0x35, 0xf0, 0xf0, 0xc5, 0x11, 0xd8, 0x09, 0x66, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66}; 87 88 const char * pbap_phonebook_type = "x-bt/phonebook"; 89 const char * pbap_phonebook_name = "pb.vcf"; 90 91 const char * pbap_vcard_listing_type = "x-bt/vcard-listing"; 92 const char * pbap_vcard_listing_name = "pb"; 93 94 typedef enum { 95 PBAP_INIT = 0, 96 PBAP_W4_GOEP_CONNECTION, 97 PBAP_W2_SEND_CONNECT_REQUEST, 98 PBAP_W4_CONNECT_RESPONSE, 99 PBAP_W4_USER_AUTHENTICATION, 100 PBAP_W2_SEND_AUTHENTICATED_CONNECT, 101 PBAP_CONNECT_RESPONSE_RECEIVED, 102 PBAP_CONNECTED, 103 // 104 PBAP_W2_SEND_DISCONNECT_REQUEST, 105 PBAP_W4_DISCONNECT_RESPONSE, 106 // 107 PBAP_W2_PULL_PHONEBOOK, 108 PBAP_W4_PHONEBOOK, 109 PBAP_W2_SET_PATH_ROOT, 110 PBAP_W4_SET_PATH_ROOT_COMPLETE, 111 PBAP_W2_SET_PATH_ELEMENT, 112 PBAP_W4_SET_PATH_ELEMENT_COMPLETE, 113 PBAP_W2_GET_PHONEBOOK_SIZE, 114 PBAP_W4_GET_PHONEBOOK_SIZE_COMPLETE, 115 // 116 PBAP_W2_GET_CARD_LIST, 117 PBAP_W4_GET_CARD_LIST_COMPLETE, 118 119 } pbap_state_t; 120 121 typedef struct pbap_client { 122 pbap_state_t state; 123 uint16_t cid; 124 bd_addr_t bd_addr; 125 hci_con_handle_t con_handle; 126 uint8_t incoming; 127 uint16_t goep_cid; 128 btstack_packet_handler_t client_handler; 129 const char * current_folder; 130 const char * phone_number; 131 uint16_t set_path_offset; 132 uint8_t authentication_options; 133 uint16_t authentication_nonce[16]; 134 const char * authentication_password; 135 yxml_t xml_parser; 136 uint8_t xml_buffer[50]; 137 } pbap_client_t; 138 139 static pbap_client_t _pbap_client; 140 static pbap_client_t * pbap_client = &_pbap_client; 141 142 static void pbap_client_emit_connected_event(pbap_client_t * context, uint8_t status){ 143 uint8_t event[15]; 144 int pos = 0; 145 event[pos++] = HCI_EVENT_PBAP_META; 146 pos++; // skip len 147 event[pos++] = PBAP_SUBEVENT_CONNECTION_OPENED; 148 little_endian_store_16(event,pos,context->cid); 149 pos+=2; 150 event[pos++] = status; 151 memcpy(&event[pos], context->bd_addr, 6); 152 pos += 6; 153 little_endian_store_16(event,pos,context->con_handle); 154 pos += 2; 155 event[pos++] = context->incoming; 156 event[1] = pos - 2; 157 if (pos != sizeof(event)) log_error("goep_client_emit_connected_event size %u", pos); 158 context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos); 159 } 160 161 static void pbap_client_emit_connection_closed_event(pbap_client_t * context){ 162 uint8_t event[5]; 163 int pos = 0; 164 event[pos++] = HCI_EVENT_PBAP_META; 165 pos++; // skip len 166 event[pos++] = PBAP_SUBEVENT_CONNECTION_CLOSED; 167 little_endian_store_16(event,pos,context->cid); 168 pos+=2; 169 event[1] = pos - 2; 170 if (pos != sizeof(event)) log_error("pbap_client_emit_connection_closed_event size %u", pos); 171 context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos); 172 } 173 174 static void pbap_client_emit_operation_complete_event(pbap_client_t * context, uint8_t status){ 175 uint8_t event[6]; 176 int pos = 0; 177 event[pos++] = HCI_EVENT_PBAP_META; 178 pos++; // skip len 179 event[pos++] = PBAP_SUBEVENT_OPERATION_COMPLETED; 180 little_endian_store_16(event,pos,context->cid); 181 pos+=2; 182 event[pos++]= status; 183 event[1] = pos - 2; 184 if (pos != sizeof(event)) log_error("pbap_client_emit_can_send_now_event size %u", pos); 185 context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos); 186 } 187 188 static void pbap_client_emit_phonebook_size_event(pbap_client_t * context, uint8_t status, uint16_t phonebook_size){ 189 uint8_t event[8]; 190 int pos = 0; 191 event[pos++] = HCI_EVENT_PBAP_META; 192 pos++; // skip len 193 event[pos++] = PBAP_SUBEVENT_PHONEBOOK_SIZE; 194 little_endian_store_16(event,pos,context->cid); 195 pos+=2; 196 event[pos++] = status; 197 little_endian_store_16(event,pos, phonebook_size); 198 pos+=2; 199 event[1] = pos - 2; 200 if (pos != sizeof(event)) log_error("pbap_client_emit_phonebook_size_event size %u", pos); 201 context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos); 202 } 203 204 static void pbap_client_emit_authentication_event(pbap_client_t * context, uint8_t options){ 205 // split options 206 uint8_t user_id_required = options & 1 ? 1 : 0; 207 uint8_t full_access = options & 2 ? 1 : 0; 208 209 uint8_t event[7]; 210 int pos = 0; 211 event[pos++] = HCI_EVENT_PBAP_META; 212 pos++; // skip len 213 event[pos++] = PBAP_SUBEVENT_AUTHENTICATION_REQUEST; 214 little_endian_store_16(event,pos,context->cid); 215 pos+=2; 216 event[pos++] = user_id_required; 217 event[pos++] = full_access; 218 if (pos != sizeof(event)) log_error("pbap_client_emit_authentication_event size %u", pos); 219 context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos); 220 } 221 222 static void pbap_client_emit_card_result_event(pbap_client_t * context, const char * name, const char * handle){ 223 uint8_t event[5 + PBAP_MAX_NAME_LEN + PBAP_MAX_HANDLE_LEN]; 224 int pos = 0; 225 event[pos++] = HCI_EVENT_PBAP_META; 226 pos++; // skip len 227 event[pos++] = PBAP_SUBEVENT_CARD_RESULT; 228 little_endian_store_16(event,pos,context->cid); 229 pos+=2; 230 int name_len = btstack_min(PBAP_MAX_NAME_LEN, strlen(name)); 231 event[pos++] = name_len; 232 memcpy(&event[pos], name, name_len); 233 pos += name_len; 234 int handle_len = btstack_min(PBAP_MAX_HANDLE_LEN, strlen(handle)); 235 event[pos++] = handle_len; 236 memcpy(&event[pos], handle, handle_len); 237 pos += handle_len; 238 event[1] = pos - 2; 239 context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos); 240 } 241 242 static const uint8_t collon = (uint8_t) ':'; 243 244 static void pbap_handle_can_send_now(void){ 245 uint8_t path_element[20]; 246 uint16_t path_element_start; 247 uint16_t path_element_len; 248 uint8_t application_parameters[PBAP_MAX_PHONE_NUMBER_LEN + 10]; 249 uint8_t challenge_response[36]; 250 int i; 251 uint16_t phone_number_len; 252 253 MD5_CTX md5_ctx; 254 255 switch (pbap_client->state){ 256 case PBAP_W2_SEND_CONNECT_REQUEST: 257 goep_client_create_connect_request(pbap_client->goep_cid, OBEX_VERSION, 0, OBEX_MAX_PACKETLEN_DEFAULT); 258 goep_client_add_header_target(pbap_client->goep_cid, 16, pbap_uuid); 259 // Add PbapSupportedFeatures 260 application_parameters[0] = PBAP_APPLICATION_PARAMETER_PBAP_SUPPORTED_FEATURES; 261 application_parameters[1] = 4; 262 big_endian_store_32(application_parameters, 2, goep_client_get_pbap_supported_features(pbap_client->goep_cid)); 263 goep_client_add_header_application_parameters(pbap_client->goep_cid, 6, &application_parameters[0]); 264 pbap_client->state = PBAP_W4_CONNECT_RESPONSE; 265 goep_client_execute(pbap_client->goep_cid); 266 break; 267 case PBAP_W2_SEND_AUTHENTICATED_CONNECT: 268 goep_client_create_connect_request(pbap_client->goep_cid, OBEX_VERSION, 0, OBEX_MAX_PACKETLEN_DEFAULT); 269 goep_client_add_header_target(pbap_client->goep_cid, 16, pbap_uuid); 270 // setup authentication challenge response 271 i = 0; 272 challenge_response[i++] = 0; // Tag Digest 273 challenge_response[i++] = 16; // Len 274 // calculate md5 275 MD5_Init(&md5_ctx); 276 MD5_Update(&md5_ctx, pbap_client->authentication_nonce, 16); 277 MD5_Update(&md5_ctx, &collon, 1); 278 MD5_Update(&md5_ctx, pbap_client->authentication_password, strlen(pbap_client->authentication_password)); 279 MD5_Final(&challenge_response[i], &md5_ctx); 280 i += 16; 281 challenge_response[i++] = 2; // Tag Nonce 282 challenge_response[i++] = 16; // Len 283 memcpy(&challenge_response[i], pbap_client->authentication_nonce, 16); 284 i += 16; 285 goep_client_add_header_challenge_response(pbap_client->goep_cid, i, challenge_response); 286 pbap_client->state = PBAP_W4_CONNECT_RESPONSE; 287 goep_client_execute(pbap_client->goep_cid); 288 break; 289 case PBAP_W2_SEND_DISCONNECT_REQUEST: 290 goep_client_create_disconnect_request(pbap_client->goep_cid); 291 pbap_client->state = PBAP_W4_DISCONNECT_RESPONSE; 292 goep_client_execute(pbap_client->goep_cid); 293 return; 294 case PBAP_W2_PULL_PHONEBOOK: 295 case PBAP_W2_GET_PHONEBOOK_SIZE: 296 goep_client_create_get_request(pbap_client->goep_cid); 297 goep_client_add_header_type(pbap_client->goep_cid, pbap_phonebook_type); 298 goep_client_add_header_name(pbap_client->goep_cid, pbap_phonebook_name); 299 if (pbap_client->state == PBAP_W2_GET_PHONEBOOK_SIZE){ 300 // Regular TLV wih 1-byte len 301 application_parameters[0] = PBAP_APPLICATION_PARAMETER_MAX_LIST_COUNT; 302 application_parameters[1] = 2; 303 big_endian_store_16(application_parameters, 2, 0); 304 goep_client_add_header_application_parameters(pbap_client->goep_cid, 4, &application_parameters[0]); 305 // state 306 pbap_client->state = PBAP_W4_GET_PHONEBOOK_SIZE_COMPLETE; 307 } else { 308 // state 309 pbap_client->state = PBAP_W4_PHONEBOOK; 310 } 311 // send packet 312 goep_client_execute(pbap_client->goep_cid); 313 break; 314 case PBAP_W2_GET_CARD_LIST: 315 goep_client_create_get_request(pbap_client->goep_cid); 316 goep_client_add_header_type(pbap_client->goep_cid, pbap_vcard_listing_type); 317 goep_client_add_header_name(pbap_client->goep_cid, pbap_vcard_listing_name); 318 // Regular TLV wih 1-byte len 319 i = 0; 320 phone_number_len = btstack_min(PBAP_MAX_PHONE_NUMBER_LEN, strlen(pbap_client->phone_number)); 321 application_parameters[i++] = PBAP_APPLICATION_PARAMETER_SEARCH_VALUE; 322 application_parameters[i++] = phone_number_len; 323 memcpy(&application_parameters[i], pbap_client->phone_number, phone_number_len); 324 i += phone_number_len; 325 application_parameters[i++] = PBAP_APPLICATION_PARAMETER_SEARCH_PROPERTY; 326 application_parameters[i++] = 1; 327 application_parameters[i++] = 0x01; // Number 328 goep_client_add_header_application_parameters(pbap_client->goep_cid, i, &application_parameters[0]); 329 pbap_client->state = PBAP_W4_GET_CARD_LIST_COMPLETE; 330 // send packet 331 goep_client_execute(pbap_client->goep_cid); 332 break; 333 case PBAP_W2_SET_PATH_ROOT: 334 goep_client_create_set_path_request(pbap_client->goep_cid, 1 << 1); // Don’t create directory 335 // On Android 4.2 Cyanogenmod, using "" as path fails 336 // goep_client_add_header_name(pbap_client->goep_cid, ""); // empty == / 337 // state 338 pbap_client->state = PBAP_W4_SET_PATH_ROOT_COMPLETE; 339 // send packet 340 goep_client_execute(pbap_client->goep_cid); 341 break; 342 case PBAP_W2_SET_PATH_ELEMENT: 343 // find '/' or '\0' 344 path_element_start = pbap_client->set_path_offset; 345 while (pbap_client->current_folder[pbap_client->set_path_offset] != '\0' && 346 pbap_client->current_folder[pbap_client->set_path_offset] != '/'){ 347 pbap_client->set_path_offset++; 348 } 349 // skip / 350 if (pbap_client->current_folder[pbap_client->set_path_offset] == '/'){ 351 pbap_client->set_path_offset++; 352 } 353 path_element_len = pbap_client->set_path_offset-path_element_start; 354 memcpy(path_element, &pbap_client->current_folder[path_element_start], path_element_len); 355 path_element[path_element_len] = 0; 356 357 goep_client_create_set_path_request(pbap_client->goep_cid, 1 << 1); // Don’t create directory 358 goep_client_add_header_name(pbap_client->goep_cid, (const char *) path_element); // next element 359 // state 360 pbap_client->state = PBAP_W4_SET_PATH_ELEMENT_COMPLETE; 361 // send packet 362 goep_client_execute(pbap_client->goep_cid); 363 break; 364 default: 365 break; 366 } 367 } 368 369 static void pbap_parse_authentication_challenge(pbap_client_t * context, const uint8_t * challenge_data, uint16_t challenge_len){ 370 // printf("Challenge: "); 371 // printf_hexdump(challenge_data, challenge_len); 372 int i; 373 // uint8_t charset_code = 0; 374 for (i=0 ; i<challenge_len ; ){ 375 int tag = challenge_data[i]; 376 int len = challenge_data[i + 1]; 377 i += 2; 378 switch (tag) { 379 case 0: 380 if (len != 0x10) { 381 log_error("Invalid OBEX digest len %u", len); 382 return; 383 } 384 memcpy(context->authentication_nonce, &challenge_data[i], 16); 385 // printf("Nonce: "); 386 // printf_hexdump(context->authentication_nonce, 16); 387 break; 388 case 1: 389 context->authentication_options = challenge_data[i]; 390 // printf("Options %u\n", context->authentication_options); 391 break; 392 case 2: 393 // TODO: handle charset 394 // charset_code = challenge_data[i]; 395 break; 396 } 397 i += len; 398 } 399 } 400 401 static void pbap_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ 402 403 UNUSED(channel); // ok: there is no channel 404 UNUSED(size); // ok: handling own geop events 405 406 obex_iterator_t it; 407 uint8_t status; 408 switch (packet_type){ 409 case HCI_EVENT_PACKET: 410 switch (hci_event_packet_get_type(packet)) { 411 case HCI_EVENT_GOEP_META: 412 switch (hci_event_goep_meta_get_subevent_code(packet)){ 413 case GOEP_SUBEVENT_CONNECTION_OPENED: 414 status = goep_subevent_connection_opened_get_status(packet); 415 pbap_client->con_handle = goep_subevent_connection_opened_get_con_handle(packet); 416 pbap_client->incoming = goep_subevent_connection_opened_get_incoming(packet); 417 goep_subevent_connection_opened_get_bd_addr(packet, pbap_client->bd_addr); 418 if (status){ 419 log_info("pbap: connection failed %u", status); 420 pbap_client->state = PBAP_INIT; 421 pbap_client_emit_connected_event(pbap_client, status); 422 } else { 423 log_info("pbap: connection established"); 424 pbap_client->goep_cid = goep_subevent_connection_opened_get_goep_cid(packet); 425 pbap_client->state = PBAP_W2_SEND_CONNECT_REQUEST; 426 goep_client_request_can_send_now(pbap_client->goep_cid); 427 } 428 break; 429 case GOEP_SUBEVENT_CONNECTION_CLOSED: 430 if (pbap_client->state != PBAP_CONNECTED){ 431 pbap_client_emit_operation_complete_event(pbap_client, OBEX_DISCONNECTED); 432 } 433 pbap_client->state = PBAP_INIT; 434 pbap_client_emit_connection_closed_event(pbap_client); 435 break; 436 case GOEP_SUBEVENT_CAN_SEND_NOW: 437 pbap_handle_can_send_now(); 438 break; 439 } 440 break; 441 default: 442 break; 443 } 444 break; 445 case GOEP_DATA_PACKET: 446 // TODO: handle chunked data 447 // obex_dump_packet(goep_client_get_request_opcode(pbap_client->goep_cid), packet, size); 448 switch (pbap_client->state){ 449 case PBAP_W4_CONNECT_RESPONSE: 450 switch (packet[0]){ 451 case OBEX_RESP_SUCCESS: 452 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)){ 453 uint8_t hi = obex_iterator_get_hi(&it); 454 if (hi == OBEX_HEADER_CONNECTION_ID){ 455 goep_client_set_connection_id(pbap_client->goep_cid, obex_iterator_get_data_32(&it)); 456 } 457 } 458 pbap_client->state = PBAP_CONNECTED; 459 pbap_client_emit_connected_event(pbap_client, 0); 460 break; 461 case OBEX_RESP_UNAUTHORIZED: 462 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)){ 463 uint8_t hi = obex_iterator_get_hi(&it); 464 if (hi == OBEX_HEADER_AUTHENTICATION_CHALLENGE){ 465 pbap_parse_authentication_challenge(pbap_client, obex_iterator_get_data(&it), obex_iterator_get_data_len(&it)); 466 } 467 } 468 pbap_client->state = PBAP_W4_USER_AUTHENTICATION; 469 pbap_client_emit_authentication_event(pbap_client, pbap_client->authentication_options); 470 break; 471 default: 472 log_info("pbap: obex connect failed, result 0x%02x", packet[0]); 473 pbap_client->state = PBAP_INIT; 474 pbap_client_emit_connected_event(pbap_client, OBEX_CONNECT_FAILED); 475 break; 476 } 477 break; 478 case PBAP_W4_DISCONNECT_RESPONSE: 479 goep_client_disconnect(pbap_client->goep_cid); 480 break; 481 case PBAP_W4_SET_PATH_ROOT_COMPLETE: 482 case PBAP_W4_SET_PATH_ELEMENT_COMPLETE: 483 if (packet[0] == OBEX_RESP_SUCCESS){ 484 if (pbap_client->current_folder){ 485 pbap_client->state = PBAP_W2_SET_PATH_ELEMENT; 486 goep_client_request_can_send_now(pbap_client->goep_cid); 487 } else { 488 pbap_client_emit_operation_complete_event(pbap_client, 0); 489 } 490 } else if (packet[0] == OBEX_RESP_NOT_FOUND){ 491 pbap_client->state = PBAP_CONNECTED; 492 pbap_client_emit_operation_complete_event(pbap_client, OBEX_NOT_FOUND); 493 } else { 494 pbap_client->state = PBAP_CONNECTED; 495 pbap_client_emit_operation_complete_event(pbap_client, OBEX_UNKNOWN_ERROR); 496 } 497 break; 498 case PBAP_W4_PHONEBOOK: 499 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)){ 500 uint8_t hi = obex_iterator_get_hi(&it); 501 if (hi == OBEX_HEADER_BODY || hi == OBEX_HEADER_END_OF_BODY){ 502 uint16_t data_len = obex_iterator_get_data_len(&it); 503 const uint8_t * data = obex_iterator_get_data(&it); 504 pbap_client->client_handler(PBAP_DATA_PACKET, pbap_client->cid, (uint8_t *) data, data_len); 505 } 506 } 507 if (packet[0] == OBEX_RESP_CONTINUE){ 508 pbap_client->state = PBAP_W2_PULL_PHONEBOOK; 509 goep_client_request_can_send_now(pbap_client->goep_cid); 510 } else if (packet[0] == OBEX_RESP_SUCCESS){ 511 pbap_client->state = PBAP_CONNECTED; 512 pbap_client_emit_operation_complete_event(pbap_client, 0); 513 } else { 514 pbap_client->state = PBAP_CONNECTED; 515 pbap_client_emit_operation_complete_event(pbap_client, OBEX_UNKNOWN_ERROR); 516 } 517 break; 518 case PBAP_W4_GET_PHONEBOOK_SIZE_COMPLETE: 519 pbap_client->state = PBAP_CONNECTED; 520 if (packet[0] == OBEX_RESP_SUCCESS){ 521 int have_size = 0; 522 uint16_t phonebook_size; 523 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)){ 524 uint8_t hi = obex_iterator_get_hi(&it); 525 if (hi == OBEX_HEADER_APPLICATION_PARAMETERS){ 526 uint16_t data_len = obex_iterator_get_data_len(&it); 527 const uint8_t * data = obex_iterator_get_data(&it); 528 // iterate over application headers (TLV with 1 bytes len) 529 unsigned int i = 0; 530 while (i<data_len){ 531 uint8_t tag = data[i++]; 532 uint8_t len = data[i++]; 533 if (tag == PBAP_APPLICATION_PARAMETER_PHONEBOOK_SIZE && len == 2){ 534 have_size = 1; 535 phonebook_size = big_endian_read_16(data, i); 536 } 537 i+=len; 538 } 539 } 540 } 541 if (have_size){ 542 pbap_client_emit_phonebook_size_event(pbap_client, 0, phonebook_size); 543 break; 544 } 545 } 546 pbap_client_emit_phonebook_size_event(pbap_client, OBEX_UNKNOWN_ERROR, 0); 547 break; 548 case PBAP_W4_GET_CARD_LIST_COMPLETE: 549 if (packet[0] == OBEX_RESP_CONTINUE){ 550 pbap_client->state = PBAP_W2_GET_CARD_LIST; 551 goep_client_request_can_send_now(pbap_client->goep_cid); 552 } else if (packet[0] == OBEX_RESP_SUCCESS){ 553 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)){ 554 uint8_t hi = obex_iterator_get_hi(&it); 555 if (hi == OBEX_HEADER_END_OF_BODY){ 556 uint16_t data_len = obex_iterator_get_data_len(&it); 557 const uint8_t * data = obex_iterator_get_data(&it); 558 // now try parsing it 559 yxml_init(&pbap_client->xml_parser, pbap_client->xml_buffer, sizeof(pbap_client->xml_buffer)); 560 int card_found = 0; 561 int name_found = 0; 562 int handle_found = 0; 563 char name[PBAP_MAX_NAME_LEN]; 564 char handle[PBAP_MAX_HANDLE_LEN]; 565 name[0] = 0; 566 handle[0] = 0; 567 while (data_len--){ 568 yxml_ret_t r = yxml_parse(&pbap_client->xml_parser, *data++); 569 switch (r){ 570 case YXML_ELEMSTART: 571 card_found = strcmp("card", pbap_client->xml_parser.elem) == 0; 572 break; 573 case YXML_ELEMEND: 574 if (card_found){ 575 pbap_client_emit_card_result_event(pbap_client, name, handle); 576 } 577 card_found = 0; 578 break; 579 case YXML_ATTRSTART: 580 if (!card_found) break; 581 if (strcmp("name", pbap_client->xml_parser.attr) == 0){ 582 name_found = 1; 583 break; 584 } 585 if (strcmp("handle", pbap_client->xml_parser.attr) == 0){ 586 handle_found = 1; 587 break; 588 } 589 break; 590 case YXML_ATTRVAL: 591 if (name_found) { 592 // "In UTF-8, characters from the U+0000..U+10FFFF range (the UTF-16 accessible range) are encoded using sequences of 1 to 4 octets." 593 if (strlen(name) + 4 + 1 >= sizeof(name)) break; 594 strcat(name, pbap_client->xml_parser.data); 595 break; 596 } 597 if (handle_found) { 598 // "In UTF-8, characters from the U+0000..U+10FFFF range (the UTF-16 accessible range) are encoded using sequences of 1 to 4 octets." 599 if (strlen(handle) + 4 + 1 >= sizeof(handle)) break; 600 strcat(handle, pbap_client->xml_parser.data); 601 break; 602 } 603 break; 604 case YXML_ATTREND: 605 name_found = 0; 606 handle_found = 0; 607 break; 608 default: 609 break; 610 } 611 } 612 // 613 pbap_client->state = PBAP_CONNECTED; 614 pbap_client_emit_operation_complete_event(pbap_client, 0); 615 } 616 } 617 618 } else { 619 // ? 620 } 621 break; 622 default: 623 break; 624 } 625 break; 626 default: 627 break; 628 } 629 } 630 631 void pbap_client_init(void){ 632 memset(pbap_client, 0, sizeof(pbap_client_t)); 633 pbap_client->state = PBAP_INIT; 634 pbap_client->cid = 1; 635 } 636 637 uint8_t pbap_connect(btstack_packet_handler_t handler, bd_addr_t addr, uint16_t * out_cid){ 638 if (pbap_client->state != PBAP_INIT) return BTSTACK_MEMORY_ALLOC_FAILED; 639 pbap_client->state = PBAP_W4_GOEP_CONNECTION; 640 pbap_client->client_handler = handler; 641 uint8_t err = goep_client_create_connection(&pbap_packet_handler, addr, BLUETOOTH_SERVICE_CLASS_PHONEBOOK_ACCESS_PSE, &pbap_client->goep_cid); 642 *out_cid = pbap_client->cid; 643 if (err) return err; 644 return 0; 645 } 646 647 uint8_t pbap_disconnect(uint16_t pbap_cid){ 648 UNUSED(pbap_cid); 649 if (pbap_client->state != PBAP_CONNECTED) return BTSTACK_BUSY; 650 pbap_client->state = PBAP_W2_SEND_DISCONNECT_REQUEST; 651 goep_client_request_can_send_now(pbap_client->goep_cid); 652 return 0; 653 } 654 655 uint8_t pbap_get_phonebook_size(uint16_t pbap_cid){ 656 UNUSED(pbap_cid); 657 if (pbap_client->state != PBAP_CONNECTED) return BTSTACK_BUSY; 658 pbap_client->state = PBAP_W2_GET_PHONEBOOK_SIZE; 659 goep_client_request_can_send_now(pbap_client->goep_cid); 660 return 0; 661 } 662 663 uint8_t pbap_pull_phonebook(uint16_t pbap_cid){ 664 UNUSED(pbap_cid); 665 if (pbap_client->state != PBAP_CONNECTED) return BTSTACK_BUSY; 666 pbap_client->state = PBAP_W2_PULL_PHONEBOOK; 667 goep_client_request_can_send_now(pbap_client->goep_cid); 668 return 0; 669 } 670 671 uint8_t pbap_set_phonebook(uint16_t pbap_cid, const char * path){ 672 UNUSED(pbap_cid); 673 if (pbap_client->state != PBAP_CONNECTED) return BTSTACK_BUSY; 674 pbap_client->state = PBAP_W2_SET_PATH_ROOT; 675 pbap_client->current_folder = path; 676 pbap_client->set_path_offset = 0; 677 goep_client_request_can_send_now(pbap_client->goep_cid); 678 return 0; 679 } 680 681 uint8_t pbap_authentication_password(uint16_t pbap_cid, const char * password){ 682 UNUSED(pbap_cid); 683 if (pbap_client->state != PBAP_W4_USER_AUTHENTICATION) return BTSTACK_BUSY; 684 pbap_client->state = PBAP_W2_SEND_AUTHENTICATED_CONNECT; 685 pbap_client->authentication_password = password; 686 goep_client_request_can_send_now(pbap_client->goep_cid); 687 return 0; 688 } 689 690 uint8_t pbap_lookup_by_number(uint16_t pbap_cid, const char * phone_number){ 691 UNUSED(pbap_cid); 692 if (pbap_client->state != PBAP_CONNECTED) return BTSTACK_BUSY; 693 pbap_client->state = PBAP_W2_GET_CARD_LIST; 694 pbap_client->phone_number = phone_number; 695 goep_client_request_can_send_now(pbap_client->goep_cid); 696 return 0; 697 } 698