diff --git a/.github/workflows/githubci.yml b/.github/workflows/githubci.yml index dce1d02a7..a7b897cda 100644 --- a/.github/workflows/githubci.yml +++ b/.github/workflows/githubci.yml @@ -54,8 +54,8 @@ jobs: ln -s $GITHUB_WORKSPACE $HOME/$BSP_PATH/$BSP_VERSION # Install library dependency - arduino-cli lib install "Adafruit AHRS" "Adafruit APDS9960 Library" "Adafruit BMP280 Library" "Adafruit Circuit Playground" "Adafruit EPD" "Adafruit GFX Library" "Adafruit HX8357 Library" "Adafruit ILI9341" "Adafruit LIS3MDL" "Adafruit LSM6DS" "Adafruit NeoPixel" "Adafruit NeoMatrix" "Adafruit Sensor Calibration" "Adafruit SHT31 Library" "Adafruit SSD1306" "Adafruit ST7735 and ST7789 Library" "SdFat - Adafruit Fork" - + arduino-cli lib install "Adafruit AHRS" "Adafruit APDS9960 Library" "Adafruit Arcada Library" "Adafruit BMP280 Library" "Adafruit Circuit Playground" "Adafruit EPD" "Adafruit GFX Library" "Adafruit HX8357 Library" "Adafruit ILI9341" "Adafruit LIS3MDL" "Adafruit LSM6DS" "Adafruit NeoPixel" "Adafruit NeoMatrix" "Adafruit Sensor Calibration" "Adafruit SHT31 Library" "Adafruit SSD1306" "Adafruit ST7735 and ST7789 Library" "SdFat - Adafruit Fork" + # TODO update to support MIDI version 5 later on arduino-cli lib install "MIDI Library"@4.3.1 diff --git a/.gitmodules b/.gitmodules index 8d325ddff..ef2d7254f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore"] path = cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore url = https://github.com/adafruit/Adafruit_TinyUSB_ArduinoCore.git +[submodule "libraries/Adafruit_nRFCrypto"] + path = libraries/Adafruit_nRFCrypto + url = https://github.com/adafruit/Adafruit_nRFCrypto.git diff --git a/README.md b/README.md index 0d0f97811..d3e5ba447 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Following boards are also included but are not officially supported: - [Nordic nRF52840DK PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK) - [Particle Xenon](https://store.particle.io/products/xenon) +- [Raytac MDBT50Q-RX Dongle](https://www.raytac.com/product/ins.php?index_id=89) ## BSP Installation @@ -49,7 +50,7 @@ There are two methods that you can use to install this BSP. We highly recommend ### Adafruit's nrfutil tools -[adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil) (derived from Nordic pc-nrfutil) is needed to upload sketch via serial port. +[adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil) (derived from Nordic [pc-nrfutil](https://github.com/NordicSemiconductor/pc-nrfutil)) is needed to upload sketch via serial port. - For Windows and macOS, pre-built executable binaries are included in the BSP at `tools/adafruit-nrfutil/`. It should work out of the box. - Linux user need to run follow command to install it from PyPi @@ -117,8 +118,7 @@ which in turn is based on the [Arduino SAMD Core](https://github.com/arduino/Ard The following libraries are used: -- adafruit-nrfutil is based on Nordic Semiconductor ASA's [pc-nrfutil](https://github.com/NordicSemiconductor/pc-nrfutil) -- [freeRTOS](https://www.freertos.org/) as operating system -- [tinyusb](https://github.com/hathach/tinyusb) as usb stack +- [FreeRTOS](https://www.freertos.org/) as operating system +- [LittleFS](https://github.com/ARMmbed/littlefs) for internal file system - [nrfx](https://github.com/NordicSemiconductor/nrfx) for peripherals driver -- [littlefs](https://github.com/ARMmbed/littlefs) for internal file system +- [TinyUSB](https://github.com/hathach/tinyusb) as usb stack diff --git a/changelog.md b/changelog.md index 56d8a6c18..d53ae2de3 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,55 @@ # Adafruit nRF52 Arduino Core Changelog +## 0.22.0 - WIP + +This version implement comprehensive LESC and Legacy pairing using dynamic & static Passkey. + +- Support static passkey (Legacy only) +- Support LESC on nRF52840 using hardware-accelerated ARM CryptoCell CC310 provided by [Adafruit_nRFCypto](https://github.com/adafruit/Adafruit_nRFCrypto). The library is included as submodule and released together with the BSP. +- Rework bonding mechanism to use IRK for peer finding. It is advisable to run `clearbonds` example to clean up bond files of previous version + +### BLESecurity + +A new class BLESecurity (access with Bluefruit.Security) is added to handle security and pairing. + +- **setPIN()** to set static passkey, this will force to use Legacy Pairing +- **setIOCaps()** to congiure IO capacities +- **setMITM()** to enable/disable Man in The Middle protection (passkey), it is auto-enabled when using passkey +- **setPairPasskeyCallback()** to register callback for displaying pairing passkey to user +- **setPairCompleteCallback()** to register callback for the result of pairing procedure (succeeded or failed) +- **setSecuredCallback()** to register callback which invoked when connection is secured. This happens after he pairing procedure is complete, or we re-connect with preivously bonded peer device + +### Other Changes + +**BLECentral** + +- will automatically use stored Long Term Key to secure connection if paired/bonded with device previously + +**Bluefruit** + +- Bluefruit::requestPairing() is removed, please use the BLEConnection::requestPairing() instead +- Bluefruit::connPaired() is removed, please use BLEConnection::secure() instead +- Default Device name is USB_PRODUCT if available e.g CLUE, Circuit Playground Bluefruit, Feather nRF52840 Express etc ... + +**BLEService** + +- Added setPermission() + +**BLEConnection** + +- BLEConnection::requestPairing() is now non-blocking, it will return right after sending request to peer device. Previously it is blocked until the pairing process is complete. +- Added BLEConnection::secured() to check if the connection is secured/encrypted +- Added BLEConnection::bonded() to check if we store Longterm Key with current peer +- Removed BLEConnection:paried(), user should either use secured() or bonded() depending on the context +- If bonded, getPeerAddr() will return peer public address instead of random address. + +**New Example Sketches** + +- **pairing_pin** to use static PIN for peripheral role +- **pairing_passkey** to use dyanmic Passkey for pairing. On Arcada compatible device such as `CLUE` or `Circuit Playground Bluefruit`, TFT display will also be used to display passkey. +- **cental_pairing** similar to pairing_passkey but for nRF running central role +- **ancs_arcada** for displaying ancs on arcada such CLUE and/or CPB. + ## 0.21.0 - 2020.08.31 Special thanks to @henrygab, @pyro9, @Nenik, @orrmany, @thaanstad, @kevinfrei for contributing and helping with this release. @@ -21,8 +71,6 @@ Special thanks to @henrygab, @pyro9, @Nenik, @orrmany, @thaanstad, @kevinfrei fo ## 0.20.5 - 2020.07.05 -Special thanks to @henrygab, @pyro9, @geeksville for contributing and helping with this release. - - Updated toolchain from gcc 7-2017q4 to 9-2019q4 - Fixed GPIOTE channel conflict between libraries - Added type-safe for arrcount() macros @@ -30,6 +78,8 @@ Special thanks to @henrygab, @pyro9, @geeksville for contributing and helping wi - Update CMSIS from v4 to v5 to build with TensorFlow - Update TinyUSB core to commit 0749077 +Special thanks to @henrygab, @pyro9, @geeksville for contributing and helping with this release. + ## 0.20.1 - 2020.04.23 - Update TinyUSB to commit c59fa77 due to a bug in the stack diff --git a/cores/nRF5/Arduino.h b/cores/nRF5/Arduino.h index 33e2dda73..ee5b2ae95 100644 --- a/cores/nRF5/Arduino.h +++ b/cores/nRF5/Arduino.h @@ -134,9 +134,9 @@ void resumeLoop(void); #define bit(b) (1UL << (b)) #ifdef NRF_P1 -#define digitalPinToPort(P) ( (g_ADigitalPinMap[P] < 32) ? NRF_P0 : NRF_P1 ) + #define digitalPinToPort(P) ( (g_ADigitalPinMap[P] < 32) ? NRF_P0 : NRF_P1 ) #else -#define digitalPinToPort(P) ( NRF_P0 ) + #define digitalPinToPort(P) ( NRF_P0 ) #endif #define digitalPinToBitMask(P) ( 1UL << ( g_ADigitalPinMap[P] < 32 ? g_ADigitalPinMap[P] : (g_ADigitalPinMap[P]-32) ) ) diff --git a/cores/nRF5/common_func.h b/cores/nRF5/common_func.h index ad42c6260..ed5ac272d 100644 --- a/cores/nRF5/common_func.h +++ b/cores/nRF5/common_func.h @@ -157,19 +157,19 @@ const char* dbg_err_str(int32_t err_id); // TODO move to other place #if CFG_DEBUG -#define LOG_LV1(...) ADALOG(__VA_ARGS__) -#define LOG_LV1_BUFFER(...) ADALOG_BUFFER(__VA_ARGS__) + #define LOG_LV1(...) ADALOG(__VA_ARGS__) + #define LOG_LV1_BUFFER(...) ADALOG_BUFFER(__VA_ARGS__) #else -#define LOG_LV1(...) -#define LOG_LV1_BUFFER(...) + #define LOG_LV1(...) + #define LOG_LV1_BUFFER(...) #endif #if CFG_DEBUG >= 2 -#define LOG_LV2(...) ADALOG(__VA_ARGS__) -#define LOG_LV2_BUFFER(...) ADALOG_BUFFER(__VA_ARGS__) + #define LOG_LV2(...) ADALOG(__VA_ARGS__) + #define LOG_LV2_BUFFER(...) ADALOG_BUFFER(__VA_ARGS__) #else -#define LOG_LV2(...) -#define LOG_LV2_BUFFER(...) + #define LOG_LV2(...) + #define LOG_LV2_BUFFER(...) #endif #if CFG_DEBUG @@ -193,7 +193,10 @@ const char* dbg_err_str(int32_t err_id); // TODO move to other place do {\ uint8_t const* p8 = (uint8_t const*) (buf);\ PRINTF(#buf ": ");\ - for(uint32_t i=0; i<(n); i++) PRINTF("%02x ", p8[i]);\ + for(uint32_t i=0; i<(n); i++) {\ + if (i%16 == 0) PRINTF("\n"); \ + PRINTF("%02x ", p8[i]); \ + }\ PRINTF("\n");\ }while(0) @@ -213,15 +216,15 @@ const char* dbg_err_str(int32_t err_id); // TODO move to other place #else -#define PRINT_LOCATION() -#define PRINT_MESS(x) -#define PRINT_HEAP() -#define PRINT_STR(x) -#define PRINT_INT(x) -#define PRINT_HEX(x) -#define PRINT_FLOAT(x) -#define PRINT_BUFFER(buf, n) -#define ADALOG(...) + #define PRINT_LOCATION() + #define PRINT_MESS(x) + #define PRTNT_HEAP() + #define PRINT_STR(x) + #define PRINT_INT(x) + #define PRINT_HEX(x) + #define PRINT_FLOAT(x) + #define PRINT_BUFFER(buf, n) + #define ADALOG(...) #endif diff --git a/cores/nRF5/common_inc.h b/cores/nRF5/common_inc.h index 51fbceebf..f179ad128 100644 --- a/cores/nRF5/common_inc.h +++ b/cores/nRF5/common_inc.h @@ -38,6 +38,7 @@ #include #include +#include #include "compiler_macro.h" #include "common_func.h" diff --git a/cores/nRF5/linker/nrf52840_s140_v6.ld b/cores/nRF5/linker/nrf52840_s140_v6.ld index d676b6053..6dad975b0 100755 --- a/cores/nRF5/linker/nrf52840_s140_v6.ld +++ b/cores/nRF5/linker/nrf52840_s140_v6.ld @@ -7,7 +7,7 @@ MEMORY { FLASH (rx) : ORIGIN = 0x26000, LENGTH = 0xED000 - 0x26000 - /* SRAM required by S132 depend on + /* SRAM required by Softdevice depend on * - Attribute Table Size (Number of Services and Characteristics) * - Vendor UUID count * - Max ATT MTU diff --git a/cores/nRF5/rtos.h b/cores/nRF5/rtos.h index 86e7b0b29..e193c1430 100644 --- a/cores/nRF5/rtos.h +++ b/cores/nRF5/rtos.h @@ -50,7 +50,10 @@ #include "queue.h" #include "semphr.h" +#define DEBUG_MALLOC 1 + #define DELAY_FOREVER portMAX_DELAY + enum { TASK_PRIO_LOWEST = 0, // Idle task, should not be used @@ -65,8 +68,11 @@ enum #define tick2ms(tck) ( ( ((uint64_t)(tck)) * 1000) / configTICK_RATE_HZ ) #define tick2us(tck) ( ( ((uint64_t)(tck)) * 1000000) / configTICK_RATE_HZ ) -#define malloc_type(type) rtos_malloc( sizeof(type) ) -#define rtos_malloc_type(_type) (_type*) rtos_malloc(sizeof(_type)) +#if DEBUG_MALLOC + #define rtos_malloc_type(_type) ({ LOG_LV2("MALLOC", #_type " = %d bytes", sizeof(_type)); ((_type*) rtos_malloc(sizeof(_type))); }) +#else + #define rtos_malloc_type(_type) ((_type*) rtos_malloc(sizeof(_type))) +#endif static inline void* rtos_malloc(size_t _size) { diff --git a/cores/nRF5/verify.h b/cores/nRF5/verify.h index f91514061..8c25c42db 100644 --- a/cores/nRF5/verify.h +++ b/cores/nRF5/verify.h @@ -61,13 +61,13 @@ extern "C" static inline void VERIFY_MESS_impl(int32_t _status, const char* (*_fstr)(int32_t), const char* func_name, int line_number) { PRINTF("%s: %d: verify failed, error = ", func_name, line_number); - if (_fstr) + if (_fstr && _fstr(_status)) { PRINTF(_fstr(_status)); } else { - PRINTF("%ld", _status); + PRINTF("0x%lX (%ld)", _status, _status); } PRINTF("\n"); } @@ -107,9 +107,15 @@ extern "C" * - status value if called with 1 parameter e.g VERIFY_STATUS(status) * - 2 parameter if called with 2 parameters e.g VERIFY_STATUS(status, errorcode) */ -#define VERIFY_STATUS(...) _GET_3RD_ARG(__VA_ARGS__, VERIFY_ERR_2ARGS, VERIFY_ERR_1ARGS)(__VA_ARGS__, dbg_err_str) +#define VERIFY_STATUS(...) _GET_3RD_ARG(__VA_ARGS__, VERIFY_ERR_2ARGS, VERIFY_ERR_1ARGS)(__VA_ARGS__, dbg_err_str) -#define VERIFY_ERROR(...) _GET_3RD_ARG(__VA_ARGS__, VERIFY_ERR_2ARGS, VERIFY_ERR_1ARGS)(__VA_ARGS__, NULL) +#define PRINT_STATUS(_exp) do \ +{ \ + int32_t _status = (int32_t) _exp; \ + if ( 0 != _status ) VERIFY_MESS(_status, dbg_err_str); \ +} while(0) \ + +#define VERIFY_ERROR(...) _GET_3RD_ARG(__VA_ARGS__, VERIFY_ERR_2ARGS, VERIFY_ERR_1ARGS)(__VA_ARGS__, NULL) /*------------------------------------------------------------------*/ /* VERIFY diff --git a/libraries/Adafruit_nRFCrypto b/libraries/Adafruit_nRFCrypto new file mode 160000 index 000000000..48b08a59d --- /dev/null +++ b/libraries/Adafruit_nRFCrypto @@ -0,0 +1 @@ +Subproject commit 48b08a59d11b167c6b3c124db043a6df81cf5007 diff --git a/libraries/Bluefruit52Lib/examples/Central/central_bleuart/central_bleuart.ino b/libraries/Bluefruit52Lib/examples/Central/central_bleuart/central_bleuart.ino index 992c9c69e..4085a9f5e 100644 --- a/libraries/Bluefruit52Lib/examples/Central/central_bleuart/central_bleuart.ino +++ b/libraries/Bluefruit52Lib/examples/Central/central_bleuart/central_bleuart.ino @@ -25,7 +25,7 @@ BLEClientUart clientUart; // bleuart client void setup() { Serial.begin(115200); - while ( !Serial ) delay(10); // for nrf52840 with native usb +// while ( !Serial ) delay(10); // for nrf52840 with native usb Serial.println("Bluefruit52 Central BLEUART Example"); Serial.println("-----------------------------------\n"); diff --git a/libraries/Bluefruit52Lib/examples/Central/central_hid/central_hid.ino b/libraries/Bluefruit52Lib/examples/Central/central_hid/central_hid.ino index 06a6d1e9d..2020b5492 100644 --- a/libraries/Bluefruit52Lib/examples/Central/central_hid/central_hid.ino +++ b/libraries/Bluefruit52Lib/examples/Central/central_hid/central_hid.ino @@ -30,7 +30,7 @@ hid_mouse_report_t last_mse_report = { 0 }; void setup() { Serial.begin(115200); - while ( !Serial ) delay(10); // for nrf52840 with native usb +// while ( !Serial ) delay(10); // for nrf52840 with native usb Serial.println("Bluefruit52 Central HID (Keyboard + Mouse) Example"); Serial.println("--------------------------------------------------\n"); @@ -55,6 +55,9 @@ void setup() Bluefruit.Central.setConnectCallback(connect_callback); Bluefruit.Central.setDisconnectCallback(disconnect_callback); + // Set connection secured callback, invoked when connection is encrypted + Bluefruit.Security.setSecuredCallback(connection_secured_callback); + /* Start Central Scanning * - Enable auto scan if disconnected * - Interval = 100 ms, window = 80 ms @@ -88,6 +91,8 @@ void scan_callback(ble_gap_evt_adv_report_t* report) */ void connect_callback(uint16_t conn_handle) { + BLEConnection* conn = Bluefruit.Connection(conn_handle); + Serial.println("Connected"); Serial.print("Discovering HID Service ... "); @@ -97,11 +102,31 @@ void connect_callback(uint16_t conn_handle) Serial.println("Found it"); // HID device mostly require pairing/bonding - if ( !Bluefruit.requestPairing(conn_handle) ) - { - Serial.print("Failed to paired"); - return; - } + conn->requestPairing(); + }else + { + Serial.println("Found NONE"); + + // disconnect since we couldn't find blehid service + conn->disconnect(); + } +} + +void connection_secured_callback(uint16_t conn_handle) +{ + BLEConnection* conn = Bluefruit.Connection(conn_handle); + + if ( !conn->secured() ) + { + // It is possible that connection is still not secured by this time. + // This happens (central only) when we try to encrypt connection using stored bond keys + // but peer reject it (probably it remove its stored key). + // Therefore we will request an pairing again --> callback again when encrypted + conn->requestPairing(); + } + else + { + Serial.println("Secured"); // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.hid_information.xml uint8_t hidInfo[4]; @@ -120,15 +145,9 @@ void connect_callback(uint16_t conn_handle) // Enable Mouse report notification if present on prph if ( hid.mousePresent() ) hid.enableMouse(); - + Serial.println("Ready to receive from peripheral"); - }else - { - Serial.println("Found NONE"); - - // disconnect since we couldn't find blehid service - Bluefruit.disconnect(conn_handle); - } + } } /** diff --git a/libraries/Bluefruit52Lib/examples/Central/central_pairing/central_pairing.ino b/libraries/Bluefruit52Lib/examples/Central/central_pairing/central_pairing.ino new file mode 100644 index 000000000..ab5043c4a --- /dev/null +++ b/libraries/Bluefruit52Lib/examples/Central/central_pairing/central_pairing.ino @@ -0,0 +1,402 @@ +/********************************************************************* + This is an example for our nRF52 based Bluefruit LE modules + + Pick one up today in the adafruit shop! + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#include + +/* This sketch demonstrates Pairing process using dynamic Passkey. + * This sketch is essentially the same as central_bleuart.ino except the BLE Uart + * service requires Security Mode with Man-In-The-Middle protection i.e + * + * BLE Pairing procedure is complicated, it is advisable for users to go through + * these articles to get familiar with the procedure and terminology + * - https://www.bluetooth.com/blog/bluetooth-pairing-part-1-pairing-feature-exchange/ + * - https://www.bluetooth.com/blog/bluetooth-pairing-part-2-key-generation-methods/ + * - https://www.bluetooth.com/blog/bluetooth-pairing-passkey-entry/ + * - https://www.bluetooth.com/blog/bluetooth-pairing-part-4/ + * + * IF TFT enabled board such as CLUE is used, the passkey will also display on the + * tft-> Following boards with TFT are supported + * - Adafruit CLUE : https://www.adafruit.com/product/4500 + * - Circuit Playground Bluefruit: https://www.adafruit.com/product/4333 + * - TFT Gizmo : https://www.adafruit.com/product/4367 + */ + +#if defined(ARDUINO_NRF52840_CIRCUITPLAY) || defined(ARDUINO_NRF52840_CLUE) + #define USE_ARCADA +#endif + +#include +#include +#include + +#ifdef USE_ARCADA + #include + + Adafruit_Arcada arcada; + Adafruit_SPITFT* tft; + +#else + // Use built-in buttons if available, else use A0, A1 + #ifdef PIN_BUTTON1 + #define BUTTON_YES PIN_BUTTON1 + #else + #define BUTTON_YES A0 + #endif + + #ifdef PIN_BUTTON2 + #define BUTTON_NO PIN_BUTTON2 + #else + #define BUTTON_NO A1 + #endif +#endif + +BLEClientUart clientUart; // bleuart client + +void setup() +{ + Serial.begin(115200); + + Serial.println("Bluefruit52 Central Pairing Example"); + Serial.println("-----------------------------------\n"); + +#ifdef USE_ARCADA + arcada.arcadaBegin(); + arcada.displayBegin(); + arcada.setBacklight(255); + + tft = arcada.display; + tft->setCursor(0, 0); + tft->setTextWrap(true); + tft->setTextSize(2); +#else + pinMode(BUTTON_YES, INPUT_PULLUP); + pinMode(BUTTON_NO, INPUT_PULLUP); +#endif + + // Initialize Bluefruit with maximum connections as Peripheral = 0, Central = 1 + // SRAM usage required by SoftDevice will increase dramatically with number of connections + Bluefruit.begin(0, 1); + + // clear bonds if BUTTON A is pressed +// Serial.println("Hold button A to clear bonds ..... "); +// Serial.flush(); +// delay(2000); +// if (0 == digitalRead(PIN_BUTTON1)) +// { +// Serial.println("Clear all central bonds"); +// Bluefruit.Central.clearBonds(); +// } + + // To use dynamic PassKey for pairing, we need to have + // - IO capacities at least DISPPLAY + // - Register callback to display/print dynamic passkey for central + // For complete mapping of the IO Capabilities to Key Generation Method, check out this article + // https://www.bluetooth.com/blog/bluetooth-pairing-part-2-key-generation-methods/ + Bluefruit.Security.setIOCaps(true, true, false); // display = true, yes/no = true, keyboard = false + Bluefruit.Security.setPairPasskeyCallback(pairing_passkey_callback); + + // Set complete callback to print the pairing result + Bluefruit.Security.setPairCompleteCallback(pairing_complete_callback); + + // Set connection secured callback, invoked when connection is encrypted + Bluefruit.Security.setSecuredCallback(connection_secured_callback); + + // Init BLE Central Uart Serivce + clientUart.begin(); + clientUart.setRxCallback(bleuart_rx_callback); + + // Increase Blink rate to different from PrPh advertising mode + Bluefruit.setConnLedInterval(250); + + // Callbacks for Central + Bluefruit.Central.setConnectCallback(connect_callback); + Bluefruit.Central.setDisconnectCallback(disconnect_callback); + +#ifdef USE_ARCADA + tft->fillScreen(ARCADA_BLACK); + tft->setTextColor(ARCADA_WHITE); + tft->setTextSize(2); + tft->setCursor(0, 0); + tft->println("Scanning ..."); +#endif + + /* Start Central Scanning + * - Enable auto scan if disconnected + * - Interval = 100 ms, window = 80 ms + * - Don't use active scan + * - Start(timeout) with timeout = 0 will scan forever (until connected) + */ + Bluefruit.Scanner.setRxCallback(scan_callback); + Bluefruit.Scanner.restartOnDisconnect(true); + Bluefruit.Scanner.setInterval(160, 80); // in unit of 0.625 ms + Bluefruit.Scanner.useActiveScan(false); + Bluefruit.Scanner.start(0); // // 0 = Don't stop scanning after n seconds +} + +/** + * Callback invoked when scanner pick up an advertising data + * @param report Structural advertising data + */ +void scan_callback(ble_gap_evt_adv_report_t* report) +{ + // Check if advertising contain BleUart service + if ( Bluefruit.Scanner.checkReportForService(report, clientUart) ) + { + Serial.print("BLE UART service detected. Connecting ... "); + + // Connect to device with bleuart service in advertising + Bluefruit.Central.connect(report); + }else + { + // For Softdevice v6: after received a report, scanner will be paused + // We need to call Scanner resume() to continue scanning + Bluefruit.Scanner.resume(); + } +} + +/** + * Callback invoked when an connection is established + * @param conn_handle + */ +void connect_callback(uint16_t conn_handle) +{ + BLEConnection* conn = Bluefruit.Connection(conn_handle); + + Serial.println("Connected"); + +#ifdef USE_ARCADA + tft->fillScreen(ARCADA_BLACK); + tft->setTextSize(2); + tft->setCursor(0, 0); + tft->println("Connected"); +#endif + + // If we are not bonded with peer previously -> send pairing request + // Else wait for the connection secured callback + if ( !conn->bonded() ) + { + conn->requestPairing(); + } +} + +/** + * Callback invoked when a connection is dropped + * @param conn_handle + * @param reason is a BLE_HCI_STATUS_CODE which can be found in ble_hci.h + */ +void disconnect_callback(uint16_t conn_handle, uint8_t reason) +{ + (void) conn_handle; + (void) reason; + + Serial.print("Disconnected, reason = 0x"); Serial.println(reason, HEX); + +#ifdef USE_ARCADA + tft->println("Scanning ..."); +#endif +} + +/** + * Callback invoked when uart received data + * @param uart_svc Reference object to the service where the data + * arrived. In this example it is clientUart + */ +void bleuart_rx_callback(BLEClientUart& uart_svc) +{ + while ( uart_svc.available() ) + { + Serial.print( (char) uart_svc.read() ); + } +} + +bool pairing_passkey_callback(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) +{ + Serial.println("Pairing Passkey"); + Serial.printf(" %.3s %.3s\n\n", passkey, passkey+3); + +#ifdef USE_ARCADA + tft->fillScreen(ARCADA_BLACK); + tft->println("Pairing Passkey\n"); + tft->setTextColor(ARCADA_YELLOW); + tft->setTextSize(4); + tft->printf(" %.3s %.3s\n", passkey, passkey+3); + + tft->setTextColor(ARCADA_WHITE); + tft->setTextSize(2); +#endif + + // match_request means peer wait for our approval (return true) + if (match_request) + { + Serial.println("Do you want to pair"); + Serial.println("Press Button Left to decline, Button Right to Accept"); + + // timeout for pressing button + uint32_t start_time = millis(); + +#ifdef USE_ARCADA + tft->println("\nDo you accept ?\n\n"); + tft->setTextSize(3); + + // Yes <-> No on CPB is reversed since GIZMO TFT is on the back of CPB + #if ARDUINO_NRF52840_CIRCUITPLAY + tft->setTextColor(ARCADA_GREEN); + tft->print("< Yes"); + tft->setTextColor(ARCADA_RED); + tft->println(" No >"); + #else + tft->setTextColor(ARCADA_RED); + tft->print("< No"); + tft->setTextColor(ARCADA_GREEN); + tft->println(" Yes >"); + #endif + + tft->setTextColor(ARCADA_WHITE); + tft->setTextSize(2); + tft->println(); + + // wait until either button is pressed (30 seconds timeout) + uint32_t justReleased; + do + { + if ( millis() > start_time + 30000 ) break; + + arcada.readButtons(); + justReleased = arcada.justReleasedButtons(); + } while ( !(justReleased & (ARCADA_BUTTONMASK_LEFT | ARCADA_BUTTONMASK_RIGHT) ) ); + + // Right = accept + if (justReleased & ARCADA_BUTTONMASK_RIGHT) return true; + + // Left = decline + if (justReleased & ARCADA_BUTTONMASK_LEFT) return false; + +#else + // wait until either button is pressed (30 seconds timeout) + while( digitalRead(BUTTON_YES) && digitalRead(BUTTON_NO) ) + { + if ( millis() > start_time + 30000 ) break; + } + + if ( 0 == digitalRead(BUTTON_YES) ) return true; + + if ( 0 == digitalRead(BUTTON_NO) ) return false; +#endif + + return false; + } + + return true; +} + +void pairing_complete_callback(uint16_t conn_handle, uint8_t auth_status) +{ + BLEConnection* conn = Bluefruit.Connection(conn_handle); + + if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) + { + Serial.println("Succeeded"); + }else + { + Serial.println("Failed"); + } + +#ifdef USE_ARCADA + if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) + { + tft->setTextColor(ARCADA_GREEN); + tft->print("Succeeded "); + }else + { + tft->setTextColor(ARCADA_RED); + tft->print("Failed "); + + // disconnect + conn->disconnect(); + } + + tft->setTextColor(ARCADA_WHITE); + tft->setTextSize(2); +#endif +} + +void connection_secured_callback(uint16_t conn_handle) +{ + BLEConnection* conn = Bluefruit.Connection(conn_handle); + + if ( !conn->secured() ) + { + // It is possible that connection is still not secured by this time. + // This happens (central only) when we try to encrypt connection using stored bond keys + // but peer reject it (probably it remove its stored key). + // Therefore we will request an pairing again --> callback again when encrypted + conn->requestPairing(); + } + else + { + Serial.println("Secured"); + + #ifdef USE_ARCADA + tft->setTextColor(ARCADA_YELLOW); + tft->println("secured"); + tft->setTextColor(ARCADA_WHITE); + #endif + + Serial.print("Discovering BLE Uart Service ... "); + if ( clientUart.discover(conn_handle) ) + { + Serial.println("Found it"); + + Serial.println("Enable TXD's notify"); + clientUart.enableTXD(); + + Serial.println("Ready to receive from peripheral"); + }else + { + Serial.println("Found NONE"); + + // disconnect since we couldn't find bleuart service + conn->disconnect(); + } + } +} + +void loop() +{ + uint16_t const conn_handle = 0; // this example only support 1 connection + BLEConnection* conn = Bluefruit.Connection(conn_handle); + + // skip if connection not exist or not connected + if ( !conn && conn->connected() ) return; + + // In this example we only read & write when connection is secured + if ( conn->secured() ) + { + // Not discovered yet + if ( clientUart.discovered() ) + { + // Discovered means in working state + // Get Serial input and send to Peripheral + if ( Serial.available() ) + { + delay(2); // delay a bit for all characters to arrive + + char str[20+1] = { 0 }; + Serial.readBytes(str, 20); + + clientUart.print( str ); + } + } + } +} diff --git a/libraries/Bluefruit52Lib/examples/Peripheral/ancs/ancs.ino b/libraries/Bluefruit52Lib/examples/Peripheral/ancs/ancs.ino index b30104d7e..11551661f 100644 --- a/libraries/Bluefruit52Lib/examples/Peripheral/ancs/ancs.ino +++ b/libraries/Bluefruit52Lib/examples/Peripheral/ancs/ancs.ino @@ -23,7 +23,8 @@ BLEClientDis bleClientDis; BLEAncs bleancs; -char buffer[128]; +#define BUFSIZE 128 +char buffer[BUFSIZE]; // Check BLEAncs.h for AncsNotification_t const char* EVENT_STR[] = { "Added", "Modified", "Removed" }; @@ -37,7 +38,7 @@ const char* CAT_STR [] = void setup() { Serial.begin(115200); - while ( !Serial ) delay(10); // for nrf52840 with native usb +// while ( !Serial ) delay(10); // for nrf52840 with native usb Serial.println("Bluefruit52 BLE ANCS Example"); Serial.println("----------------------------\n"); @@ -52,10 +53,12 @@ void setup() Bluefruit.begin(); Bluefruit.setTxPower(4); // Check bluefruit.h for supported values - Bluefruit.setName("Bluefruit52"); Bluefruit.Periph.setConnectCallback(connect_callback); Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + // Set connection secured callback, invoked when connection is encrypted + Bluefruit.Security.setSecuredCallback(connection_secured_callback); + // Configure DIS client bleClientDis.begin(); @@ -108,6 +111,8 @@ void loop() void connect_callback(uint16_t conn_handle) { + BLEConnection* conn = Bluefruit.Connection(conn_handle); + Serial.println("Connected"); Serial.print("Discovering DIS ... "); @@ -116,16 +121,16 @@ void connect_callback(uint16_t conn_handle) Serial.println("Discovered"); // Read and print Manufacturer string - memset(buffer, 0, sizeof(buffer)); - if ( bleClientDis.getManufacturer(buffer, sizeof(buffer)) ) + memset(buffer, 0, BUFSIZE); + if ( bleClientDis.getManufacturer(buffer, BUFSIZE) ) { Serial.print("Manufacturer: "); Serial.println(buffer); } // Read and print Model Number string - memset(buffer, 0, sizeof(buffer)); - if ( bleClientDis.getModel(buffer, sizeof(buffer)) ) + memset(buffer, 0, BUFSIZE); + if ( bleClientDis.getModel(buffer, BUFSIZE) ) { Serial.print("Model: "); Serial.println(buffer); @@ -141,54 +146,77 @@ void connect_callback(uint16_t conn_handle) // ANCS requires pairing to work, it makes sense to request security here as well Serial.print("Attempting to PAIR with the iOS device, please press PAIR on your phone ... "); - if ( Bluefruit.requestPairing(conn_handle) ) + conn->requestPairing(); + } +} + +void connection_secured_callback(uint16_t conn_handle) +{ + BLEConnection* conn = Bluefruit.Connection(conn_handle); + + if ( !conn->secured() ) + { + // It is possible that connection is still not secured by this time. + // This happens (central only) when we try to encrypt connection using stored bond keys + // but peer reject it (probably it remove its stored key). + // Therefore we will request an pairing again --> callback again when encrypted + conn->requestPairing(); + } + else + { + Serial.println("Secured"); + + if ( bleancs.discovered() ) { - Serial.println("Done"); Serial.println("Enabling notifications"); Serial.println(); bleancs.enableNotification(); - - Serial.println("| Event | Category (count) | Title | Message | App ID | App Name |"); - Serial.println("---------------------------------------------------------------------------------------------------------------"); } } } void ancs_notification_callback(AncsNotification_t* notif) { - int n; - Serial.printf("| %-8s | ", EVENT_STR[notif->eventID]); + uint32_t const uid = notif->uid; + + // Application ID & Name + char appID[128] = { 0 }; + bleancs.getAppID(uid, appID, sizeof(appID)); - // Print Category with padding - n = Serial.printf("%s (%d)", CAT_STR[notif->categoryID], notif->categoryCount); - for (int i=n; i<20; i++) Serial.print(' '); - Serial.print(" | "); + memset(buffer, 0, BUFSIZE); + bleancs.getAppAttribute(appID, ANCS_APP_ATTR_DISPLAY_NAME, buffer, BUFSIZE); + + Serial.printf("%-15s (%s)\n", buffer, appID); // Get notification Title - // iDevice often includes Unicode "Bidirection Text Control" in the Title. - // Most strings have U+202D at the beginning and U+202C at the end. You may - // want to remove them. - // U+202D is E2-80-AD, U+202C is E2-80-AC in UTF-8 - memset(buffer, 0, sizeof(buffer)); - bleancs.getAttribute(notif->uid, ANCS_ATTR_TITLE, buffer, sizeof(buffer)); - Serial.printf("%-14s | ", buffer); + // iDevice often include Unicode "Bidirection Text Control" in the Title. + // Mostly are U+202D as beginning and U+202C as ending. Let's remove them + memset(buffer, 0, BUFSIZE); + if ( bleancs.getTitle(uid, buffer, BUFSIZE) ) + { + char u202D[3] = { 0xE2, 0x80, 0xAD }; // U+202D in UTF-8 + char u202C[3] = { 0xE2, 0x80, 0xAC }; // U+202C in UTF-8 + + int len = strlen(buffer); + + if ( 0 == memcmp(&buffer[len-3], u202C, 3) ) + { + len -= 3; + buffer[len] = 0; // chop ending U+202C + } + + if ( 0 == memcmp(buffer, u202D, 3) ) + { + memmove(buffer, buffer+3, len-2); // move null-terminator as well + } + } + Serial.printf("%-15s %s\n", buffer, EVENT_STR[notif->eventID]); + // Get notification Message - memset(buffer, 0, sizeof(buffer)); - bleancs.getAttribute(notif->uid, ANCS_ATTR_MESSAGE, buffer, sizeof(buffer)); - Serial.printf("%-15s | ", buffer); - - // Get App ID and store in the app_id variable - char app_id[64] = { 0 }; - memset(buffer, 0, sizeof(buffer)); - bleancs.getAttribute(notif->uid, ANCS_ATTR_APP_IDENTIFIER, buffer, sizeof(buffer)); - strcpy(app_id, buffer); - Serial.printf("%-20s | ", app_id); - - // Get Application Name - memset(buffer, 0, sizeof(buffer)); - bleancs.getAppAttribute(app_id, ANCS_APP_ATTR_DISPLAY_NAME, buffer, sizeof(buffer)); - Serial.printf("%-15s | ", buffer); + memset(buffer, 0, BUFSIZE); + bleancs.getMessage(uid, buffer, BUFSIZE); + Serial.printf(" %s\n", buffer); Serial.println(); diff --git a/libraries/Bluefruit52Lib/examples/Peripheral/ancs_arcada/.cluenrf52840.test.only b/libraries/Bluefruit52Lib/examples/Peripheral/ancs_arcada/.cluenrf52840.test.only new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/Bluefruit52Lib/examples/Peripheral/ancs_arcada/.cplaynrf52840.test.only b/libraries/Bluefruit52Lib/examples/Peripheral/ancs_arcada/.cplaynrf52840.test.only new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/Bluefruit52Lib/examples/Peripheral/ancs_arcada/ancs_arcada.ino b/libraries/Bluefruit52Lib/examples/Peripheral/ancs_arcada/ancs_arcada.ino new file mode 100644 index 000000000..c1c1bcbba --- /dev/null +++ b/libraries/Bluefruit52Lib/examples/Peripheral/ancs_arcada/ancs_arcada.ino @@ -0,0 +1,412 @@ +/********************************************************************* + This is an example for our nRF52 based Bluefruit LE modules + + Pick one up today in the adafruit shop! + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* + * This sketch is similar to 'ancs', but it uses TFT to display + * incoming ANCS alerts. Supported boards are: + * - CLUE https://www.adafruit.com/product/4500 + * - Circuit Playground Bluefruit + TFT Gizmo + * - https://www.adafruit.com/product/4333 + * - https://www.adafruit.com/product/4367 + * + * Button Left: Next or Answer call + * Button Right: Previous or Reject call + * + * Note on CPB button A is RIGHT and button B is LEFT, this is due to + * the TFT is on the back of the board. + */ +#include +#include + +Adafruit_Arcada arcada; +Adafruit_SPITFT* tft; + +/*------------- Notification List -------------*/ +#define MAX_COUNT 20 +#define BUFSIZE 64 + +typedef struct +{ + AncsNotification_t ntf; + char title[BUFSIZE]; + char message[BUFSIZE]; + char app_name[BUFSIZE]; +} MyNotif_t; + +MyNotif_t myNotifs[MAX_COUNT] = { 0 }; + +// Number of notifications +int notifCount = 0; + +/*------------- Display Management -------------*/ +#define ONSCREEN_TIME 10000 // On-screen time for each notification + +int activeIndex = 0; // Index of currently displayed notification +int displayIndex = -1; // Index of notification about to display + +uint32_t drawTime = 0; // Last time oled display notification + +/*------------- BLE Client Service-------------*/ +BLEAncs bleancs; + +void setup() +{ + arcada.arcadaBegin(); + arcada.displayBegin(); + arcada.setBacklight(255); + + tft = arcada.display; + tft->setCursor(0, 0); + tft->setTextWrap(true); + tft->setTextSize(2); + + tft->println("Advertising..."); + + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + + Bluefruit.begin(); + Bluefruit.setTxPower(4); // Check bluefruit.h for supported values + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // Set connection secured callback, invoked when connection is encrypted + Bluefruit.Security.setSecuredCallback(connection_secured_callback); + + // Configure and Start Service + bleancs.begin(); + bleancs.setNotificationCallback(ancs_notification_callback); + + // Set up and start advertising + startAdv(); +} + +void startAdv(void) +{ + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + + // Include ANCS 128-bit uuid + Bluefruit.Advertising.addService(bleancs); + + // Secondary Scan Response packet (optional) + // Since there is no room for 'Name' in Advertising packet + Bluefruit.ScanResponse.addName(); + + /* Start Advertising + * - Enable auto advertising if disconnected + * - Interval: fast mode = 20 ms, slow mode = 152.5 ms + * - Timeout for fast mode is 30 seconds + * - Start(timeout) with timeout = 0 will advertise forever (until connected) + * + * For recommended advertising interval + * https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds +} + +void loop() +{ + // This example only support 1 connection + uint16_t const conn_handle = 0; + BLEConnection* conn = Bluefruit.Connection(conn_handle); + + // connection exist, connected, and secured + if ( !(conn && conn->connected() && conn->secured()) ) return; + + // If service is not yet discovered + if ( !bleancs.discovered() ) return; + + // No notifications, do nothing + if ( notifCount == 0 ) return; + + arcada.readButtons(); + uint8_t const justReleased = arcada.justReleasedButtons(); + uint8_t buttonLeft = (justReleased & ARCADA_BUTTONMASK_LEFT); + uint8_t buttonRight = (justReleased & ARCADA_BUTTONMASK_RIGHT); + +#ifdef ARDUINO_NRF52840_CLUE + // swap button left & right on CLUE since the screen is on opposite side + uint8_t temp = buttonLeft; + buttonLeft = buttonRight; + buttonRight = temp; +#endif + + if ( myNotifs[activeIndex].ntf.categoryID == ANCS_CAT_INCOMING_CALL ) + { + /* Incoming call event + * - Button A to accept call + * - Button B to decline call + */ + if ( buttonLeft ) bleancs.actPositive(myNotifs[activeIndex].ntf.uid); + if ( buttonRight ) bleancs.actNegative(myNotifs[activeIndex].ntf.uid); + } + else + { + /* Normal events navigation (wrap around) + * - Button A to display previous notification + * - Button B to display next notification + * + * When a notification is display ONSCREEN_TIME, + * we will display the next one + */ + if ( buttonLeft ) + { + displayIndex = (activeIndex != 0) ? (activeIndex-1) : (notifCount-1) ; + } + + if ( buttonRight ) + { + displayIndex = (activeIndex != (notifCount-1)) ? (activeIndex + 1) : 0; + } + + // Display requested notification + if ( displayIndex >= 0 ) + { + activeIndex = displayIndex; + displayIndex = -1; + + displayNotification(activeIndex); + drawTime = millis(); // Save time we draw + } + // Display next notification if time is up + else if ( drawTime + ONSCREEN_TIME < millis() ) + { + activeIndex = (activeIndex+1)%notifCount; + + displayNotification(activeIndex); + drawTime = millis(); // Save time we draw + } + } +} + +/** + * Display notification contents to oled screen + * @param index index of notification + */ +void displayNotification(int index) +{ + // safeguard + if ( index < 0 || (index >= notifCount) ) return; + + // let's Turn on and off RED LED when we draw to get attention + digitalWrite(LED_BUILTIN, HIGH); + + /*------------- Display to OLED -------------*/ + MyNotif_t* myNtf = &myNotifs[index]; + + tft->fillScreen(ARCADA_BLACK); + tft->setCursor(0, 0); + + tft->setTextSize(3); + tft->setTextColor(ARCADA_GREEN); + tft->println(myNtf->app_name); + tft->println(); + + // Incoming call event, display a bit differently + if ( myNtf->ntf.categoryID == ANCS_CAT_INCOMING_CALL ) + { + tft->setTextColor(ARCADA_YELLOW); + tft->println(myNtf->title); + tft->println(); + + tft->println("calling ..."); + tft->println(); + + tft->setTextSize(2); + tft->setTextColor(ARCADA_GREEN); + tft->print("< Answer"); + tft->setTextColor(ARCADA_RED); + tft->println(" Reject >"); + }else + { + tft->setTextSize(2); + tft->setTextColor(ARCADA_GREENYELLOW); + tft->printf("%02d/%02d\n", index+1, notifCount); + tft->println(); + + tft->setTextColor(ARCADA_YELLOW); + tft->println(myNtf->title); + tft->println(); + + tft->setTextColor(ARCADA_WHITE); + tft->println(myNtf->message); + tft->println(); + } + + digitalWrite(LED_BUILTIN, LOW); +} + +/** + * Connect Callback + * Perform ANCS discovering, request Pairing + */ +void connect_callback(uint16_t conn_handle) +{ + BLEConnection* conn = Bluefruit.Connection(conn_handle); + + tft->println("Connected"); + tft->print("Discovering ... "); + + if ( bleancs.discover( conn_handle ) ) + { + tft->println("OK"); + + // ANCS requires secured connection + // request Pairing if not bonded + tft->print("Paring ... "); + conn->requestPairing(); + }else + { + // disconnect if couldn't find ancs service + conn->disconnect(); + tft->println("Failed"); + } +} + +void connection_secured_callback(uint16_t conn_handle) +{ + BLEConnection* conn = Bluefruit.Connection(conn_handle); + + if ( !conn->secured() ) + { + // It is possible that connection is still not secured by this time. + // This happens (central only) when we try to encrypt connection using stored bond keys + // but peer reject it (probably it remove its stored key). + // Therefore we will request an pairing again --> callback again when encrypted + conn->requestPairing(); + } + else + { + Serial.println("Secured"); + + tft->println("Secured"); + + if ( bleancs.discovered() ) + { + bleancs.enableNotification(); + tft->println("Ready to receive"); + } + } +} + +/** + * Notification callback + * @param notif Notification from iDevice + * + * Save/Modify notification into myNotifs struct to display later + */ +void ancs_notification_callback(AncsNotification_t* notif) +{ + if (notif->eventID == ANCS_EVT_NOTIFICATION_ADDED ) + { + myNotifs[ notifCount ].ntf = *notif; + + /*------------- Retrieve Title, Message, App Name -------------*/ + MyNotif_t* myNtf = &myNotifs[notifCount]; + uint32_t uid = myNtf->ntf.uid; + + // iDevice often include Unicode "Bidirection Text Control" in the Title. + // Mostly are U+202D as beginning and U+202C as ending. Let's remove them + if ( bleancs.getTitle(uid, myNtf->title, BUFSIZE) ) + { + char u202D[3] = { 0xE2, 0x80, 0xAD }; // U+202D in UTF-8 + char u202C[3] = { 0xE2, 0x80, 0xAC }; // U+202C in UTF-8 + + int len = strlen(myNtf->title); + + if ( 0 == memcmp(&myNtf->title[len-3], u202C, 3) ) + { + len -= 3; + myNtf->title[len] = 0; // chop ending U+202C + } + + if ( 0 == memcmp(myNtf->title, u202D, 3) ) + { + memmove(myNtf->title, myNtf->title+3, len-2); // move null-terminator as well + } + } + + bleancs.getMessage(uid, myNtf->message , BUFSIZE); + bleancs.getAppName(uid, myNtf->app_name, BUFSIZE); + + displayIndex = notifCount++; // display new notification + }else if (notif->eventID == ANCS_EVT_NOTIFICATION_REMOVED ) + { + for(int i=0; iuid == myNotifs[i].ntf.uid ) + { + // remove by swapping with the last one + notifCount--; + myNotifs[i] = myNotifs[notifCount]; + + // Invalid removed data + memset(&myNotifs[notifCount], 0, sizeof(MyNotif_t)); + + if (activeIndex == notifCount) + { + // If remove the last notification, adjust display index + displayIndex = notifCount-1; + }else if (activeIndex == i) + { + // Re-draw if remove currently active one + displayIndex = activeIndex; + } + + break; + } + } + }else + { + // Modification + for(int i=0; iuid == myNotifs[i].ntf.uid ) + { + // Display modification + displayIndex = i; + break; + } + } + } +} + +/** + * Callback invoked when a connection is dropped + * @param conn_handle connection where this event happens + * @param reason is a BLE_HCI_STATUS_CODE which can be found in ble_hci.h + */ +void disconnect_callback(uint16_t conn_handle, uint8_t reason) +{ + (void) conn_handle; + (void) reason; + + // reset notification array + notifCount = 0; + activeIndex = 0; + displayIndex = -1; + + memset(myNotifs, 0, sizeof(myNotifs)); + + tft->fillScreen(ARCADA_BLACK); + tft->setCursor(0, 0); + tft->println("Not connected"); +} diff --git a/libraries/Bluefruit52Lib/examples/Peripheral/ancs_oled/ancs_oled.ino b/libraries/Bluefruit52Lib/examples/Peripheral/ancs_oled/ancs_oled.ino index 07e0b5be7..141d8fddd 100644 --- a/libraries/Bluefruit52Lib/examples/Peripheral/ancs_oled/ancs_oled.ino +++ b/libraries/Bluefruit52Lib/examples/Peripheral/ancs_oled/ancs_oled.ino @@ -30,19 +30,11 @@ #if defined ARDUINO_NRF52832_FEATHER // Feather nRF52832 #define BUTTON_A 31 - #define BUTTON_B 30 #define BUTTON_C 27 -#elif defined ARDUINO_NRF52840_CIRCUITPLAY - // Circuit Playground nRF52840 - FYI doesnt work probably because of button polarity! - #define BUTTON_A 4 // left button - #define BUTTON_B 7 // center switch - #define BUTTON_C 5 // right button - #else // Default for others #define BUTTON_A 9 - #define BUTTON_B 6 #define BUTTON_C 5 #endif @@ -67,7 +59,7 @@ MyNotif_t myNotifs[MAX_COUNT] = { 0 }; int notifCount = 0; /*------------- Display Management -------------*/ -#define ONSCREEN_TIME 5000 // On-screen time for each notification +#define ONSCREEN_TIME 10000 // On-screen time for each notification int activeIndex = 0; // Index of currently displayed notification int displayIndex = -1; // Index of notification about to display @@ -81,7 +73,6 @@ void setup() { // Button configured pinMode(BUTTON_A, INPUT_PULLUP); - pinMode(BUTTON_B, INPUT_PULLUP); pinMode(BUTTON_C, INPUT_PULLUP); // init with the I2C addr 0x3C (for the 128x32) and show splashscreen @@ -94,14 +85,17 @@ void setup() // Config the peripheral connection with maximum bandwidth // more SRAM required by SoftDevice // Note: All config***() function must be called before begin() - //Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); Bluefruit.begin(); Bluefruit.setTxPower(4); // Check bluefruit.h for supported values - Bluefruit.setName("Bluefruit52"); + Bluefruit.Periph.setConnectCallback(connect_callback); Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + // Set connection secured callback, invoked when connection is encrypted + Bluefruit.Security.setSecuredCallback(connection_secured_callback); + // Configure and Start Service bleancs.begin(); bleancs.setNotificationCallback(ancs_notification_callback); @@ -148,6 +142,13 @@ void startAdv(void) void loop() { + // This example only support 1 connection + uint16_t const conn_handle = 0; + BLEConnection* conn = Bluefruit.Connection(conn_handle); + + // connection exist, connected, and secured + if ( !(conn && conn->connected() && conn->secured()) ) return; + // If service is not yet discovered if ( !bleancs.discovered() ) return; @@ -261,6 +262,8 @@ void displayNotification(int index) */ void connect_callback(uint16_t conn_handle) { + BLEConnection* conn = Bluefruit.Connection(conn_handle); + oled.clearDisplay(); oled.setCursor(0, 0); oled.println("Connected."); @@ -272,20 +275,9 @@ void connect_callback(uint16_t conn_handle) oled.println("OK"); // ANCS requires pairing to work - oled.print("Paring ... "); - - oled.display(); - - if ( Bluefruit.requestPairing(conn_handle) ) - { - oled.println("OK"); - - bleancs.enableNotification(); - oled.println("Receiving ..."); - }else - { - oled.println("Failed"); - } + // request Pairing if not bonded + oled.println("Paring ... "); + conn->requestPairing(); }else { oled.println("Failed"); @@ -294,6 +286,31 @@ void connect_callback(uint16_t conn_handle) oled.display(); } +void connection_secured_callback(uint16_t conn_handle) +{ + BLEConnection* conn = Bluefruit.Connection(conn_handle); + + if ( !conn->secured() ) + { + // It is possible that connection is still not secured by this time. + // This happens (central only) when we try to encrypt connection using stored bond keys + // but peer reject it (probably it remove its stored key). + // Therefore we will request an pairing again --> callback again when encrypted + conn->requestPairing(); + } + else + { + Serial.println("Secured"); + + if ( bleancs.discovered() ) + { + bleancs.enableNotification(); + oled.println("Ready to receive"); + oled.display(); + } + } +} + /** * Notification callback * @param notif Notification from iDevice @@ -433,8 +450,7 @@ uint32_t readPressedButtons(void) // Take current read and masked with BUTTONs // Note: Bitwise inverted since buttons are active (pressed) LOW - uint32_t debounced = ~(*portInputRegister( digitalPinToPort(0) )); - debounced &= (bit(BUTTON_A) | bit(BUTTON_B) | bit(BUTTON_C)); + uint32_t debounced = ~( (digitalRead(BUTTON_A) << BUTTON_A) | (digitalRead(BUTTON_C) << BUTTON_C) ); // Copy current state into array states[ (index & (MAX_CHECKS-1)) ] = debounced; diff --git a/libraries/Bluefruit52Lib/examples/Peripheral/bleuart/bleuart.ino b/libraries/Bluefruit52Lib/examples/Peripheral/bleuart/bleuart.ino index 2287c4940..dc0bfb52f 100644 --- a/libraries/Bluefruit52Lib/examples/Peripheral/bleuart/bleuart.ino +++ b/libraries/Bluefruit52Lib/examples/Peripheral/bleuart/bleuart.ino @@ -34,7 +34,7 @@ void setup() Serial.println("---------------------------\n"); // Setup the BLE LED to be enabled on CONNECT - // Note: This is actually the default behaviour, but provided + // Note: This is actually the default behavior, but provided // here in case you want to control this LED manually via PIN 19 Bluefruit.autoConnLed(true); diff --git a/libraries/Bluefruit52Lib/examples/Peripheral/clearbonds/clearbonds.ino b/libraries/Bluefruit52Lib/examples/Peripheral/clearbonds/clearbonds.ino index d6770d449..5a9720556 100644 --- a/libraries/Bluefruit52Lib/examples/Peripheral/clearbonds/clearbonds.ino +++ b/libraries/Bluefruit52Lib/examples/Peripheral/clearbonds/clearbonds.ino @@ -22,7 +22,11 @@ void setup() { Serial.begin(115200); - while ( !Serial ) delay(10); // for nrf52840 with native usb + +#if CFG_DEBUG + // Blocking wait for connection when debug mode is enabled via IDE + while ( !Serial ) yield(); +#endif Serial.println("Bluefruit52 Clear Bonds Example"); Serial.println("-------------------------------\n"); @@ -34,7 +38,7 @@ void setup() bond_print_list(BLE_GAP_ROLE_PERIPH); bond_print_list(BLE_GAP_ROLE_CENTRAL); - Bluefruit.clearBonds(); + Bluefruit.Periph.clearBonds(); Bluefruit.Central.clearBonds(); Serial.println(); diff --git a/libraries/Bluefruit52Lib/examples/Peripheral/client_cts/client_cts.ino b/libraries/Bluefruit52Lib/examples/Peripheral/client_cts/client_cts.ino index d354c81a7..15458b780 100644 --- a/libraries/Bluefruit52Lib/examples/Peripheral/client_cts/client_cts.ino +++ b/libraries/Bluefruit52Lib/examples/Peripheral/client_cts/client_cts.ino @@ -33,7 +33,7 @@ BLEClientCts bleCTime; void setup() { Serial.begin(115200); - while ( !Serial ) delay(10); // for nrf52840 with native usb +// while ( !Serial ) delay(10); // for nrf52840 with native usb Serial.println("Bluefruit52 BLE Client Current Time Example"); Serial.println("-------------------------------------------\n"); @@ -48,10 +48,13 @@ void setup() Bluefruit.begin(); Bluefruit.setTxPower(4); // Check bluefruit.h for supported values - Bluefruit.setName("Bluefruit52"); + Bluefruit.Periph.setConnectCallback(connect_callback); Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + // Set connection secured callback, invoked when connection is encrypted + Bluefruit.Security.setSecuredCallback(connection_secured_callback); + // Configure CTS client bleCTime.begin(); @@ -94,12 +97,16 @@ void startAdv(void) void loop() { + // This example only support 1 connection + uint16_t const conn_handle = 0; + BLEConnection* conn = Bluefruit.Connection(conn_handle); + + // connection exist, connected, and secured + if ( !(conn && conn->connected() && conn->secured()) ) return; + // Skip if service is not yet discovered if ( !bleCTime.discovered() ) return; - // Skip if service connection is not paired/secured - if ( !Bluefruit.connPaired( bleCTime.connHandle() ) ) return; - // Get Time from iOS once per second // Note it is not advised to update this quickly // Application should use local clock and update time after @@ -113,6 +120,8 @@ void loop() void connect_callback(uint16_t conn_handle) { + BLEConnection* conn = Bluefruit.Connection(conn_handle); + Serial.println("Connected"); Serial.print("Discovering CTS ... "); @@ -120,11 +129,31 @@ void connect_callback(uint16_t conn_handle) { Serial.println("Discovered"); - // iOS requires pairing to work, it makes sense to request security here as well - Serial.print("Attempting to PAIR with the iOS device, please press PAIR on your phone ... "); - if ( Bluefruit.requestPairing(conn_handle) ) + // Current Time Service requires pairing to work + // request Pairing if not bonded + Serial.println("Attempting to PAIR with the iOS device, please press PAIR on your phone ... "); + conn->requestPairing(); + } +} + +void connection_secured_callback(uint16_t conn_handle) +{ + BLEConnection* conn = Bluefruit.Connection(conn_handle); + + if ( !conn->secured() ) + { + // It is possible that connection is still not secured by this time. + // This happens (central only) when we try to encrypt connection using stored bond keys + // but peer reject it (probably it remove its stored key). + // Therefore we will request an pairing again --> callback again when encrypted + conn->requestPairing(); + } + else + { + Serial.println("Secured"); + + if ( bleCTime.discovered() ) { - Serial.println("Done"); Serial.println("Enabling Time Adjust Notify"); bleCTime.enableAdjust(); @@ -136,8 +165,6 @@ void connect_callback(uint16_t conn_handle) Serial.println(); } - - Serial.println(); } } diff --git a/libraries/Bluefruit52Lib/examples/Peripheral/client_cts_oled/client_cts_oled.ino b/libraries/Bluefruit52Lib/examples/Peripheral/client_cts_oled/client_cts_oled.ino index 0372cf1b4..b8e21451c 100644 --- a/libraries/Bluefruit52Lib/examples/Peripheral/client_cts_oled/client_cts_oled.ino +++ b/libraries/Bluefruit52Lib/examples/Peripheral/client_cts_oled/client_cts_oled.ino @@ -54,10 +54,13 @@ void setup() Bluefruit.begin(); Bluefruit.setTxPower(4); // Check bluefruit.h for supported values - Bluefruit.setName("Bluefruit52"); + Bluefruit.Periph.setConnectCallback(connect_callback); Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + // Set connection secured callback, invoked when connection is encrypted + Bluefruit.Security.setSecuredCallback(connection_secured_callback); + // Configure CTS client bleCTime.begin(); @@ -103,12 +106,16 @@ void startAdv(void) void loop() { + // This example only support 1 connection + uint16_t const conn_handle = 0; + BLEConnection* conn = Bluefruit.Connection(conn_handle); + + // connection exist, connected, and secured + if ( !(conn && conn->connected() && conn->secured()) ) return; + // Skip if service is not yet discovered if ( !bleCTime.discovered() ) return; - // Skip if service connection is not paired/secured - if ( !Bluefruit.connPaired( bleCTime.connHandle() ) ) return; - // Get Time from iOS once per second // Note it is not advised to update this quickly // Application should use local clock and update time after @@ -123,6 +130,8 @@ void loop() void connect_callback(uint16_t conn_handle) { + BLEConnection* conn = Bluefruit.Connection(conn_handle); + oled.clearDisplay(); oled.setCursor(0, 0); oled.println("Connected."); @@ -133,24 +142,42 @@ void connect_callback(uint16_t conn_handle) { oled.println("OK"); - // ANCS requires pairing to work + // Current Time Service requires pairing to work + // request Pairing if not bonded oled.print("Paring ... "); - oled.display(); - if ( Bluefruit.requestPairing(conn_handle) ) - { - oled.println("OK"); + conn->requestPairing(); + } +} +void connection_secured_callback(uint16_t conn_handle) +{ + BLEConnection* conn = Bluefruit.Connection(conn_handle); + + if ( !conn->secured() ) + { + // It is possible that connection is still not secured by this time. + // This happens (central only) when we try to encrypt connection using stored bond keys + // but peer reject it (probably it remove its stored key). + // Therefore we will request an pairing again --> callback again when encrypted + conn->requestPairing(); + } + else + { + oled.println("Secured"); + + if ( bleCTime.discovered() ) + { bleCTime.enableAdjust(); oled.println("Receiving Time..."); + oled.display(); + bleCTime.getCurrentTime(); bleCTime.getLocalTimeInfo(); } } - - oled.display(); } void printTime(void) diff --git a/libraries/Bluefruit52Lib/examples/Peripheral/hid_camerashutter/hid_camerashutter.ino b/libraries/Bluefruit52Lib/examples/Peripheral/hid_camerashutter/hid_camerashutter.ino index 39aff0ac9..da03966d2 100644 --- a/libraries/Bluefruit52Lib/examples/Peripheral/hid_camerashutter/hid_camerashutter.ino +++ b/libraries/Bluefruit52Lib/examples/Peripheral/hid_camerashutter/hid_camerashutter.ino @@ -14,21 +14,24 @@ /* * This sketch uses the HID Consumer Key API to send the Volume Down - * key when PIN_SHUTTER is grounded. This will cause your mobile device + * key when pinShutter is grounded. This will cause your mobile device * to capture a photo when you are in the camera app. - * - * For Feather nRF52840 PIN_SHUTTER is conveniently user switch. */ #include BLEDis bledis; BLEHidAdafruit blehid; -#define PIN_SHUTTER 7 +// Use on-board button if available, else use A0 pin +#ifdef PIN_BUTTON1 + uint8_t pinShutter = PIN_BUTTON1; +#else + uint8_t pinShutter = A0; +#endif void setup() { - pinMode(PIN_SHUTTER, INPUT_PULLUP); + pinMode(pinShutter, INPUT_PULLUP); Serial.begin(115200); while ( !Serial ) delay(10); // for nrf52840 with native usb @@ -41,7 +44,7 @@ void setup() Serial.println("then open the camera application"); Serial.println(); - Serial.printf("Set pin %d to GND to capture a photo\n", PIN_SHUTTER); + Serial.printf("Set pin %d to GND to capture a photo\n", pinShutter); Serial.println(); Bluefruit.begin(); @@ -104,14 +107,14 @@ void startAdv(void) void loop() { // Skip if shutter pin is not Ground - if ( digitalRead(PIN_SHUTTER) == 1 ) return; + if ( digitalRead(pinShutter) == 1 ) return; // Make sure we are connected and bonded/paired for (uint16_t conn_hdl=0; conn_hdl < BLE_MAX_CONNECTION; conn_hdl++) { BLEConnection* connection = Bluefruit.Connection(conn_hdl); - if ( connection && connection->connected() && connection->paired() ) + if ( connection && connection->connected() && connection->secured() ) { // Turn on red LED when we start sending data digitalWrite(LED_RED, 1); diff --git a/libraries/Bluefruit52Lib/examples/Peripheral/image_transfer/image_transfer.ino b/libraries/Bluefruit52Lib/examples/Peripheral/image_transfer/image_transfer.ino index 3ac9ee6a3..6b06ca019 100644 --- a/libraries/Bluefruit52Lib/examples/Peripheral/image_transfer/image_transfer.ino +++ b/libraries/Bluefruit52Lib/examples/Peripheral/image_transfer/image_transfer.ino @@ -55,14 +55,16 @@ // Default for others #define TFT_DC 10 #define TFT_CS 9 - #endif // 832 + #endif #if TFT_IN_USE == TFT_35_FEATHERWING #include "Adafruit_HX8357.h" Adafruit_HX8357 tft = Adafruit_HX8357(TFT_CS, TFT_DC); + #elif TFT_IN_USE == TFT_24_FEATHERWING #include Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC); + #else #error "TFT display is not supported" #endif // TFT diff --git a/libraries/Bluefruit52Lib/examples/Peripheral/nrf_blinky/nrf_blinky.ino b/libraries/Bluefruit52Lib/examples/Peripheral/nrf_blinky/nrf_blinky.ino index 70886e52d..00c1805a5 100644 --- a/libraries/Bluefruit52Lib/examples/Peripheral/nrf_blinky/nrf_blinky.ino +++ b/libraries/Bluefruit52Lib/examples/Peripheral/nrf_blinky/nrf_blinky.ino @@ -62,9 +62,9 @@ BLECharacteristic lsbLED(LBS_UUID_CHR_LED); // Use on-board button if available, else use A0 pin #ifdef PIN_BUTTON1 -uint8_t button = PIN_BUTTON1; + uint8_t button = PIN_BUTTON1; #else -uint8_t button = A0; + uint8_t button = A0; #endif uint8_t buttonState; diff --git a/libraries/Bluefruit52Lib/examples/Peripheral/pairing_passkey/pairing_passkey.ino b/libraries/Bluefruit52Lib/examples/Peripheral/pairing_passkey/pairing_passkey.ino new file mode 100644 index 000000000..b1f45758e --- /dev/null +++ b/libraries/Bluefruit52Lib/examples/Peripheral/pairing_passkey/pairing_passkey.ino @@ -0,0 +1,364 @@ +/********************************************************************* + This is an example for our nRF52 based Bluefruit LE modules + + Pick one up today in the adafruit shop! + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This sketch demonstrates Pairing process using dynamic Passkey. + * This sketch is essentially the same as bleuart.ino except the BLE Uart + * service requires Security Mode with Man-In-The-Middle protection i.e + * + * BLE Pairing procedure is complicated, it is advisable for users to go through + * these articles to get familiar with the procedure and terminology + * - https://www.bluetooth.com/blog/bluetooth-pairing-part-1-pairing-feature-exchange/ + * - https://www.bluetooth.com/blog/bluetooth-pairing-part-2-key-generation-methods/ + * - https://www.bluetooth.com/blog/bluetooth-pairing-passkey-entry/ + * - https://www.bluetooth.com/blog/bluetooth-pairing-part-4/ + * + * IF TFT enabled board such as CLUE is used, the passkey will also display on the + * TFT. Following boards with TFT are supported + * - Adafruit CLUE : https://www.adafruit.com/product/4500 + * - Circuit Playground Bluefruit: https://www.adafruit.com/product/4333 + * - TFT Gizmo : https://www.adafruit.com/product/4367 + */ + +#if defined(ARDUINO_NRF52840_CIRCUITPLAY) || defined(ARDUINO_NRF52840_CLUE) + #define USE_ARCADA +#endif + +#include +#include +#include + +#ifdef USE_ARCADA + #include + + Adafruit_Arcada arcada; + Adafruit_SPITFT* tft; + +#else + // Use built-in buttons if available, else use A0, A1 + #ifdef PIN_BUTTON1 + #define BUTTON_YES PIN_BUTTON1 + #else + #define BUTTON_YES A0 + #endif + + #ifdef PIN_BUTTON2 + #define BUTTON_NO PIN_BUTTON2 + #else + #define BUTTON_NO A1 + #endif +#endif + +// BLE Service +BLEUart bleuart; // uart over ble + +void setup() +{ + Serial.begin(115200); + + Serial.println("Bluefruit52 Pairing Display Example"); + Serial.println("-----------------------------------\n"); + +#ifdef USE_ARCADA + arcada.arcadaBegin(); + arcada.displayBegin(); + arcada.setBacklight(255); + + tft = arcada.display; + tft->setCursor(0, 0); + tft->setTextWrap(true); + tft->setTextSize(2); +#else + pinMode(BUTTON_YES, INPUT_PULLUP); + pinMode(BUTTON_NO, INPUT_PULLUP); +#endif + + // Setup the BLE LED to be enabled on CONNECT + // Note: This is actually the default behavior, but provided + // here in case you want to control this LED manually via PIN 19 + Bluefruit.autoConnLed(true); + + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + + Bluefruit.begin(); + Bluefruit.setTxPower(4); // Check bluefruit.h for supported values + + /* To use dynamic PassKey for pairing, we need to have + * - IO capacities at least DISPPLAY + * - Display only: user have to enter 6-digit passkey on their phone + * - DIsplay + Yes/No: user ony need to press Accept on both central and device + * - Register callback to display/print dynamic passkey for central + * + * For complete mapping of the IO Capabilities to Key Generation Method, check out this article + * https://www.bluetooth.com/blog/bluetooth-pairing-part-2-key-generation-methods/ + */ + Bluefruit.Security.setIOCaps(true, true, false); // display = true, yes/no = true, keyboard = false + Bluefruit.Security.setPairPasskeyCallback(pairing_passkey_callback); + + // Set complete callback to print the pairing result + Bluefruit.Security.setPairCompleteCallback(pairing_complete_callback); + + // Set connection secured callback, invoked when connection is encrypted + Bluefruit.Security.setSecuredCallback(connection_secured_callback); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // Configure and Start BLE Uart Service + // Set Permission to access BLE Uart is to require man-in-the-middle protection + // This will cause central to perform pairing with a generated passkey, the passkey will + // be printed on display or Serial and wait for our input + Serial.println("Configure BLE Uart to require man-in-the-middle protection for PIN pairing"); + bleuart.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); + bleuart.begin(); + +#ifdef USE_ARCADA + tft->fillScreen(ARCADA_BLACK); + tft->setTextColor(ARCADA_WHITE); + tft->setTextSize(2); + tft->setCursor(0, 0); + tft->print("Advertising..."); +#endif + + Serial.println("Please use Adafruit's Bluefruit LE app to connect in UART mode"); + Serial.println("Your phone should pop-up PIN input"); + Serial.println("Once connected, enter character(s) that you wish to send"); + + // Set up and start advertising + startAdv(); +} + +void startAdv(void) +{ + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + + // Include bleuart 128-bit uuid + Bluefruit.Advertising.addService(bleuart); + + // Secondary Scan Response packet (optional) + // Since there is no room for 'Name' in Advertising packet + Bluefruit.ScanResponse.addName(); + + /* Start Advertising + * - Enable auto advertising if disconnected + * - Interval: fast mode = 20 ms, slow mode = 152.5 ms + * - Timeout for fast mode is 30 seconds + * - Start(timeout) with timeout = 0 will advertise forever (until connected) + * + * For recommended advertising interval + * https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds +} + +void loop() +{ + // Forward data from HW Serial to BLEUART + while (Serial.available()) + { + // Delay to wait for enough input, since we have a limited transmission buffer + delay(2); + + uint8_t buf[64]; + int count = Serial.readBytes(buf, sizeof(buf)); + bleuart.write( buf, count ); + } + + // Forward from BLEUART to HW Serial + while ( bleuart.available() ) + { + uint8_t ch; + ch = (uint8_t) bleuart.read(); + Serial.write(ch); + } +} + + +// callback invoked when central connects +void connect_callback(uint16_t conn_handle) +{ + // Get the reference to current connection + BLEConnection* connection = Bluefruit.Connection(conn_handle); + + char central_name[32] = { 0 }; + connection->getPeerName(central_name, sizeof(central_name)); + + Serial.print("Connected to "); + Serial.println(central_name); + +#ifdef USE_ARCADA + tft->fillScreen(ARCADA_BLACK); + tft->setTextSize(2); + tft->setCursor(0, 0); + tft->println("Connected"); +#endif +} + +// callback invoked when pairing passkey is generated +// - passkey: 6 keys (without null terminator) for displaying +// - match_request: true when authentication method is Numberic Comparison. +// Then this callback's return value is used to accept (true) or +// reject (false) the pairing process. Otherwise, return value has no effect +bool pairing_passkey_callback(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) +{ + Serial.println("Pairing Passkey"); + Serial.printf(" %.3s %.3s\n", passkey, passkey+3); + +#ifdef USE_ARCADA + tft->fillScreen(ARCADA_BLACK); + tft->println("Pairing Passkey\n"); + tft->setTextColor(ARCADA_YELLOW); + tft->setTextSize(4); + tft->printf(" %.3s %.3s\n", passkey, passkey+3); + + tft->setTextColor(ARCADA_WHITE); + tft->setTextSize(2); +#endif + + // match_request means peer wait for our approval (return true) + if (match_request) + { + Serial.println("Do you want to pair"); + Serial.println("Press Button Left to decline, Button Right to Accept"); + + // timeout for pressing button + uint32_t start_time = millis(); + +#ifdef USE_ARCADA + tft->println("\nDo you accept ?\n\n"); + tft->setTextSize(3); + + // Yes <-> No on CPB is reversed since GIZMO TFT is on the back of CPB + #if ARDUINO_NRF52840_CIRCUITPLAY + tft->setTextColor(ARCADA_GREEN); + tft->print("< Yes"); + tft->setTextColor(ARCADA_RED); + tft->println(" No >"); + #else + tft->setTextColor(ARCADA_RED); + tft->print("< No"); + tft->setTextColor(ARCADA_GREEN); + tft->println(" Yes >"); + #endif + + tft->setTextColor(ARCADA_WHITE); + tft->setTextSize(2); + tft->println(); + + // wait until either button is pressed (30 seconds timeout) + uint32_t justReleased; + do + { + // Peer is disconnected while waiting for input + if ( !Bluefruit.connected(conn_handle) ) break; + + // time out + if ( millis() > start_time + 30000 ) break; + + arcada.readButtons(); + justReleased = arcada.justReleasedButtons(); + } while ( !(justReleased & (ARCADA_BUTTONMASK_LEFT | ARCADA_BUTTONMASK_RIGHT) ) ); + + // Right = accept + if (justReleased & ARCADA_BUTTONMASK_RIGHT) return true; + + // Left = decline + if (justReleased & ARCADA_BUTTONMASK_LEFT) return false; + +#else + // wait until either button is pressed (30 seconds timeout) + while( digitalRead(BUTTON_YES) && digitalRead(BUTTON_NO) ) + { + // Peer is disconnected while waiting for input + if ( !Bluefruit.connected(conn_handle) ) break; + + // time out + if ( millis() > start_time + 30000 ) break; + } + + if ( 0 == digitalRead(BUTTON_YES) ) return true; + + if ( 0 == digitalRead(BUTTON_NO) ) return false; +#endif + + return false; + } + + return true; +} + +void pairing_complete_callback(uint16_t conn_handle, uint8_t auth_status) +{ + if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) + { + Serial.println("Succeeded"); + }else + { + Serial.println("Failed"); + } + +#ifdef USE_ARCADA + if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) + { + tft->setTextColor(ARCADA_GREEN); + tft->println("Succeeded"); + }else + { + tft->setTextColor(ARCADA_RED); + tft->println("Failed"); + } + + tft->setTextColor(ARCADA_WHITE); + tft->setTextSize(2); +#endif +} + +void connection_secured_callback(uint16_t conn_handle) +{ + Serial.println("Secured"); + +#ifdef USE_ARCADA + tft->setTextColor(ARCADA_YELLOW); + tft->println("secured"); + tft->setTextColor(ARCADA_WHITE); +#endif +} + +/** + * Callback invoked when a connection is dropped + * @param conn_handle connection where this event happens + * @param reason is a BLE_HCI_STATUS_CODE which can be found in ble_hci.h + */ +void disconnect_callback(uint16_t conn_handle, uint8_t reason) +{ + (void) conn_handle; + (void) reason; + + Serial.println(); + Serial.print("Disconnected, reason = 0x"); Serial.println(reason, HEX); + +#ifdef USE_ARCADA + tft->fillScreen(ARCADA_BLACK); + tft->setTextSize(2); + tft->setCursor(0, 0); + tft->println("Advertising ..."); +#endif +} diff --git a/libraries/Bluefruit52Lib/examples/Peripheral/pairing_pin/pairing_pin.ino b/libraries/Bluefruit52Lib/examples/Peripheral/pairing_pin/pairing_pin.ino new file mode 100644 index 000000000..19a714bf1 --- /dev/null +++ b/libraries/Bluefruit52Lib/examples/Peripheral/pairing_pin/pairing_pin.ino @@ -0,0 +1,149 @@ +/********************************************************************* + This is an example for our nRF52 based Bluefruit LE modules + + Pick one up today in the adafruit shop! + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This sketch demonstrates Pairing process using static Passkey aka PIN. + * This sketch is essentially the same as bleuart.ino except the BLE Uart + * service requires Security Mode with Man-In-The-Middle protection i.e + * using 6 digits PIN for pairing. + */ + +#include +#include +#include + +// Static PIN is 6 digits from 000000-999999 +#define PAIRING_PIN "123456" + +// BLE Service +BLEUart bleuart; // uart over ble + +void setup() +{ + Serial.begin(115200); + while ( !Serial ) delay(10); // for nrf52840 with native usb + + Serial.println("Bluefruit52 BLEUART Example"); + Serial.println("---------------------------\n"); + + // Setup the BLE LED to be enabled on CONNECT + // Note: This is actually the default behavior, but provided + // here in case you want to control this LED manually via PIN 19 + Bluefruit.autoConnLed(true); + + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + + Bluefruit.begin(); + Bluefruit.setTxPower(4); // Check bluefruit.h for supported values + Bluefruit.setName("Bluefruit52"); + + Serial.println("Setting pairing PIN to: " PAIRING_PIN); + Bluefruit.Security.setPIN(PAIRING_PIN); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // Configure and Start BLE Uart Service + // Set Permission to access BLE Uart is to require man-in-the-middle protection + // This will cause central to perform pairing with static PIN we set above + Serial.println("Configure BLE Uart to require man-in-the-middle protection for PIN pairing"); + bleuart.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); + bleuart.begin(); + + // Set up and start advertising + startAdv(); + + Serial.println("Please use Adafruit's Bluefruit LE app to connect in UART mode"); + Serial.println("Your phone should pop-up PIN input"); + Serial.println("Once connected, enter character(s) that you wish to send"); +} + +void startAdv(void) +{ + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + + // Include bleuart 128-bit uuid + Bluefruit.Advertising.addService(bleuart); + + // Secondary Scan Response packet (optional) + // Since there is no room for 'Name' in Advertising packet + Bluefruit.ScanResponse.addName(); + + /* Start Advertising + * - Enable auto advertising if disconnected + * - Interval: fast mode = 20 ms, slow mode = 152.5 ms + * - Timeout for fast mode is 30 seconds + * - Start(timeout) with timeout = 0 will advertise forever (until connected) + * + * For recommended advertising interval + * https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds +} + +void loop() +{ + // Forward data from HW Serial to BLEUART + while (Serial.available()) + { + // Delay to wait for enough input, since we have a limited transmission buffer + delay(2); + + uint8_t buf[64]; + int count = Serial.readBytes(buf, sizeof(buf)); + bleuart.write( buf, count ); + } + + // Forward from BLEUART to HW Serial + while ( bleuart.available() ) + { + uint8_t ch; + ch = (uint8_t) bleuart.read(); + Serial.write(ch); + } +} + +// callback invoked when central connects +void connect_callback(uint16_t conn_handle) +{ + // Get the reference to current connection + BLEConnection* connection = Bluefruit.Connection(conn_handle); + + char central_name[32] = { 0 }; + connection->getPeerName(central_name, sizeof(central_name)); + + Serial.print("Connected to "); + Serial.println(central_name); +} + +/** + * Callback invoked when a connection is dropped + * @param conn_handle connection where this event happens + * @param reason is a BLE_HCI_STATUS_CODE which can be found in ble_hci.h + */ +void disconnect_callback(uint16_t conn_handle, uint8_t reason) +{ + (void) conn_handle; + (void) reason; + + Serial.println(); + Serial.print("Disconnected, reason = 0x"); Serial.println(reason, HEX); +} diff --git a/libraries/Bluefruit52Lib/keywords.txt b/libraries/Bluefruit52Lib/keywords.txt index 1e63c8ac5..f1537af89 100644 --- a/libraries/Bluefruit52Lib/keywords.txt +++ b/libraries/Bluefruit52Lib/keywords.txt @@ -69,7 +69,6 @@ disconnect KEYWORD2 setConnInterval KEYWORD2 setConnIntervalMS KEYWORD2 connHandle KEYWORD2 -connPaired KEYWORD2 connInterval KEYWORD2 requestPairing KEYWORD2 clearBonds KEYWORD2 diff --git a/libraries/Bluefruit52Lib/src/BLECentral.cpp b/libraries/Bluefruit52Lib/src/BLECentral.cpp index 467e23255..2b9cf220f 100644 --- a/libraries/Bluefruit52Lib/src/BLECentral.cpp +++ b/libraries/Bluefruit52Lib/src/BLECentral.cpp @@ -133,17 +133,35 @@ void BLECentral::_eventHandler(ble_evt_t* evt) { // conn handle has fixed offset regardless of event type const uint16_t conn_hdl = evt->evt.common_evt.conn_handle; + BLEConnection* conn = Bluefruit.Connection(conn_hdl); /* PrPh handle connection is already filtered. Only handle Central events or * connection handle is BLE_CONN_HANDLE_INVALID (e.g BLE_GAP_EVT_ADV_REPORT) */ switch ( evt->header.evt_id ) { case BLE_GAP_EVT_CONNECTED: + { + // Try to secure connection if we bonded previously + bond_keys_t ltkey; + if ( conn->loadBondKey(<key) ) + { + Bluefruit.Security._encrypt(conn_hdl, <key); + } + } break; case BLE_GAP_EVT_DISCONNECTED: break; + case BLE_GAP_EVT_CONN_SEC_UPDATE: + if ( conn->bonded() && !conn->secured() ) + { + // Bonded but failed to secure connection + // Peer must have removed LTKey, we should remove ours as well + conn->removeBondKey(); + } + break; + case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST: { // Peripheral request to change connection parameter diff --git a/libraries/Bluefruit52Lib/src/BLECentral.h b/libraries/Bluefruit52Lib/src/BLECentral.h index 8372a2201..c5b5bd379 100644 --- a/libraries/Bluefruit52Lib/src/BLECentral.h +++ b/libraries/Bluefruit52Lib/src/BLECentral.h @@ -59,7 +59,7 @@ class BLECentral bool connected(uint16_t conn_hdl); // Connected as central to this connection uint8_t connected(void); // Number of connected as central - void clearBonds (void); + void clearBonds(void); /*------------- Callbacks -------------*/ void setConnectCallback ( ble_connect_callback_t fp); diff --git a/libraries/Bluefruit52Lib/src/BLECharacteristic.cpp b/libraries/Bluefruit52Lib/src/BLECharacteristic.cpp index e38f2796f..9d902ef06 100644 --- a/libraries/Bluefruit52Lib/src/BLECharacteristic.cpp +++ b/libraries/Bluefruit52Lib/src/BLECharacteristic.cpp @@ -50,7 +50,8 @@ void BLECharacteristic::_init(void) varclr(&_properties); varclr(&_attr_meta); - _attr_meta.read_perm = _attr_meta.write_perm = BLE_SECMODE_OPEN; + _attr_meta.read_perm = BLE_SECMODE_OPEN; + _attr_meta.write_perm = BLE_SECMODE_OPEN; _attr_meta.vlen = 1; _attr_meta.vloc = BLE_GATTS_VLOC_STACK; _userbuf = NULL; @@ -152,7 +153,7 @@ void BLECharacteristic::setBuffer(void* buf, uint16_t bufsize) _attr_meta.vloc = buf ? BLE_GATTS_VLOC_USER : BLE_GATTS_VLOC_STACK; } -void BLECharacteristic::setPermission(BleSecurityMode read_perm, BleSecurityMode write_perm) +void BLECharacteristic::setPermission(SecureMode_t read_perm, SecureMode_t write_perm) { memcpy(&_attr_meta.read_perm , &read_perm, 1); memcpy(&_attr_meta.write_perm, &write_perm, 1); @@ -217,15 +218,19 @@ ble_gatts_char_handles_t BLECharacteristic::handles(void) return _handles; } +// return the higher security mode +static inline ble_gap_conn_sec_mode_t max_secmode(ble_gap_conn_sec_mode_t sm1, ble_gap_conn_sec_mode_t sm2) +{ + if ( (sm1.sm > sm2.sm) || (sm1.sm == sm2.sm && sm1.lv > sm2.lv) ) return sm1; + return sm2; +} + err_t BLECharacteristic::begin(void) { _service = BLEService::lastService; // Add UUID128 if needed - (void) uuid.begin(); - - // Permission is OPEN if passkey is disabled. -// if (!nvm_data.core.passkey_enable) BLE_GAP_CONN_SEC_MODE_SET_OPEN(&p_char_def->permission); + uuid.begin(); // Correct Read/Write permission according to properties if ( !(_properties.read || _properties.notify || _properties.indicate ) ) @@ -238,6 +243,25 @@ err_t BLECharacteristic::begin(void) _attr_meta.write_perm = BLE_SECMODE_NO_ACCESS; } + // Correct Read/Write permission according to parent service + // Use service permission if it has higher secure mode + SecureMode_t svc_rd_secmode, svc_wr_secmod; + _service->getPermission(&svc_rd_secmode, &svc_wr_secmod); + + ble_gap_conn_sec_mode_t svc_rd_perm, svc_wr_perm; + memcpy(&svc_rd_perm, &svc_rd_secmode, 1); + memcpy(&svc_wr_perm, &svc_wr_secmod , 1); + + if ( _attr_meta.read_perm.sm != 0 ) // skip no access + { + _attr_meta.read_perm = max_secmode(_attr_meta.read_perm, svc_rd_perm); + } + + if ( _attr_meta.write_perm.sm != 0 ) // skip no access + { + _attr_meta.write_perm = max_secmode(_attr_meta.write_perm, svc_wr_perm); + } + /* CCCD attribute metadata */ ble_gatts_attr_md_t cccd_md; @@ -332,7 +356,7 @@ err_t BLECharacteristic::begin(void) return ERROR_NONE; } -err_t BLECharacteristic::addDescriptor(BLEUuid bleuuid, void const * content, uint16_t len, BleSecurityMode read_perm, BleSecurityMode write_perm) +err_t BLECharacteristic::addDescriptor(BLEUuid bleuuid, void const * content, uint16_t len, SecureMode_t read_perm, SecureMode_t write_perm) { // Meta Data ble_gatts_attr_md_t meta; diff --git a/libraries/Bluefruit52Lib/src/BLECharacteristic.h b/libraries/Bluefruit52Lib/src/BLECharacteristic.h index d0ca796ad..acd175315 100644 --- a/libraries/Bluefruit52Lib/src/BLECharacteristic.h +++ b/libraries/Bluefruit52Lib/src/BLECharacteristic.h @@ -43,23 +43,6 @@ class AdafruitBluefruit; class BLEService; -enum BleSecurityMode -{ - SECMODE_NO_ACCESS = 0x00, - SECMODE_OPEN = 0x11, - SECMODE_ENC_NO_MITM = 0x21, - SECMODE_ENC_WITH_MITM = 0x31, - SECMODE_SIGNED_NO_MITM = 0x12, - SECMODE_SIGNED_WITH_MITM = 0x22 -}; - -#define BLE_SECMODE_NO_ACCESS ((ble_gap_conn_sec_mode_t) { .sm = 0, .lv = 0 }) -#define BLE_SECMODE_OPEN ((ble_gap_conn_sec_mode_t) { .sm = 1, .lv = 1 }) -#define BLE_SECMODE_ENC_NO_MITM ((ble_gap_conn_sec_mode_t) { .sm = 1, .lv = 2 }) -#define BLE_SECMODE_ENC_WITH_MITM ((ble_gap_conn_sec_mode_t) { .sm = 1, .lv = 3 }) -#define BLE_SECMODE_SIGNED_NO_MITM ((ble_gap_conn_sec_mode_t) { .sm = 2, .lv = 1 }) -#define BLE_SECMODE_SIGNED_WITH_MITM ((ble_gap_conn_sec_mode_t) { .sm = 2, .lv = 2 }) - enum CharsProperties { CHR_PROPS_BROADCAST = bit(0), @@ -95,7 +78,7 @@ class BLECharacteristic /*------------- Configure -------------*/ void setUuid(BLEUuid bleuuid); void setProperties(uint8_t prop); - void setPermission(BleSecurityMode read_perm, BleSecurityMode write_perm); + void setPermission(SecureMode_t read_perm, SecureMode_t write_perm); void setMaxLen(uint16_t max_len); void setFixedLen(uint16_t fixed_len); void setBuffer(void* buf, uint16_t bufsize); @@ -117,7 +100,7 @@ class BLECharacteristic virtual err_t begin(void); // Add Descriptor function must be called right after begin() - err_t addDescriptor(BLEUuid bleuuid, void const * content, uint16_t len, BleSecurityMode read_perm = SECMODE_OPEN, BleSecurityMode write_perm = SECMODE_NO_ACCESS); + err_t addDescriptor(BLEUuid bleuuid, void const * content, uint16_t len, SecureMode_t read_perm = SECMODE_OPEN, SecureMode_t write_perm = SECMODE_NO_ACCESS); ble_gatts_char_handles_t handles(void); diff --git a/libraries/Bluefruit52Lib/src/BLEConnection.cpp b/libraries/Bluefruit52Lib/src/BLEConnection.cpp index f85adbcbe..67a36ac8d 100644 --- a/libraries/Bluefruit52Lib/src/BLEConnection.cpp +++ b/libraries/Bluefruit52Lib/src/BLEConnection.cpp @@ -55,12 +55,13 @@ BLEConnection::BLEConnection(uint16_t conn_hdl, ble_gap_evt_connected_t const* e _hvn_sem = xSemaphoreCreateCounting(hvn_qsize, hvn_qsize); _wrcmd_sem = xSemaphoreCreateCounting(wrcmd_qsize, wrcmd_qsize); - _paired = false; + _sec_mode.sm = _sec_mode.lv = 1; // default to open + + _bonded = false; _hvc_sem = NULL; _hvc_received = false; - _pair_sem = NULL; - _ediv = 0xFFFF; // invalid ediv value - _bond_keys = NULL; + + _ediv = 0xFFFF; } BLEConnection::~BLEConnection() @@ -68,7 +69,8 @@ BLEConnection::~BLEConnection() vSemaphoreDelete( _hvn_sem ); vSemaphoreDelete( _wrcmd_sem ); - if ( _hvc_sem ) vSemaphoreDelete( _hvc_sem ); + //------------- on-the-fly data must be freed -------------// + if (_hvc_sem ) vSemaphoreDelete(_hvc_sem ); } uint16_t BLEConnection::handle (void) @@ -81,9 +83,14 @@ bool BLEConnection::connected(void) return _connected; } -bool BLEConnection::paired (void) +bool BLEConnection::bonded(void) +{ + return _bonded; +} + +bool BLEConnection::secured(void) { - return _paired; + return !(_sec_mode.sm == 1 && _sec_mode.lv == 1); } uint8_t BLEConnection::getRole (void) @@ -113,7 +120,7 @@ uint8_t BLEConnection::getPHY(void) ble_gap_addr_t BLEConnection::getPeerAddr (void) { - return _peer_addr; + return _bonded ? _bond_id_addr : _peer_addr; } uint16_t BLEConnection::getPeerName(char* buf, uint16_t bufsize) @@ -121,6 +128,11 @@ uint16_t BLEConnection::getPeerName(char* buf, uint16_t bufsize) return Bluefruit.Gatt.readCharByUuid(_conn_hdl, BLEUuid(BLE_UUID_GAP_CHARACTERISTIC_DEVICE_NAME), buf, bufsize); } +ble_gap_conn_sec_mode_t BLEConnection::getSecureMode(void) +{ + return _sec_mode; +} + static inline bool is_tx_power_valid(int8_t power) { #if defined(NRF52832_XXAA) @@ -220,68 +232,45 @@ bool BLEConnection::getWriteCmdPacket (void) return xSemaphoreTake(_wrcmd_sem, ms2tick(BLE_GENERIC_TIMEOUT)); } -bool BLEConnection::storeCccd(void) +bool BLEConnection::saveCccd(void) { - return bond_save_cccd( _role, _conn_hdl, _ediv); + return bond_save_cccd(_role, _conn_hdl, &_bond_id_addr); } -bool BLEConnection::loadKeys(bond_keys_t* bkeys) +bool BLEConnection::loadCccd(void) { - return bond_load_keys(_role, _ediv, bkeys); + return bond_load_cccd(_role, _conn_hdl, &_bond_id_addr); } -bool BLEConnection::requestPairing(void) +bool BLEConnection::saveBondKey(bond_keys_t const* ltkey) { - // skip if already paired - if ( _paired ) return true; - - ble_gap_sec_params_t sec_param = Bluefruit.getSecureParam(); - - // on-the-fly semaphore - _pair_sem = xSemaphoreCreateBinary(); - - if ( _role == BLE_GAP_ROLE_PERIPH ) - { - VERIFY_STATUS( sd_ble_gap_authenticate(_conn_hdl, &sec_param ), false); - xSemaphoreTake(_pair_sem, portMAX_DELAY); - } - else - { - uint16_t cntr_ediv = 0xFFFF; - bond_keys_t bkeys; - - // Check to see if we did bonded with current prph previously - // TODO currently only matches key using fixed address - if ( bond_find_cntr(&_peer_addr, &bkeys) ) - { - cntr_ediv = bkeys.peer_enc.master_id.ediv; - LOG_LV2("BOND", "Load Keys from file " BOND_FNAME_CNTR, cntr_ediv); - VERIFY_STATUS( sd_ble_gap_encrypt(_conn_hdl, &bkeys.peer_enc.master_id, &bkeys.peer_enc.enc_info), false); - - }else - { - VERIFY_STATUS( sd_ble_gap_authenticate(_conn_hdl, &sec_param ), false); - } - - xSemaphoreTake(_pair_sem, portMAX_DELAY); - - // Failed to pair using central stored keys, this happens when - // Prph delete bonds while we did not --> let's remove the obsolete keyfile and retry - if ( !_paired && (cntr_ediv != 0xffff) ) - { - bond_remove_key(BLE_GAP_ROLE_CENTRAL, cntr_ediv); + bond_save_keys(_role, _conn_hdl, ltkey); + _bond_id_addr = ltkey->peer_id.id_addr_info; + _bonded = true; + return true; +} - // Re-try with a fresh session - VERIFY_STATUS( sd_ble_gap_authenticate(_conn_hdl, &sec_param ), false); +bool BLEConnection::loadBondKey(bond_keys_t* ltkey) +{ + _bonded = bond_load_keys(_role, &_peer_addr, ltkey); + VERIFY(_bonded); + _bond_id_addr = ltkey->peer_id.id_addr_info; + return true; +} - xSemaphoreTake(_pair_sem, portMAX_DELAY); - } - } +bool BLEConnection::removeBondKey(void) +{ + VERIFY(_bonded); + bond_remove_key(_role, &_bond_id_addr); + return true; +} - vSemaphoreDelete(_pair_sem); - _pair_sem = NULL; +bool BLEConnection::requestPairing(void) +{ + // skip if already paired + if ( secured() ) return true; - return _paired; + return Bluefruit.Security._authenticate(_conn_hdl); } bool BLEConnection::waitForIndicateConfirm(void) @@ -307,143 +296,18 @@ void BLEConnection::_eventHandler(ble_evt_t* evt) _connected = false; break; - //--------------------------------------------------------------------+ - /* First-time Pairing - * Connect -> SEC_PARAMS_REQUEST -> CONN_SEC_UPDATE -> AUTH_STATUS - * 1. Either we or peer initiate the process - * 2. Peer ask for Secure Parameter ( I/O Caps ) BLE_GAP_EVT_SEC_PARAMS_REQUEST - * 3. Pair Key exchange ( PIN code) - * 4. Connection is secured BLE_GAP_EVT_CONN_SEC_UPDATE - * 5. Long term Keys exchanged BLE_GAP_EVT_AUTH_STATUS - * - * Reconnect using bonded key - * Connect -> SEC_INFO_REQUEST -> CONN_SEC_UPDATE - * 1. Either we or peer initiate the process - * 2. Peer ask for Secure Info ( bond keys ) BLE_GAP_EVT_SEC_INFO_REQUEST - * 3. Connection is secured BLE_GAP_EVT_CONN_SEC_UPDATE - */ - //--------------------------------------------------------------------+ - case BLE_GAP_EVT_SEC_PARAMS_REQUEST: - { - // Pairing in progress, Peer asking for our info - _bond_keys = (bond_keys_t*) rtos_malloc( sizeof(bond_keys_t)); - VERIFY(_bond_keys, ); - memclr(_bond_keys, sizeof(bond_keys_t)); - - _ediv = 0xFFFF; // invalid value for ediv - - /* Step 1: Pairing/Bonding - * - Central supplies its parameters - * - We replies with our security parameters - */ - // ble_gap_sec_params_t* peer = &evt->evt.gap_evt.params.sec_params_request.peer_params; - COMMENT_OUT( - // Change security parameter according to authentication type - if ( _auth_type == BLE_GAP_AUTH_KEY_TYPE_PASSKEY) - { - sec_para.mitm = 1; - sec_para.io_caps = BLE_GAP_IO_CAPS_DISPLAY_ONLY; - } - ) - - ble_gap_sec_keyset_t keyset = - { - .keys_own = { - .p_enc_key = &_bond_keys->own_enc, - .p_id_key = NULL, - .p_sign_key = NULL, - .p_pk = NULL - }, - - .keys_peer = { - .p_enc_key = &_bond_keys->peer_enc, - .p_id_key = &_bond_keys->peer_id, - .p_sign_key = NULL, - .p_pk = NULL - } - }; - - ble_gap_sec_params_t sec_param = Bluefruit.getSecureParam(); - VERIFY_STATUS(sd_ble_gap_sec_params_reply(_conn_hdl, - BLE_GAP_SEC_STATUS_SUCCESS, - _role == BLE_GAP_ROLE_PERIPH ? &sec_param : NULL, - &keyset), - ); - } - break; - - case BLE_GAP_EVT_AUTH_STATUS: - { - // Pairing process completed - ble_gap_evt_auth_status_t* status = &evt->evt.gap_evt.params.auth_status; - - // Pairing succeeded --> save encryption keys ( Bonding ) - if (BLE_GAP_SEC_STATUS_SUCCESS == status->auth_status) - { - _paired = true; - _ediv = _bond_keys->own_enc.master_id.ediv; - - bond_save_keys(_role, _conn_hdl, _bond_keys); - }else - { - PRINT_HEX(status->auth_status); - } - - rtos_free(_bond_keys); - _bond_keys = NULL; - } - break; - - case BLE_GAP_EVT_SEC_INFO_REQUEST: - { - // Peer asks for the stored keys. - // - load key and return if bonded previously. - // - Else return NULL --> Initiate key exchange - ble_gap_evt_sec_info_request_t* sec_req = (ble_gap_evt_sec_info_request_t*) &evt->evt.gap_evt.params.sec_info_request; - - bond_keys_t bkeys; - varclr(&bkeys); - - if ( bond_load_keys(_role, sec_req->master_id.ediv, &bkeys) ) - { - sd_ble_gap_sec_info_reply(_conn_hdl, &bkeys.own_enc.enc_info, &bkeys.peer_id.id_info, NULL); - - _ediv = bkeys.own_enc.master_id.ediv; - } else - { - sd_ble_gap_sec_info_reply(_conn_hdl, NULL, NULL, NULL); - } - } - break; - - case BLE_GAP_EVT_PASSKEY_DISPLAY: - { - // ble_gap_evt_passkey_display_t const* passkey_display = &evt->evt.gap_evt.params.passkey_display; - // PRINT_INT(passkey_display->match_request); - // PRINT_BUFFER(passkey_display->passkey, 6); - - // sd_ble_gap_auth_key_reply - } - break; - case BLE_GAP_EVT_CONN_SEC_UPDATE: { const ble_gap_conn_sec_t* conn_sec = &evt->evt.gap_evt.params.conn_sec_update.conn_sec; + _sec_mode = conn_sec->sec_mode; + // Connection is secured (paired) if encryption level > 1 - if ( !( conn_sec->sec_mode.sm == 1 && conn_sec->sec_mode.lv == 1) ) + if ( this->secured() ) { - // Previously bonded --> secure by re-connection process --> Load & Set SysAttr (Apply Service Context) - // Else Init SysAttr (first bonded) - if ( !bond_load_cccd(_role, _conn_hdl, _ediv) ) - { - sd_ble_gatts_sys_attr_set(_conn_hdl, NULL, 0, 0); - } - - _paired = true; + // Try to restore CCCD with bonded peer, if it doesn't exist (newly bonded), initialize it + if ( !loadCccd() ) sd_ble_gatts_sys_attr_set(_conn_hdl, NULL, 0, 0); } - - if (_pair_sem) xSemaphoreGive(_pair_sem); } break; diff --git a/libraries/Bluefruit52Lib/src/BLEConnection.h b/libraries/Bluefruit52Lib/src/BLEConnection.h index d47d65da3..d940878c3 100644 --- a/libraries/Bluefruit52Lib/src/BLEConnection.h +++ b/libraries/Bluefruit52Lib/src/BLEConnection.h @@ -54,10 +54,13 @@ class BLEConnection uint16_t _ediv; bool _connected; - bool _paired; + bool _bonded; // have LTK stored in InternalFS bool _hvc_received; - ble_gap_addr_t _peer_addr; + ble_gap_conn_sec_mode_t _sec_mode; + + ble_gap_addr_t _peer_addr; // resolvable connect address + ble_gap_addr_t _bond_id_addr; // address stored as bonded SemaphoreHandle_t _hvn_sem; SemaphoreHandle_t _wrcmd_sem; @@ -65,16 +68,14 @@ class BLEConnection // On-demand semaphore/data that are created on the fly SemaphoreHandle_t _hvc_sem; - SemaphoreHandle_t _pair_sem; - bond_keys_t* _bond_keys; // Shared keys with bonded device, size ~ 80 bytes - public: BLEConnection(uint16_t conn_hdl, ble_gap_evt_connected_t const * evt_connected, uint8_t hvn_qsize, uint8_t wrcmd_qsize); virtual ~BLEConnection(); uint16_t handle(void); bool connected(void); - bool paired(void); + bool bonded(void); + bool secured(void); uint8_t getRole(void); uint16_t getMtu (void); @@ -85,6 +86,8 @@ class BLEConnection ble_gap_addr_t getPeerAddr(void); uint16_t getPeerName(char* buf, uint16_t bufsize); + ble_gap_conn_sec_mode_t getSecureMode(void); + bool disconnect(void); bool setTxPower(int8_t power); // set power for this connection @@ -104,8 +107,12 @@ class BLEConnection bool getWriteCmdPacket(void); bool waitForIndicateConfirm(void); - bool storeCccd(void); - bool loadKeys(bond_keys_t* bkeys); + bool saveBondKey(bond_keys_t const* ltkey); + bool loadBondKey(bond_keys_t* ltkey); + bool removeBondKey(void); + + bool saveCccd(void); + bool loadCccd(void); /*------------------------------------------------------------------*/ /* INTERNAL USAGE ONLY diff --git a/libraries/Bluefruit52Lib/src/BLEGatt.cpp b/libraries/Bluefruit52Lib/src/BLEGatt.cpp index 48b67f590..45dc06420 100644 --- a/libraries/Bluefruit52Lib/src/BLEGatt.cpp +++ b/libraries/Bluefruit52Lib/src/BLEGatt.cpp @@ -140,9 +140,9 @@ void BLEGatt::_eventHandler(ble_evt_t* evt) chr->_eventHandler(evt); // Save CCCD if paired - if ( conn->paired() && (evt_id == BLE_GATTS_EVT_WRITE) && (req_handle == chr->handles().cccd_handle) ) + if ( conn->secured() && (evt_id == BLE_GATTS_EVT_WRITE) && (req_handle == chr->handles().cccd_handle) ) { - conn->storeCccd(); + conn->saveCccd(); } } } diff --git a/libraries/Bluefruit52Lib/src/BLEPeriph.cpp b/libraries/Bluefruit52Lib/src/BLEPeriph.cpp index bd4894ec5..bda4f9cb7 100644 --- a/libraries/Bluefruit52Lib/src/BLEPeriph.cpp +++ b/libraries/Bluefruit52Lib/src/BLEPeriph.cpp @@ -78,6 +78,11 @@ uint8_t BLEPeriph::connected (void) return count; } +void BLEPeriph::clearBonds(void) +{ + bond_clear_prph(); +} + bool BLEPeriph::setConnInterval (uint16_t min, uint16_t max) { _ppcp.min_conn_interval = min; @@ -121,6 +126,7 @@ void BLEPeriph::setDisconnectCallback( ble_disconnect_callback_t fp ) void BLEPeriph::_eventHandler(ble_evt_t* evt) { uint16_t const conn_hdl = evt->evt.common_evt.conn_handle; + BLEConnection* conn = Bluefruit.Connection(conn_hdl); switch ( evt->header.evt_id ) { @@ -128,14 +134,11 @@ void BLEPeriph::_eventHandler(ble_evt_t* evt) { ble_gap_evt_connected_t* para = &evt->evt.gap_evt.params.connected; - if (para->role == BLE_GAP_ROLE_PERIPH) + // Connection interval set by Central is out of preferred range + // Try to negotiate with Central using our preferred values + if ( !is_within(_ppcp.min_conn_interval, para->conn_params.min_conn_interval, _ppcp.max_conn_interval) ) { - // Connection interval set by Central is out of preferred range - // Try to negotiate with Central using our preferred values - if ( !is_within(_ppcp.min_conn_interval, para->conn_params.min_conn_interval, _ppcp.max_conn_interval) ) - { - VERIFY_STATUS( sd_ble_gap_conn_param_update(conn_hdl, &_ppcp), ); - } + VERIFY_STATUS( sd_ble_gap_conn_param_update(conn_hdl, &_ppcp), ); } } break; diff --git a/libraries/Bluefruit52Lib/src/BLEPeriph.h b/libraries/Bluefruit52Lib/src/BLEPeriph.h index 12491533d..2649e928b 100644 --- a/libraries/Bluefruit52Lib/src/BLEPeriph.h +++ b/libraries/Bluefruit52Lib/src/BLEPeriph.h @@ -50,6 +50,8 @@ class BLEPeriph bool connected(uint16_t conn_hdl); // Connected as prph to this connection uint8_t connected(void); // Number of connected as peripherals + void clearBonds(void); + bool setConnInterval (uint16_t min, uint16_t max); bool setConnIntervalMS (uint16_t min_ms, uint16_t max_ms); bool setConnSupervisionTimeout(uint16_t timeout); diff --git a/libraries/Bluefruit52Lib/src/BLESecurity.cpp b/libraries/Bluefruit52Lib/src/BLESecurity.cpp new file mode 100644 index 000000000..f0f58cf96 --- /dev/null +++ b/libraries/Bluefruit52Lib/src/BLESecurity.cpp @@ -0,0 +1,409 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ha Thach (tinyusb.org) for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "bluefruit.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ +#ifdef NRF_CRYPTOCELL + #define LESC_SUPPORTED 1 +#else + #define LESC_SUPPORTED 0 +#endif + +// default is Just Work +static const ble_gap_sec_params_t _sec_param_default = +{ + .bond = 1, + .mitm = 0, + .lesc = LESC_SUPPORTED, + .keypress = 0, + .io_caps = BLE_GAP_IO_CAPS_NONE, + .oob = 0, + .min_key_size = 7, + .max_key_size = 16, + .kdist_own = { .enc = 1, .id = 1}, + .kdist_peer = { .enc = 1, .id = 1} +}; + +//------------- IMPLEMENTATION -------------// + +// convert N-byte Number from Big <-> Little Endian to use with BLE +// Public Key = 32-byte N1 + 32-byte N2 +static void swap_endian(uint8_t data[], uint32_t nbytes) +{ + for(uint8_t i=0; iaddr_type == BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE); + + uint8_t const* hash = p_addr->addr; + uint8_t const* rand = p_addr->addr+3; + + nrf_ecb_hal_data_t ecb_data; + memclr(&ecb_data, sizeof(nrf_ecb_hal_data_t)); + + // Swap Endian for IRK (other padding with 0s) + memcpy(ecb_data.key, irk->irk, SOC_ECB_KEY_LENGTH); + swap_endian(ecb_data.key, SOC_ECB_KEY_LENGTH); + + // Swap input endian + memcpy(ecb_data.cleartext, rand, 3); + swap_endian(ecb_data.cleartext, SOC_ECB_CLEARTEXT_LENGTH); + + // compute using HW AES peripherals + (void) sd_ecb_block_encrypt(&ecb_data); + + // Swap output endian + swap_endian(ecb_data.ciphertext, SOC_ECB_CIPHERTEXT_LENGTH); + + return 0 == memcmp(hash, ecb_data.ciphertext, 3); +} + +// Use Legacy SC static Passkey +bool BLESecurity::setPIN(const char* pin) +{ + VERIFY(pin && strlen(pin) == BLE_GAP_PASSKEY_LEN); + + // Static Passkey requires using + // - Legacy SC + // - IO cap: Display + // - MITM is on + _sec_param.mitm = 1; + _sec_param.lesc = 0; + _sec_param.io_caps = BLE_GAP_IO_CAPS_DISPLAY_ONLY; + + ble_opt_t opt; + opt.gap_opt.passkey.p_passkey = (const uint8_t*) pin; + VERIFY_STATUS(sd_ble_opt_set(BLE_GAP_OPT_PASSKEY, &opt), false); + + return true; +} + +// Pairing using LESC with peripheral display +bool BLESecurity::setPairPasskeyCallback(pair_passkey_cb_t fp) +{ + _passkey_cb = fp; + + // mitm is required to trigger passkey generation + _sec_param.mitm = 1; + + return true; +} + +void BLESecurity::setPairCompleteCallback(pair_complete_cb_t fp) +{ + _complete_cb = fp; +} + +void BLESecurity::setSecuredCallback(secured_conn_cb_t fp) +{ + _secured_cb = fp; +} + +bool BLESecurity::_authenticate(uint16_t conn_hdl) +{ + VERIFY_STATUS(sd_ble_gap_authenticate(conn_hdl, &_sec_param ), false); + return true; +} + +bool BLESecurity::_encrypt(uint16_t conn_hdl, bond_keys_t const* ltkey) +{ + // LESC use own key, Legacy use peer key + ble_gap_enc_key_t const* enc_key = ltkey->own_enc.enc_info.lesc ? <key->own_enc : <key->peer_enc; + VERIFY_STATUS(sd_ble_gap_encrypt(conn_hdl, &enc_key->master_id, &enc_key->enc_info), false); + return true; +} + +//--------------------------------------------------------------------+ +/* First-time Pairing + * + * Connect -> SEC_PARAMS_REQUEST -> PASSKEY_DISPLAY -> BLE_GAP_EVT_LESC_DHKEY_REQUEST -> + * CONN_SEC_UPDATE -> AUTH_STATUS + * 1. Either we or peer initiate the process + * 2. Peer ask for Secure Parameter ( I/O Caps ) BLE_GAP_EVT_SEC_PARAMS_REQUEST + * 3. Pair PassKey exchange + * 4. Connection is secured BLE_GAP_EVT_CONN_SEC_UPDATE + * 5. Long term Keys exchanged BLE_GAP_EVT_AUTH_STATUS + * + * Reconnect using bonded key + * Connect -> SEC_INFO_REQUEST -> CONN_SEC_UPDATE + * 1. Either we or peer initiate the process + * 2. Peer ask for Secure Info ( bond keys ) BLE_GAP_EVT_SEC_INFO_REQUEST + * 3. Connection is secured BLE_GAP_EVT_CONN_SEC_UPDATE + */ +//--------------------------------------------------------------------+ +void BLESecurity::_eventHandler(ble_evt_t* evt) +{ + uint16_t const conn_hdl = evt->evt.common_evt.conn_handle; + BLEConnection* conn = Bluefruit.Connection(conn_hdl); + + switch(evt->header.evt_id) + { + case BLE_GAP_EVT_SEC_PARAMS_REQUEST: + { + /* Pairing is started, Peer is asking for our info + * Step 1: Pairing/Bonding + * - Central supplies its parameters + * - We replies with our security parameters + */ + ble_gap_sec_params_t const* peer = &evt->evt.gap_evt.params.sec_params_request.peer_params; + (void) peer; + LOG_LV2("PAIR", "Peer Params: bond = %d, mitm = %d, lesc = %d, io_caps = %d", + peer->bond, peer->mitm, peer->lesc, peer->io_caps); + + ble_gap_sec_keyset_t keyset = + { + .keys_own = { + .p_enc_key = &_bond_keys.own_enc, + .p_id_key = NULL, + .p_sign_key = NULL, + .p_pk = NULL + }, + + .keys_peer = { + .p_enc_key = &_bond_keys.peer_enc, + .p_id_key = &_bond_keys.peer_id, + .p_sign_key = NULL, + .p_pk = NULL + } + }; + + #ifdef NRF_CRYPTOCELL + keyset.keys_own.p_pk = (ble_gap_lesc_p256_pk_t*) (_pubkey+1); + keyset.keys_peer.p_pk = (ble_gap_lesc_p256_pk_t*) (_peer_pubkey+1); + #endif + + VERIFY_STATUS(sd_ble_gap_sec_params_reply(conn_hdl, BLE_GAP_SEC_STATUS_SUCCESS, + conn->getRole() == BLE_GAP_ROLE_PERIPH ? &_sec_param : NULL, &keyset), ); + } + break; + + case BLE_GAP_EVT_PASSKEY_DISPLAY: + { + ble_gap_evt_passkey_display_t const* passkey_display = &evt->evt.gap_evt.params.passkey_display; + LOG_LV2("PAIR", "Passkey = %.6s, match request = %d", passkey_display->passkey, passkey_display->match_request); + + // Invoke display callback + if ( _passkey_cb ) + { + ada_callback(passkey_display->passkey, 6, _passkey_display_cabllack_dfr, _passkey_cb, conn_hdl, passkey_display->passkey, passkey_display->match_request); + } + } + break; + +#ifdef NRF_CRYPTOCELL + case BLE_GAP_EVT_LESC_DHKEY_REQUEST: + { + ble_gap_evt_lesc_dhkey_request_t* dhkey_req = &evt->evt.gap_evt.params.lesc_dhkey_request; + + if ( dhkey_req->oobd_req ) + { + // Out of Band not supported yet + } + + // Raw public key from peer device is uncompressed + // _peer_pubkey + 1 == dhkey_req->p_pk_peer->pk + _peer_pubkey[0] = CRYS_EC_PointUncompressed; + + // Swap Endian data from air + swap_endian(_peer_pubkey+1 , 32); + swap_endian(_peer_pubkey+1+32, 32); + + // Create nRFCrypto pubkey from raw format + nRFCrypto_ECC_PublicKey peerPublickKey; + peerPublickKey.begin(CRYS_ECPKI_DomainID_secp256r1); + peerPublickKey.fromRaw(_peer_pubkey, 1+BLE_GAP_LESC_P256_PK_LEN); + + // Create shared secret derivation primitive using ECC Diffie-Hellman + ble_gap_lesc_dhkey_t dhkey; + nRFCrypto_ECC::SVDP_DH(_private_key, peerPublickKey, dhkey.key, sizeof(dhkey.key)); + + peerPublickKey.end(); + + // Swap Endian before sending to air + swap_endian(dhkey.key, 32); + + // Swap back the peer pubkey since SoftDevice still need this until Authentication is complete + swap_endian(_peer_pubkey+1 , 32); + swap_endian(_peer_pubkey+1+32, 32); + + sd_ble_gap_lesc_dhkey_reply(conn_hdl, &dhkey); + } + break; +#endif + + // Pairing process completed + case BLE_GAP_EVT_AUTH_STATUS: + { + ble_gap_evt_auth_status_t* status = &evt->evt.gap_evt.params.auth_status; + + LOG_LV2("PAIR", "Auth Status = 0x%02X, Bonded = %d, LESC = %d, Our Kdist = 0x%02X, Peer Kdist = 0x%02X ", + status->auth_status, status->bonded, status->lesc, *((uint8_t*) &status->kdist_own), *((uint8_t*) &status->kdist_peer)); + + // Pairing succeeded --> save encryption keys ( Bonding ) + if (BLE_GAP_SEC_STATUS_SUCCESS == status->auth_status) + { + conn->saveBondKey(&_bond_keys); + } + + // Invoke callback + if (_complete_cb) ada_callback(NULL, 0, _complete_cb, conn_hdl, status->auth_status); + } + break; + + case BLE_GAP_EVT_SEC_INFO_REQUEST: + { + // Peer asks for the stored keys. + // - load key and return if bonded previously. + // - Else return NULL --> Initiate key exchange + ble_gap_evt_sec_info_request_t* sec_info = (ble_gap_evt_sec_info_request_t*) &evt->evt.gap_evt.params.sec_info_request; + + LOG_LV2("PAIR", "Address: ID = %d, Type = 0x%02X, %02X:%02X:%02X:%02X:%02X:%02X", + sec_info->peer_addr.addr_id_peer, sec_info->peer_addr.addr_type, + sec_info->peer_addr.addr[0], sec_info->peer_addr.addr[1], sec_info->peer_addr.addr[2], + sec_info->peer_addr.addr[3], sec_info->peer_addr.addr[4], sec_info->peer_addr.addr[5]); + + bond_keys_t bkeys; + + if ( conn->loadBondKey(&bkeys) ) + { + VERIFY_STATUS(sd_ble_gap_sec_info_reply(conn_hdl, &bkeys.own_enc.enc_info, &bkeys.peer_id.id_info, NULL), ); + } else + { + VERIFY_STATUS(sd_ble_gap_sec_info_reply(conn_hdl, NULL, NULL, NULL), ); + } + } + break; + + case BLE_GAP_EVT_CONN_SEC_UPDATE: + { + const ble_gap_conn_sec_t* conn_sec = &evt->evt.gap_evt.params.conn_sec_update.conn_sec; + LOG_LV2("PAIR", "Security Mode = %d, Level = %d", conn_sec->sec_mode.sm, conn_sec->sec_mode.lv); + + if ( _secured_cb ) + { + ada_callback(NULL, 0, _secured_cb, conn_hdl); + } + } + break; + + default: break; + } +} diff --git a/libraries/Bluefruit52Lib/src/BLESecurity.h b/libraries/Bluefruit52Lib/src/BLESecurity.h new file mode 100644 index 000000000..2a3ea098c --- /dev/null +++ b/libraries/Bluefruit52Lib/src/BLESecurity.h @@ -0,0 +1,89 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ha Thach (tinyusb.org) for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef BLESECURITY_H_ +#define BLESECURITY_H_ + +#include "bluefruit_common.h" +#include "utility/bonding.h" + +#ifdef NRF_CRYPTOCELL +#include "Adafruit_nRFCrypto.h" +#endif + +class BLESecurity +{ + public: + typedef bool (*pair_passkey_cb_t ) (uint16_t conn_hdl, uint8_t const passkey[6], bool match_request); + typedef void (*pair_complete_cb_t) (uint16_t conn_hdl, uint8_t auth_status); + typedef void (*secured_conn_cb_t) (uint16_t conn_hdl); + + BLESecurity(void); + + bool begin(void); + + // Use static Passkey (Legacy SC) + bool setPIN(const char* pin); + + // Set IO capacities + void setIOCaps(bool display, bool yes_no, bool keyboard); + + // Enable/Disable Man in the middle protection + void setMITM(bool enabled); + + // resolve address with IRK to see if it matches + bool resolveAddress(ble_gap_addr_t const * p_addr, ble_gap_irk_t const * irk); + + //------------- Callbacks -------------// + bool setPairPasskeyCallback(pair_passkey_cb_t fp); + void setPairCompleteCallback(pair_complete_cb_t fp); + void setSecuredCallback(secured_conn_cb_t fp); + + /*------------------------------------------------------------------*/ + /* INTERNAL USAGE ONLY + * Although declare as public, it is meant to be invoked by internal + * code. User should not call these directly + *------------------------------------------------------------------*/ + void _eventHandler(ble_evt_t* evt); + + bool _authenticate(uint16_t conn_hdl); + bool _encrypt(uint16_t conn_hdl, bond_keys_t const* ltkey); + + private: + ble_gap_sec_params_t _sec_param; + +#ifdef NRF_CRYPTOCELL + nRFCrypto_ECC_PrivateKey _private_key; + uint8_t _pubkey[1+BLE_GAP_LESC_P256_PK_LEN]; // our public key: 1 header + 64 data + uint8_t _peer_pubkey[1+BLE_GAP_LESC_P256_PK_LEN]; // peer public key when using LESC +#endif + + bond_keys_t _bond_keys; // Shared keys with bonded device during securing connection, size ~ 80 bytes + + pair_passkey_cb_t _passkey_cb; + pair_complete_cb_t _complete_cb; + secured_conn_cb_t _secured_cb; +}; + +#endif /* BLESECURITY_H_ */ diff --git a/libraries/Bluefruit52Lib/src/BLEService.cpp b/libraries/Bluefruit52Lib/src/BLEService.cpp index b85c54f80..bbee03e9e 100644 --- a/libraries/Bluefruit52Lib/src/BLEService.cpp +++ b/libraries/Bluefruit52Lib/src/BLEService.cpp @@ -41,6 +41,8 @@ BLEService* BLEService::lastService = NULL; void BLEService::_init(void) { _handle = BLE_GATT_HANDLE_INVALID; + _read_perm = SECMODE_OPEN; + _write_perm = SECMODE_OPEN; } BLEService::BLEService(void) @@ -60,6 +62,18 @@ void BLEService::setUuid(BLEUuid bleuuid) uuid = bleuuid; } +void BLEService::setPermission(SecureMode_t read_perm, SecureMode_t write_perm) +{ + _read_perm = read_perm; + _write_perm = write_perm; +} + +void BLEService::getPermission(SecureMode_t* read_perm, SecureMode_t* write_perm) +{ + *read_perm = _read_perm; + *write_perm = _write_perm; +} + err_t BLEService::begin(void) { // Add UUID128 if needed diff --git a/libraries/Bluefruit52Lib/src/BLEService.h b/libraries/Bluefruit52Lib/src/BLEService.h index 8bb251d0e..4de334f55 100644 --- a/libraries/Bluefruit52Lib/src/BLEService.h +++ b/libraries/Bluefruit52Lib/src/BLEService.h @@ -43,6 +43,8 @@ class BLEService { protected: uint16_t _handle; // service gatt handle + SecureMode_t _read_perm; + SecureMode_t _write_perm; void _init(void); @@ -59,6 +61,9 @@ class BLEService void setUuid(BLEUuid bleuuid); + void setPermission(SecureMode_t read_perm, SecureMode_t write_perm); + void getPermission(SecureMode_t* read_perm, SecureMode_t* write_perm); + virtual err_t begin(void); friend class BLEGatt; diff --git a/libraries/Bluefruit52Lib/src/bluefruit.cpp b/libraries/Bluefruit52Lib/src/bluefruit.cpp index f1f66763a..62549455d 100644 --- a/libraries/Bluefruit52Lib/src/bluefruit.cpp +++ b/libraries/Bluefruit52Lib/src/bluefruit.cpp @@ -41,8 +41,10 @@ #define CFG_BLE_TX_POWER_LEVEL 0 #endif -#ifndef CFG_DEFAULT_NAME -#define CFG_DEFAULT_NAME "Bluefruit52" +#ifdef USB_PRODUCT + #define CFG_DEFAULT_NAME USB_PRODUCT +#else + #define CFG_DEFAULT_NAME "Feather nRF52832" #endif #ifndef CFG_BLE_TASK_STACKSIZE @@ -186,20 +188,6 @@ AdafruitBluefruit::AdafruitBluefruit(void) _event_cb = NULL; _rssi_cb = NULL; - - _sec_param = ((ble_gap_sec_params_t) - { - .bond = 1, - .mitm = 0, - .lesc = 0, - .keypress = 0, - .io_caps = BLE_GAP_IO_CAPS_NONE, - .oob = 0, - .min_key_size = 7, - .max_key_size = 16, - .kdist_own = { .enc = 1, .id = 1}, - .kdist_peer = { .enc = 1, .id = 1}, - }); } void AdafruitBluefruit::configServiceChanged(bool changed) @@ -468,6 +456,8 @@ bool AdafruitBluefruit::begin(uint8_t prph_count, uint8_t central_count) // Init Peripheral role VERIFY( Periph.begin() ); + Security.begin(); + // Default device name ble_gap_conn_sec_mode_t sec_mode = BLE_SECMODE_OPEN; VERIFY_STATUS(sd_ble_gap_device_name_set(&sec_mode, (uint8_t const *) CFG_DEFAULT_NAME, strlen(CFG_DEFAULT_NAME)), false); @@ -630,7 +620,7 @@ bool AdafruitBluefruit::disconnect(uint16_t conn_hdl) return true; // not connected still return true } -void AdafruitBluefruit::setEventCallback ( void (*fp) (ble_evt_t*) ) +void AdafruitBluefruit::setEventCallback (event_cb_t fp) { _event_cb = fp; } @@ -640,12 +630,6 @@ uint16_t AdafruitBluefruit::connHandle(void) return _conn_hdl; } -bool AdafruitBluefruit::connPaired(uint16_t conn_hdl) -{ - BLEConnection* conn = Bluefruit.Connection(conn_hdl); - return conn && conn->paired(); -} - uint16_t AdafruitBluefruit::getMaxMtu(uint8_t role) { return (role == BLE_GAP_ROLE_PERIPH) ? _sd_cfg.prph.mtu_max : _sd_cfg.central.mtu_max; @@ -656,34 +640,16 @@ BLEConnection* AdafruitBluefruit::Connection(uint16_t conn_hdl) return (conn_hdl < BLE_MAX_CONNECTION) ? _connection[conn_hdl] : NULL; } -void AdafruitBluefruit::setRssiCallback(rssi_callback_t fp) +void AdafruitBluefruit::setRssiCallback(rssi_cb_t fp) { _rssi_cb = fp; } -COMMENT_OUT ( -bool AdafruitBluefruit::setPIN(const char* pin) -{ - VERIFY ( strlen(pin) == BLE_GAP_PASSKEY_LEN ); - - _auth_type = BLE_GAP_AUTH_KEY_TYPE_PASSKEY; - memcpy(_pin, pin, BLE_GAP_PASSKEY_LEN); - -// Config Static Passkey -// ble_opt_t opt -// uint8_t passkey[] = STATIC_PASSKEY; -// m_static_pin_option.gap.passkey.p_passkey = passkey; -//err_code = sd_ble_opt_set(BLE_GAP_OPT_PASSKEY, &m_static_pin_option); - - return true; -} -) - /*------------------------------------------------------------------*/ /* Thread & SoftDevice Event handler *------------------------------------------------------------------*/ -void SD_EVT_IRQHandler(void) +extern "C" void SD_EVT_IRQHandler(void) { #if CFG_SYSVIEW SEGGER_SYSVIEW_RecordEnterISR(); @@ -803,7 +769,11 @@ void AdafruitBluefruit::_ble_handler(ble_evt_t* evt) LOG_LV2("BLE", "%s : Conn Handle = %d", dbg_ble_event_str(evt->header.evt_id), conn_hdl); // GAP handler - if ( conn ) conn->_eventHandler(evt); + if ( conn ) + { + conn->_eventHandler(evt); + Security._eventHandler(evt); + } switch(evt->header.evt_id) { @@ -966,22 +936,6 @@ void AdafruitBluefruit::_setConnLed (bool on_off) } } -/*------------------------------------------------------------------*/ -/* Bonds - *------------------------------------------------------------------*/ -bool AdafruitBluefruit::requestPairing(uint16_t conn_hdl) -{ - BLEConnection* conn = this->Connection(conn_hdl); - VERIFY(conn); - - return conn->requestPairing(); -} - -void AdafruitBluefruit::clearBonds(void) -{ - bond_clear_prph(); -} - //--------------------------------------------------------------------+ // //--------------------------------------------------------------------+ @@ -993,7 +947,7 @@ void Bluefruit_printInfo(void) void AdafruitBluefruit::printInfo(void) { - // Skip if Serial is not initialised + // Skip if Serial is not initialized if ( !Serial ) return; // prepare for ability to change output, based on compile-time flags Print& logger = Serial; diff --git a/libraries/Bluefruit52Lib/src/bluefruit.h b/libraries/Bluefruit52Lib/src/bluefruit.h index f4a4a3189..6224408cc 100644 --- a/libraries/Bluefruit52Lib/src/bluefruit.h +++ b/libraries/Bluefruit52Lib/src/bluefruit.h @@ -55,6 +55,7 @@ #include "BLEDiscovery.h" #include "BLEConnection.h" #include "BLEGatt.h" +#include "BLESecurity.h" // Services #include "services/BLEDis.h" @@ -100,20 +101,22 @@ extern "C" class AdafruitBluefruit { public: - typedef void (*rssi_callback_t) (uint16_t conn_hdl, int8_t rssi); + typedef void (*event_cb_t) (ble_evt_t* evt); + typedef void (*rssi_cb_t) (uint16_t conn_hdl, int8_t rssi); - AdafruitBluefruit(void); // Constructor + AdafruitBluefruit(void); /*------------------------------------------------------------------*/ /* Lower Level Classes (Bluefruit.Advertising.*, etc.) *------------------------------------------------------------------*/ + BLEPeriph Periph; + BLECentral Central; + BLESecurity Security; BLEGatt Gatt; BLEAdvertising Advertising; BLEAdvertisingData ScanResponse; BLEScanner Scanner; - BLEPeriph Periph; - BLECentral Central; BLEDiscovery Discovery; /*------------------------------------------------------------------*/ @@ -151,16 +154,9 @@ class AdafruitBluefruit bool setAppearance (uint16_t appear); uint16_t getAppearance (void); - ble_gap_sec_params_t getSecureParam(void) - { - return _sec_param; - } - void autoConnLed (bool enabled); void setConnLedInterval (uint32_t ms); - void clearBonds (void); - /*------------------------------------------------------------------*/ /* GAP, Connections and Bonding *------------------------------------------------------------------*/ @@ -168,11 +164,9 @@ class AdafruitBluefruit bool connected (uint16_t conn_hdl); uint16_t connHandle (void); - bool connPaired (uint16_t conn_hdl); // Alias to BLEConnection API() bool disconnect (uint16_t conn_hdl); - bool requestPairing (uint16_t conn_hdl); uint16_t getMaxMtu(uint8_t role); @@ -192,10 +186,8 @@ class AdafruitBluefruit /*------------------------------------------------------------------*/ /* Callbacks *------------------------------------------------------------------*/ - void setRssiCallback(rssi_callback_t fp); - void setEventCallback( void (*fp) (ble_evt_t*) ); - - COMMENT_OUT ( bool setPIN(const char* pin); ) + void setRssiCallback(rssi_cb_t fp); + void setEventCallback(event_cb_t fp); /*------------------------------------------------------------------*/ /* INTERNAL USAGE ONLY @@ -240,6 +232,7 @@ class AdafruitBluefruit SemaphoreHandle_t* _mprot_event_sem; #endif + // Auto LED Blinky TimerHandle_t _led_blink_th; bool _led_conn; @@ -247,13 +240,9 @@ class AdafruitBluefruit BLEConnection* _connection[BLE_MAX_CONNECTION]; - rssi_callback_t _rssi_cb; - void (*_event_cb) (ble_evt_t*); - -COMMENT_OUT( - uint8_t _auth_type; - char _pin[BLE_GAP_PASSKEY_LEN]; -) + //------------- Callbacks -------------// + rssi_cb_t _rssi_cb; + event_cb_t _event_cb; /*------------------------------------------------------------------*/ /* INTERNAL USAGE ONLY diff --git a/libraries/Bluefruit52Lib/src/bluefruit_common.h b/libraries/Bluefruit52Lib/src/bluefruit_common.h index 89fde753a..b8004c39b 100644 --- a/libraries/Bluefruit52Lib/src/bluefruit_common.h +++ b/libraries/Bluefruit52Lib/src/bluefruit_common.h @@ -68,4 +68,23 @@ typedef void (*ble_connect_callback_t ) (uint16_t conn_hdl); typedef void (*ble_disconnect_callback_t ) (uint16_t conn_hdl, uint8_t reason); +enum SecureMode_t +{ + SECMODE_NO_ACCESS = 0x00, + SECMODE_OPEN = 0x11, + SECMODE_ENC_NO_MITM = 0x21, + SECMODE_ENC_WITH_MITM = 0x31, + SECMODE_ENC_WITH_LESC_MITM = 0x41, // LESC MITM with 128-bit key + SECMODE_SIGNED_NO_MITM = 0x12, + SECMODE_SIGNED_WITH_MITM = 0x22 +}; + +#define BLE_SECMODE_NO_ACCESS ((ble_gap_conn_sec_mode_t) { .sm = 0, .lv = 0 }) +#define BLE_SECMODE_OPEN ((ble_gap_conn_sec_mode_t) { .sm = 1, .lv = 1 }) +#define BLE_SECMODE_ENC_NO_MITM ((ble_gap_conn_sec_mode_t) { .sm = 1, .lv = 2 }) +#define BLE_SECMODE_ENC_WITH_MITM ((ble_gap_conn_sec_mode_t) { .sm = 1, .lv = 3 }) +#define BLE_SECMODE_SIGNED_NO_MITM ((ble_gap_conn_sec_mode_t) { .sm = 2, .lv = 1 }) +#define BLE_SECMODE_SIGNED_WITH_MITM ((ble_gap_conn_sec_mode_t) { .sm = 2, .lv = 2 }) + + #endif /* BLUEFRUIT_COMMON_H_ */ diff --git a/libraries/Bluefruit52Lib/src/services/BLEDfu.cpp b/libraries/Bluefruit52Lib/src/services/BLEDfu.cpp index 110b758b9..3f3a241a1 100644 --- a/libraries/Bluefruit52Lib/src/services/BLEDfu.cpp +++ b/libraries/Bluefruit52Lib/src/services/BLEDfu.cpp @@ -137,11 +137,10 @@ static void bledfu_control_wr_authorize_cb(uint16_t conn_hdl, BLECharacteristic* // Get Bond Data or using Address if not bonded peer_data->addr = conn->getPeerAddr(); - if ( conn->paired() ) + if ( conn->secured() ) { bond_keys_t bkeys; - - if ( conn->loadKeys(&bkeys) ) + if ( conn->loadBondKey(&bkeys) ) { peer_data->addr = bkeys.peer_id.id_addr_info; peer_data->irk = bkeys.peer_id.id_info; @@ -184,7 +183,8 @@ static void bledfu_control_wr_authorize_cb(uint16_t conn_hdl, BLECharacteristic* } } -BLEDfu::BLEDfu(void) : BLEService(UUID128_SVC_DFU_OTA), _chr_control(UUID128_CHR_DFU_CONTROL) +BLEDfu::BLEDfu(void) + : BLEService(UUID128_SVC_DFU_OTA), _chr_control(UUID128_CHR_DFU_CONTROL) { } diff --git a/libraries/Bluefruit52Lib/src/utility/bonding.cpp b/libraries/Bluefruit52Lib/src/utility/bonding.cpp index 5c5f666aa..c4bf8c222 100644 --- a/libraries/Bluefruit52Lib/src/utility/bonding.cpp +++ b/libraries/Bluefruit52Lib/src/utility/bonding.cpp @@ -43,7 +43,7 @@ using namespace Adafruit_LittleFS_Namespace; -#define BOND_DEBUG 0 +#define BOND_DEBUG 1 #if (CFG_DEBUG == 1 && BOND_DEBUG == 1) || (CFG_DEBUG >= 2) #define BOND_LOG(...) LOG_LV1("BOND", __VA_ARGS__) @@ -53,18 +53,19 @@ using namespace Adafruit_LittleFS_Namespace; /*------------------------------------------------------------------*/ /* Bond Key is saved in following layout - * - Bond Data : 80 bytes - * - Name : variable (including null char) - * - CCCD : variable + * - Keyset : 80 bytes (sizeof(bond_keys_t)) + * - Name : variable (including null char) + * - CCCD : variable * * Each field has an 1-byte preceding length *------------------------------------------------------------------*/ #define SVC_CONTEXT_FLAG (BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS | BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS) #define BOND_FNAME_LEN max(sizeof(BOND_FNAME_PRPH), sizeof(BOND_FNAME_CNTR)) -static void get_fname (char* fname, uint8_t role, uint16_t ediv) +static void get_fname (char* fname, uint8_t role, uint8_t const mac[6]) { - sprintf(fname, (role == BLE_GAP_ROLE_PERIPH) ? BOND_FNAME_PRPH : BOND_FNAME_CNTR, ediv); + sprintf(fname, (role == BLE_GAP_ROLE_PERIPH) ? BOND_FNAME_PRPH : BOND_FNAME_CNTR, + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } static bool bdata_skip_field(File* file) @@ -94,12 +95,14 @@ void bond_init(void) /*------------------------------------------------------------------*/ /* Keys *------------------------------------------------------------------*/ -static void bond_save_keys_dfr (uint8_t role, uint16_t conn_hdl, bond_keys_t* bkeys) +static void bond_save_keys_dfr (uint8_t role, uint16_t conn_hdl, bond_keys_t const * bkeys) { uint16_t const ediv = (role == BLE_GAP_ROLE_PERIPH) ? bkeys->own_enc.master_id.ediv : bkeys->peer_enc.master_id.ediv; + uint8_t const * mac = bkeys->peer_id.id_addr_info.addr; + // Bond store keys using peer mac address e.g 1a2b3c4d5e6f char filename[BOND_FNAME_LEN]; - get_fname(filename, role, ediv); + get_fname(filename, role, mac); // delete if file already exists if ( InternalFS.exists(filename) ) InternalFS.remove(filename); @@ -117,7 +120,6 @@ static void bond_save_keys_dfr (uint8_t role, uint16_t conn_hdl, bond_keys_t* bk // If couldn't get devname then use peer mac address if ( !devname[0] ) { - uint8_t* mac = bkeys->peer_id.id_addr_info.addr; sprintf(devname, "%02X:%02X:%02X:%02X:%02X:%02X", mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]); } @@ -128,36 +130,83 @@ static void bond_save_keys_dfr (uint8_t role, uint16_t conn_hdl, bond_keys_t* bk file.close(); } -bool bond_save_keys (uint8_t role, uint16_t conn_hdl, bond_keys_t* bkeys) +bool bond_save_keys (uint8_t role, uint16_t conn_hdl, bond_keys_t const* bkeys) { // queue to execute in Ada Callback thread - return ada_callback(bkeys, sizeof(bond_keys_t), bond_save_keys_dfr, role, conn_hdl, bkeys); + return ada_callback(NULL, 0, bond_save_keys_dfr, role, conn_hdl, bkeys); } -bool bond_load_keys(uint8_t role, uint16_t ediv, bond_keys_t* bkeys) +bool bond_load_keys(uint8_t role, ble_gap_addr_t* addr, bond_keys_t* bkeys) { - char filename[BOND_FNAME_LEN]; - get_fname(filename, role, ediv); + bool ret = false; - File file(filename, FILE_O_READ, InternalFS); - VERIFY(file); + switch(addr->addr_type) + { + case BLE_GAP_ADDR_TYPE_PUBLIC: + case BLE_GAP_ADDR_TYPE_RANDOM_STATIC: + { + // Peer probably uses RANDOM_STATIC or in rarer case PUBLIC + // Address is used as identity - int keylen = file.read(); - VERIFY(keylen == sizeof(bond_keys_t)); + char filename[BOND_FNAME_LEN]; + get_fname(filename, role, addr->addr); - file.read(bkeys, keylen); - file.close(); + File file(InternalFS); + if( file.open(filename, FILE_O_READ) ) + { + int keylen = file.read(); + if ( keylen > 0 ) + { + file.read((uint8_t*) bkeys, keylen); - BOND_LOG("Loaded keys from file %s", filename); + ret = true; + BOND_LOG("Loaded keys from file %s", filename); + } + } - return true; + file.close(); + } + break; + + case BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE: + { + // Resolvable address, we have to go through the whole list to perform IRK Address matching + + char const * dpath = (role == BLE_GAP_ROLE_PERIPH ? BOND_DIR_PRPH : BOND_DIR_CNTR); + File dir(dpath, FILE_O_READ, InternalFS); + File file(InternalFS); + + while ( !ret && (file = dir.openNextFile(FILE_O_READ)) ) + { + int keylen = file.read(); + if ( keylen == sizeof(bond_keys_t) ) + { + file.read((uint8_t*) bkeys, keylen); + if ( Bluefruit.Security.resolveAddress(addr, &bkeys->peer_id.id_info) ) + { + ret = true; + BOND_LOG("Loaded keys from file %s/%s", dpath, file.name()); + } + } + + file.close(); + } + + dir.close(); + } + break; + + default: return false; // Non Resolvable & Anonymous are not supported + } + + return ret; } /*------------------------------------------------------------------*/ /* CCCD *------------------------------------------------------------------*/ -static void bond_save_cccd_dfr (uint8_t role, uint16_t conn_hdl, uint16_t ediv) +static void bond_save_cccd_dfr (uint8_t role, uint16_t conn_hdl, ble_gap_addr_t const* id_addr) { uint16_t len=0; sd_ble_gatts_sys_attr_get(conn_hdl, NULL, &len, SVC_CONTEXT_FLAG); @@ -167,7 +216,7 @@ static void bond_save_cccd_dfr (uint8_t role, uint16_t conn_hdl, uint16_t ediv) VERIFY_STATUS(sd_ble_gatts_sys_attr_get(conn_hdl, sys_attr, &len, SVC_CONTEXT_FLAG),); char filename[BOND_FNAME_LEN]; - get_fname(filename, role, ediv); + get_fname(filename, role, id_addr->addr); File file(filename, FILE_O_WRITE, InternalFS); VERIFY(file,); @@ -176,60 +225,66 @@ static void bond_save_cccd_dfr (uint8_t role, uint16_t conn_hdl, uint16_t ediv) bdata_skip_field(&file); // skip key bdata_skip_field(&file); // skip name - bdata_write(&file, sys_attr, len); + // only write if there is any data changes + bool do_write = true; - BOND_LOG("Saved CCCD setting to file %s ( offset = %ld, len = %d bytes )", filename, file.size() - (len + 1), len); + if ( len == ((uint16_t) file.read()) ) + { + uint8_t old_data[len]; + file.read(old_data, len); + + if ( 0 == memcmp(sys_attr, old_data, len) ) + { + do_write = false; + BOND_LOG("CCCD matches file %s contents, no need to write", filename); + } + } + + if (do_write) + { + bdata_write(&file, sys_attr, len); + BOND_LOG("Saved CCCD to file %s ( offset = %ld, len = %d bytes )", filename, file.size() - (len + 1), len); + } file.close(); } -bool bond_save_cccd (uint8_t role, uint16_t conn_hdl, uint16_t ediv) +bool bond_save_cccd (uint8_t role, uint16_t conn_hdl, ble_gap_addr_t const* id_addr) { - VERIFY(ediv != 0xFFFF); - // queue to execute in Ada Callback thread - return ada_callback(NULL, 0, bond_save_cccd_dfr, role, conn_hdl, ediv); + return ada_callback(id_addr, sizeof(ble_gap_addr_t), bond_save_cccd_dfr, role, conn_hdl, id_addr); } -bool bond_load_cccd(uint8_t role, uint16_t conn_hdl, uint16_t ediv) +bool bond_load_cccd(uint8_t role, uint16_t conn_hdl, ble_gap_addr_t const* id_addr) { bool loaded = false; - if ( ediv != 0xFFFF ) - { - char filename[BOND_FNAME_LEN]; - get_fname(filename, role, ediv); + char filename[BOND_FNAME_LEN]; + get_fname(filename, role, id_addr->addr); - File file(filename, FILE_O_READ, InternalFS); + File file(filename, FILE_O_READ, InternalFS); + if ( file ) + { + bdata_skip_field(&file); // skip key + bdata_skip_field(&file); // skip name - if ( file ) + int len = file.read(); + if ( len > 0 ) { - bdata_skip_field(&file); // skip key - bdata_skip_field(&file); // skip name + uint8_t sys_attr[len]; + file.read(sys_attr, len); - int len = file.read(); - if ( len > 0 ) + if ( ERROR_NONE == sd_ble_gatts_sys_attr_set(conn_hdl, sys_attr, len, SVC_CONTEXT_FLAG) ) { - uint8_t sys_attr[len]; - - file.read(sys_attr, len); - - if ( ERROR_NONE == sd_ble_gatts_sys_attr_set(conn_hdl, sys_attr, len, SVC_CONTEXT_FLAG) ) - { - loaded = true; - BOND_LOG("Loaded CCCD from file %s ( offset = %ld, len = %d bytes )", filename, file.size() - (len + 1), len); - } + loaded = true; + BOND_LOG("Loaded CCCD from file %s ( offset = %ld, len = %d bytes )", filename, file.size() - (len + 1), len); } } - - file.close(); } - if ( !loaded ) - { - LOG_LV1("BOND", "CCCD setting not found"); - } + file.close(); + if ( !loaded ) LOG_LV1("BOND", "CCCD setting not found"); return loaded; } @@ -263,44 +318,6 @@ void bond_print_list(uint8_t role) dir.close(); } - -bool bond_find_cntr(ble_gap_addr_t const * addr, bond_keys_t* bkeys) -{ - bool found = false; - - File dir(BOND_DIR_CNTR, FILE_O_READ, InternalFS); - File file(InternalFS); - - while ( (file = dir.openNextFile(FILE_O_READ)) ) - { - // Read bond data of each stored file - int keylen = file.read(); - if ( keylen == sizeof(bond_keys_t) ) - { - file.read((uint8_t*) bkeys, keylen); - - // Compare static address - if ( !memcmp(addr->addr, bkeys->peer_id.id_addr_info.addr, 6) ) - { - found = true; - } - else if ( addr->addr_type == BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE ) - { - // Resolving private address - } - } - - file.close(); - - if ( found ) break; - } - - file.close(); - dir.close(); - - return found; -} - /*------------------------------------------------------------------*/ /* DELETE *------------------------------------------------------------------*/ @@ -320,7 +337,6 @@ void bond_clear_cntr(void) // Create an empty one InternalFS.mkdir(BOND_DIR_CNTR); - } void bond_clear_all(void) @@ -334,10 +350,12 @@ void bond_clear_all(void) InternalFS.mkdir(BOND_DIR_CNTR); } -void bond_remove_key(uint8_t role, uint16_t ediv) +void bond_remove_key(uint8_t role, ble_gap_addr_t const* id_addr) { char filename[BOND_FNAME_LEN]; - get_fname(filename, role, ediv); + get_fname(filename, role, id_addr->addr); InternalFS.remove(filename); + + BOND_LOG("Removed keys from file %s", filename); } diff --git a/libraries/Bluefruit52Lib/src/utility/bonding.h b/libraries/Bluefruit52Lib/src/utility/bonding.h index b23cb2c5d..f6c4dfb98 100644 --- a/libraries/Bluefruit52Lib/src/utility/bonding.h +++ b/libraries/Bluefruit52Lib/src/utility/bonding.h @@ -41,8 +41,8 @@ #define BOND_DIR_PRPH "/adafruit/bond_prph" #define BOND_DIR_CNTR "/adafruit/bond_cntr" -#define BOND_FNAME_PRPH BOND_DIR_PRPH "/%04x" -#define BOND_FNAME_CNTR BOND_DIR_CNTR "/%04x" +#define BOND_FNAME_PRPH BOND_DIR_PRPH "/%02X%02X%02X%02X%02X%02X" +#define BOND_FNAME_CNTR BOND_DIR_CNTR "/%02X%02X%02X%02X%02X%02X" // Shared keys with bonded device, size = 80 bytes typedef struct @@ -57,18 +57,14 @@ void bond_clear_prph(void); void bond_clear_cntr(void); void bond_clear_all(void); -void bond_remove_key(uint8_t role, uint16_t ediv); +void bond_remove_key(uint8_t role, ble_gap_addr_t const* id_addr); -bool bond_save_keys (uint8_t role, uint16_t conn_hdl, bond_keys_t* bkeys); -bool bond_load_keys(uint8_t role, uint16_t ediv, bond_keys_t* bkeys); +bool bond_save_keys (uint8_t role, uint16_t conn_hdl, bond_keys_t const* bkeys); +bool bond_load_keys(uint8_t role, ble_gap_addr_t* peer_addr, bond_keys_t* bkeys); -bool bond_save_cccd (uint8_t role, uint16_t conn_hdl, uint16_t ediv); -bool bond_load_cccd (uint8_t role, uint16_t conn_hdl, uint16_t ediv); +bool bond_save_cccd (uint8_t role, uint16_t conn_hdl, ble_gap_addr_t const* id_addr); +bool bond_load_cccd (uint8_t role, uint16_t conn_hdl, ble_gap_addr_t const* id_addr); void bond_print_list(uint8_t role); -bool bond_find_cntr(ble_gap_addr_t const * addr, bond_keys_t* bkeys); - - - #endif /* BONDING_H_ */ diff --git a/platform.txt b/platform.txt index a5a457e65..3c602a267 100644 --- a/platform.txt +++ b/platform.txt @@ -37,7 +37,8 @@ compiler.c.flags=-mcpu={build.mcu} -mthumb -c -g {compiler.warning_flags} {build compiler.c.elf.cmd=arm-none-eabi-gcc compiler.c.elf.flags={compiler.optimization_flag} -Wl,--gc-sections -save-temps compiler.S.cmd=arm-none-eabi-gcc -compiler.S.flags=-c -g -x assembler-with-cpp +compiler.S.flags=-mcpu={build.mcu} -mthumb -mabi=aapcs {compiler.optimization_flag} -g -c {build.float_flags} -x assembler-with-cpp + compiler.cpp.cmd=arm-none-eabi-g++ compiler.cpp.flags=-mcpu={build.mcu} -mthumb -c -g {compiler.warning_flags} {build.float_flags} -std=gnu++11 -ffunction-sections -fdata-sections -fno-threadsafe-statics -nostdlib --param max-inline-insns-single=500 -fno-rtti -fno-exceptions -MMD compiler.ar.cmd=arm-none-eabi-ar @@ -61,9 +62,9 @@ build.sysview_flags=-DCFG_SYSVIEW=0 rtos.path={build.core.path}/freertos nordic.path={build.core.path}/nordic -# build.logger_flags and build.sysview_flags and intentionally empty, +# build.logger_flags and build.sysview_flags are intentionally empty, # to allow modification via a user's own boards.local.txt or platform.local.txt files. -build.flags.nrf= -DSOFTDEVICE_PRESENT -DARDUINO_NRF52_ADAFRUIT -DNRF52_SERIES -DLFS_NAME_MAX=64 {compiler.optimization_flag} {build.debug_flags} {build.logger_flags} {build.sysview_flags} "-I{build.core.path}/cmsis/Core/Include" "-I{nordic.path}" "-I{nordic.path}/nrfx" "-I{nordic.path}/nrfx/hal" "-I{nordic.path}/nrfx/mdk" "-I{nordic.path}/nrfx/soc" "-I{nordic.path}/nrfx/drivers/include" "-I{nordic.path}/nrfx/drivers/src" "-I{nordic.path}/softdevice/{build.sd_name}_nrf52_{build.sd_version}_API/include" "-I{nordic.path}/softdevice/{build.sd_name}_nrf52_{build.sd_version}_API/include/nrf52" "-I{rtos.path}/Source/include" "-I{rtos.path}/config" "-I{rtos.path}/portable/GCC/nrf52" "-I{rtos.path}/portable/CMSIS/nrf52" "-I{build.core.path}/sysview/SEGGER" "-I{build.core.path}/sysview/Config" "-I{build.core.path}/TinyUSB" "-I{build.core.path}/TinyUSB/Adafruit_TinyUSB_ArduinoCore" "-I{build.core.path}/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src" +build.flags.nrf= -DSOFTDEVICE_PRESENT -DARDUINO_NRF52_ADAFRUIT -DNRF52_SERIES -DDX_CC_TEE -DLFS_NAME_MAX=64 {compiler.optimization_flag} {build.debug_flags} {build.logger_flags} {build.sysview_flags} "-I{build.core.path}/cmsis/Core/Include" "-I{nordic.path}" "-I{nordic.path}/nrfx" "-I{nordic.path}/nrfx/hal" "-I{nordic.path}/nrfx/mdk" "-I{nordic.path}/nrfx/soc" "-I{nordic.path}/nrfx/drivers/include" "-I{nordic.path}/nrfx/drivers/src" "-I{nordic.path}/softdevice/{build.sd_name}_nrf52_{build.sd_version}_API/include" "-I{nordic.path}/softdevice/{build.sd_name}_nrf52_{build.sd_version}_API/include/nrf52" "-I{rtos.path}/Source/include" "-I{rtos.path}/config" "-I{rtos.path}/portable/GCC/nrf52" "-I{rtos.path}/portable/CMSIS/nrf52" "-I{build.core.path}/sysview/SEGGER" "-I{build.core.path}/sysview/Config" "-I{build.core.path}/TinyUSB" "-I{build.core.path}/TinyUSB/Adafruit_TinyUSB_ArduinoCore" "-I{build.core.path}/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src" # usb flags build.flags.usb= -DUSBCON -DUSE_TINYUSB -DUSB_VID={build.vid} -DUSB_PID={build.pid} '-DUSB_MANUFACTURER={build.usb_manufacturer}' '-DUSB_PRODUCT={build.usb_product}' @@ -95,7 +96,7 @@ recipe.S.o.pattern="{compiler.path}{compiler.S.cmd}" {compiler.S.flags} -DF_CPU= recipe.ar.pattern="{compiler.path}{compiler.ar.cmd}" {compiler.ar.flags} {compiler.ar.extra_flags} "{archive_file_path}" "{object_file}" ## Combine gc-sections, archives, and objects -recipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" "-L{build.path}" {compiler.c.elf.flags} {compiler.c.elf.extra_flags} "-L{build.core.path}/linker" "-T{build.ldscript}" "-Wl,-Map,{build.path}/{build.project_name}.map" {compiler.ldflags} -o "{build.path}/{build.project_name}.elf" {object_files} {compiler.libraries.ldflags} -Wl,--start-group -lm "{build.path}/{archive_file}" -Wl,--end-group +recipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" "-L{build.path}" {compiler.c.elf.flags} {compiler.c.elf.extra_flags} "-L{build.core.path}/linker" "-T{build.ldscript}" "-Wl,-Map,{build.path}/{build.project_name}.map" {compiler.ldflags} -o "{build.path}/{build.project_name}.elf" {object_files} -Wl,--start-group -lm "{build.path}/{archive_file}" {compiler.libraries.ldflags} -Wl,--end-group ## Create output (bin file) #recipe.objcopy.bin.pattern="{compiler.path}{compiler.elf2bin.cmd}" {compiler.elf2bin.flags} {compiler.elf2bin.extra_flags} "{build.path}/{build.project_name}.elf" "{build.path}/{build.project_name}.bin"