Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 56a2245

Browse files
lucasssvazpre-commit-ci-lite[bot]
andauthored
fix(ble): Fix broken functions and add IRK retrieval (#11948)
* fix(ble): Fix authentication deadlock * feat(irk): Add peer's IRK retrieval methods * fix(ble): Fix deinit function * fix(ble): Fix notification timing * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent fc8ce8f commit 56a2245

File tree

9 files changed

+531
-16
lines changed

9 files changed

+531
-16
lines changed

‎libraries/BLE/examples/Client_secure_static_passkey/Client_secure_static_passkey.ino‎

Lines changed: 86 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
/*
2-
Secure client with static passkey
2+
Secure client with static passkey and IRK retrieval
33
44
This example demonstrates how to create a secure BLE client that connects to
55
a secure BLE server using a static passkey without prompting the user.
66
The client will automatically use the same passkey (123456) as the server.
77
8+
After successful bonding, the example demonstrates how to retrieve the
9+
server's Identity Resolving Key (IRK) in multiple formats:
10+
- Comma-separated hex format: 0x1A,0x1B,0x1C,...
11+
- Base64 encoded (for Home Assistant Private BLE Device service)
12+
- Reverse hex order (for Home Assistant ESPresense)
13+
814
This client is designed to work with the Server_secure_static_passkey example.
915
1016
Note that ESP32 uses Bluedroid by default and the other SoCs use NimBLE.
1117
Bluedroid initiates security on-connect, while NimBLE initiates security on-demand.
1218
This means that in NimBLE you can read the insecure characteristic without entering
1319
the passkey. This is not possible in Bluedroid.
1420
15-
IMPORTANT: MITM (Man-In-The-Middle protection) must be enabled for password prompts
16-
to work. Without MITM, the BLE stack assumes no user interaction is needed and will use
17-
"Just Works" pairing method (with encryption if secure connection is enabled).
21+
IMPORTANT:
22+
- MITM (Man-In-The-Middle protection) must be enabled for password prompts to work.
23+
- Bonding must be enabled to store and retrieve the IRK.
24+
- The server must distribute its Identity Key during pairing.
1825
1926
Based on examples from Neil Kolban and h2zero.
2027
Created by lucasssvaz.
@@ -36,10 +43,59 @@ static BLEUUID secureCharUUID("ff1d2614-e2d6-4c87-9154-6625d39ca7f8");
3643
static boolean doConnect = false;
3744
static boolean connected = false;
3845
static boolean doScan = false;
46+
static BLEClient *pClient = nullptr;
3947
static BLERemoteCharacteristic *pRemoteInsecureCharacteristic;
4048
static BLERemoteCharacteristic *pRemoteSecureCharacteristic;
4149
static BLEAdvertisedDevice *myDevice;
4250

51+
// Print an IRK buffer as hex with leading zeros and ':' separator
52+
static void printIrkBinary(uint8_t *irk) {
53+
for (int i = 0; i < 16; i++) {
54+
if (irk[i] < 0x10) {
55+
Serial.print("0");
56+
}
57+
Serial.print(irk[i], HEX);
58+
if (i < 15) {
59+
Serial.print(":");
60+
}
61+
}
62+
}
63+
64+
static void get_peer_irk(BLEAddress peerAddr) {
65+
Serial.println("\n=== Retrieving peer IRK (Server) ===\n");
66+
67+
uint8_t irk[16];
68+
69+
// Get IRK in binary format
70+
if (BLEDevice::getPeerIRK(peerAddr, irk)) {
71+
Serial.println("Successfully retrieved peer IRK in binary format:");
72+
printIrkBinary(irk);
73+
Serial.println("\n");
74+
}
75+
76+
// Get IRK in different string formats
77+
String irkString = BLEDevice::getPeerIRKString(peerAddr);
78+
String irkBase64 = BLEDevice::getPeerIRKBase64(peerAddr);
79+
String irkReverse = BLEDevice::getPeerIRKReverse(peerAddr);
80+
81+
if (irkString.length() > 0) {
82+
Serial.println("Successfully retrieved peer IRK in multiple formats:\n");
83+
Serial.print("IRK (comma-separated hex): ");
84+
Serial.println(irkString);
85+
Serial.print("IRK (Base64 for Home Assistant Private BLE Device): ");
86+
Serial.println(irkBase64);
87+
Serial.print("IRK (reverse hex for Home Assistant ESPresense): ");
88+
Serial.println(irkReverse);
89+
Serial.println();
90+
} else {
91+
Serial.println("!!! Failed to retrieve peer IRK !!!");
92+
Serial.println("This is expected if bonding is disabled or the peer doesn't distribute its Identity Key.");
93+
Serial.println("To enable bonding, change setAuthenticationMode to: pSecurity->setAuthenticationMode(true, true, true);\n");
94+
}
95+
96+
Serial.println("=======================================\n");
97+
}
98+
4399
// Callback function to handle notifications
44100
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
45101
Serial.print("Notify callback for characteristic ");
@@ -62,11 +118,30 @@ class MyClientCallback : public BLEClientCallbacks {
62118
}
63119
};
64120

121+
// Security callbacks to print IRKs once authentication completes
122+
class MySecurityCallbacks : public BLESecurityCallbacks {
123+
#if defined(CONFIG_BLUEDROID_ENABLED)
124+
void onAuthenticationComplete(esp_ble_auth_cmpl_t desc) override {
125+
// Print the IRK received by the peer
126+
BLEAddress peerAddr(desc.bd_addr);
127+
get_peer_irk(peerAddr);
128+
}
129+
#endif
130+
131+
#if defined(CONFIG_NIMBLE_ENABLED)
132+
void onAuthenticationComplete(ble_gap_conn_desc *desc) override {
133+
// Print the IRK received by the peer
134+
BLEAddress peerAddr(desc->peer_id_addr.val, desc->peer_id_addr.type);
135+
get_peer_irk(peerAddr);
136+
}
137+
#endif
138+
};
139+
65140
bool connectToServer() {
66141
Serial.print("Forming a secure connection to ");
67142
Serial.println(myDevice->getAddress().toString().c_str());
68143

69-
BLEClient *pClient = BLEDevice::createClient();
144+
pClient = BLEDevice::createClient();
70145
Serial.println(" - Created client");
71146

72147
pClient->setClientCallbacks(new MyClientCallback());
@@ -192,15 +267,19 @@ void setup() {
192267
pSecurity->setPassKey(true, CLIENT_PIN);
193268

194269
// Set authentication mode to match server requirements
195-
// Enable secure connection and MITM (for password prompts) for this example
196-
pSecurity->setAuthenticationMode(false, true, true);
270+
// Enable bonding, MITM (for password prompts), and secure connection for this example
271+
// Bonding is required to store and retrieve the IRK
272+
pSecurity->setAuthenticationMode(true, true, true);
197273

198274
// Set IO capability to KeyboardOnly
199275
// We need the proper IO capability for MITM authentication even
200276
// if the passkey is static and won't be entered by the user
201277
// See https://www.bluetooth.com/blog/bluetooth-pairing-part-2-key-generation-methods/
202278
pSecurity->setCapability(ESP_IO_CAP_IN);
203279

280+
// Set callbacks to handle authentication completion and print IRKs
281+
BLEDevice::setSecurityCallbacks(new MySecurityCallbacks());
282+
204283
// Retrieve a Scanner and set the callback we want to use to be informed when we
205284
// have detected a new device. Specify that we want active scanning and start the
206285
// scan to run for 5 seconds.

‎libraries/BLE/examples/Server_secure_static_passkey/Server_secure_static_passkey.ino‎

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,73 @@
4040
// This is an example passkey. You should use a different or random passkey.
4141
#define SERVER_PIN 123456
4242

43+
// Print an IRK buffer as hex with leading zeros and ':' separator
44+
static void printIrkBinary(uint8_t *irk) {
45+
for (int i = 0; i < 16; i++) {
46+
if (irk[i] < 0x10) {
47+
Serial.print("0");
48+
}
49+
Serial.print(irk[i], HEX);
50+
if (i < 15) {
51+
Serial.print(":");
52+
}
53+
}
54+
}
55+
56+
static void get_peer_irk(BLEAddress peerAddr) {
57+
Serial.println("\n=== Retrieving peer IRK (Client) ===\n");
58+
59+
uint8_t irk[16];
60+
61+
// Get IRK in binary format
62+
if (BLEDevice::getPeerIRK(peerAddr, irk)) {
63+
Serial.println("Successfully retrieved peer IRK in binary format:");
64+
printIrkBinary(irk);
65+
Serial.println("\n");
66+
}
67+
68+
// Get IRK in different string formats
69+
String irkString = BLEDevice::getPeerIRKString(peerAddr);
70+
String irkBase64 = BLEDevice::getPeerIRKBase64(peerAddr);
71+
String irkReverse = BLEDevice::getPeerIRKReverse(peerAddr);
72+
73+
if (irkString.length() > 0) {
74+
Serial.println("Successfully retrieved peer IRK in multiple formats:\n");
75+
Serial.print("IRK (comma-separated hex): ");
76+
Serial.println(irkString);
77+
Serial.print("IRK (Base64 for Home Assistant Private BLE Device): ");
78+
Serial.println(irkBase64);
79+
Serial.print("IRK (reverse hex for Home Assistant ESPresense): ");
80+
Serial.println(irkReverse);
81+
Serial.println();
82+
} else {
83+
Serial.println("!!! Failed to retrieve peer IRK !!!");
84+
Serial.println("This is expected if bonding is disabled or the peer doesn't distribute its Identity Key.");
85+
Serial.println("To enable bonding, change setAuthenticationMode to: pSecurity->setAuthenticationMode(true, true, true);\n");
86+
}
87+
88+
Serial.println("=======================================\n");
89+
}
90+
91+
// Security callbacks to print IRKs once authentication completes
92+
class MySecurityCallbacks : public BLESecurityCallbacks {
93+
#if defined(CONFIG_BLUEDROID_ENABLED)
94+
void onAuthenticationComplete(esp_ble_auth_cmpl_t desc) override {
95+
// Print the IRK received by the peer
96+
BLEAddress peerAddr(desc.bd_addr);
97+
get_peer_irk(peerAddr);
98+
}
99+
#endif
100+
101+
#if defined(CONFIG_NIMBLE_ENABLED)
102+
void onAuthenticationComplete(ble_gap_conn_desc *desc) override {
103+
// Print the IRK received by the peer
104+
BLEAddress peerAddr(desc->peer_id_addr.val, desc->peer_id_addr.type);
105+
get_peer_irk(peerAddr);
106+
}
107+
#endif
108+
};
109+
43110
void setup() {
44111
Serial.begin(115200);
45112
Serial.println("Starting BLE work!");
@@ -76,8 +143,11 @@ void setup() {
76143
pSecurity->setCapability(ESP_IO_CAP_OUT);
77144

78145
// Set authentication mode
79-
// Require secure connection and MITM (for password prompts) for this example
80-
pSecurity->setAuthenticationMode(false, true, true);
146+
// Enable bonding, MITM (for password prompts), and secure connection for this example
147+
pSecurity->setAuthenticationMode(true, true, true);
148+
149+
// Set callbacks to handle authentication completion and print IRKs
150+
BLEDevice::setSecurityCallbacks(new MySecurityCallbacks());
81151

82152
BLEServer *pServer = BLEDevice::createServer();
83153
pServer->advertiseOnDisconnect(true);

‎libraries/BLE/src/BLECharacteristic.cpp‎

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,52 @@ void BLECharacteristicCallbacks::onWrite(BLECharacteristic *pCharacteristic, esp
904904

905905
#if defined(CONFIG_NIMBLE_ENABLED)
906906

907+
/**
908+
* @brief Process a deferred write callback.
909+
*
910+
* This function is called as a FreeRTOS task to execute the onWrite callback
911+
* after the write response has been sent to the client. This maintains backwards
912+
* compatibility with Bluedroid, where the write response is sent before the
913+
* onWrite callback is invoked.
914+
*
915+
* The delay is based on the connection interval to ensure the write response
916+
* packet has been transmitted over the air before the callback executes.
917+
*
918+
* See: https://github.com/espressif/arduino-esp32/issues/11938
919+
*/
920+
void BLECharacteristic::processDeferredWriteCallback(void *pvParameters) {
921+
DeferredWriteCallback *pCallback = (DeferredWriteCallback *)pvParameters;
922+
923+
// Get connection parameters to calculate appropriate delay
924+
ble_gap_conn_desc desc;
925+
int rc = ble_gap_conn_find(pCallback->conn_handle, &desc);
926+
927+
if (rc == 0) {
928+
// Connection interval is in units of 1.25ms
929+
// Wait for at least one connection interval to ensure the write response
930+
// has been transmitted. Add a small buffer for processing.
931+
uint16_t intervalMs = (desc.conn_itvl * 125) / 100; // Convert to milliseconds
932+
uint16_t delayMs = intervalMs + 5; // Add 5ms buffer
933+
934+
log_v("Deferring write callback by %dms (conn_interval=%d units, %dms)", delayMs, desc.conn_itvl, intervalMs);
935+
vTaskDelay(pdMS_TO_TICKS(delayMs));
936+
} else {
937+
// If we can't get connection parameters, use a conservative default
938+
// Most connections use 7.5-30ms intervals, so 50ms should be safe
939+
log_w("Could not get connection parameters, using default 50ms delay");
940+
vTaskDelay(pdMS_TO_TICKS(50));
941+
}
942+
943+
// Call the onWrite callback now that the response has been transmitted
944+
pCallback->pCharacteristic->m_pCallbacks->onWrite(pCallback->pCharacteristic, &pCallback->desc);
945+
946+
// Free the allocated memory
947+
delete pCallback;
948+
949+
// Delete this one-shot task
950+
vTaskDelete(NULL);
951+
}
952+
907953
int BLECharacteristic::handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
908954
const ble_uuid_t *uuid;
909955
int rc;
@@ -955,7 +1001,28 @@ int BLECharacteristic::handleGATTServerEvent(uint16_t conn_handle, uint16_t attr
9551001
rc = ble_gap_conn_find(conn_handle, &desc);
9561002
assert(rc == 0);
9571003
pCharacteristic->setValue(buf, len);
958-
pCharacteristic->m_pCallbacks->onWrite(pCharacteristic, &desc);
1004+
1005+
// Defer the onWrite callback to maintain backwards compatibility with Bluedroid.
1006+
// In Bluedroid, the write response is sent BEFORE the onWrite callback is invoked.
1007+
// In NimBLE, the response is sent implicitly when this function returns.
1008+
// By deferring the callback to a separate task with a delay based on the connection
1009+
// interval, we ensure the response packet is transmitted before the callback executes.
1010+
// See: https://github.com/espressif/arduino-esp32/issues/11938
1011+
DeferredWriteCallback *pCallback = new DeferredWriteCallback();
1012+
pCallback->pCharacteristic = pCharacteristic;
1013+
pCallback->desc = desc;
1014+
pCallback->conn_handle = conn_handle;
1015+
1016+
// Create a one-shot task to execute the callback after the response is transmitted
1017+
// Using priority 1 (low priority) and sufficient stack for callback operations
1018+
// Note: Stack must be large enough to handle notify() calls from within onWrite()
1019+
xTaskCreate(
1020+
processDeferredWriteCallback, "BLEWriteCB",
1021+
4096, // Stack size - increased to handle notify() operations
1022+
pCallback,
1023+
1, // Priority (low)
1024+
NULL // Task handle (not needed for one-shot task)
1025+
);
9591026

9601027
return 0;
9611028
}

‎libraries/BLE/src/BLECharacteristic.h‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
#include <host/ble_gatt.h>
4848
#include <host/ble_att.h>
4949
#include "BLEConnInfo.h"
50+
#include <freertos/FreeRTOS.h>
51+
#include <freertos/task.h>
5052
#define ESP_GATT_MAX_ATTR_LEN BLE_ATT_ATTR_MAX_LEN
5153
#define ESP_GATT_CHAR_PROP_BIT_READ BLE_GATT_CHR_PROP_READ
5254
#define ESP_GATT_CHAR_PROP_BIT_WRITE BLE_GATT_CHR_PROP_WRITE
@@ -246,6 +248,13 @@ class BLECharacteristic {
246248
portMUX_TYPE m_readMux;
247249
uint8_t m_removed;
248250
std::vector<std::pair<uint16_t, uint16_t>> m_subscribedVec;
251+
252+
// Deferred callback support for maintaining backwards compatibility with Bluedroid timing
253+
struct DeferredWriteCallback {
254+
BLECharacteristic *pCharacteristic;
255+
ble_gap_conn_desc desc;
256+
uint16_t conn_handle;
257+
};
249258
#endif
250259

251260
/***************************************************************************
@@ -271,6 +280,7 @@ class BLECharacteristic {
271280
#if defined(CONFIG_NIMBLE_ENABLED)
272281
void setSubscribe(struct ble_gap_event *event);
273282
static int handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
283+
static void processDeferredWriteCallback(void *pvParameters);
274284
#endif
275285
}; // BLECharacteristic
276286

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /