Chapter 61: BLE Bonding and Device Management
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the significance of managing bonded BLE devices.
- Explain how bonding information, including security keys, is stored on the ESP32 using Non-Volatile Storage (NVS).
- Describe the role of the Identity Resolving Key (IRK) in device privacy and address resolution.
- Utilize ESP-IDF APIs to retrieve the list of bonded devices.
- Implement functionality to remove bonding information for a specific device or all devices.
- Understand the implications of managing the bond list for application behavior and security.
- Handle reconnections with bonded devices efficiently.
- Recognize best practices for managing the lifecycle of bonded relationships.
Introduction
In Chapter 60, we explored the critical processes of BLE pairing and the initial establishment of secure connections. A key outcome of successful pairing, when desired, is bonding – the act of storing the generated security keys and peer device information. This allows devices to recognize each other and re-establish secure connections quickly in the future without repeating the entire pairing procedure.
While bonding enhances user convenience and maintains security over time, it also introduces the need for device management. Applications may need to know which devices they are bonded with, remove outdated bonds, or even clear all bonding information, perhaps due to a factory reset or a change in security policy. Furthermore, understanding how these stored bonds affect privacy, especially concerning device tracking and address resolution, is crucial.
This chapter focuses on the practical aspects of managing bonded BLE devices using ESP-IDF v5.x. We will delve into how the ESP32 stores bonding data, how to interact with this stored information using ESP-IDF APIs, and the best practices for maintaining a clean and secure list of trusted devices.
Theory
Bonding transforms a transient, paired relationship into a persistent one. This persistence relies on storing critical security material.
Recap: What is Stored During Bonding?
When two BLE devices bond, they typically store the following information about each other:
- Long Term Key (LTK): This is the primary key used to encrypt and decrypt communications on subsequent connections. It’s generated during pairing (either directly in LE Secure Connections or derived from the STK in legacy pairing).
- Identity Resolving Key (IRK): This key is fundamental for privacy. It’s used by a device to generate Resolvable Private Addresses (RPAs) for advertising and initiating connections. The peer device, possessing this IRK, can then resolve the RPA back to the sender’s fixed identity address.
- Identity Address and Address Type: The stable public or static random address of the bonded peer device. This is linked with the IRK for address resolution.
- Connection Signature Resolving Key (CSRK) (Optional): Used for signing data at the ATT layer to ensure data integrity, particularly for unencrypted characteristics. If link-layer encryption is active (which is typical after bonding), the CSRK’s role is often superseded by the integrity checks provided by AES-CCM encryption.
- Other Security Attributes: Potentially information about the level of authentication achieved during pairing (e.g., whether MITM protection was used).
Stored Information | Description | Primary Role |
---|---|---|
Long Term Key (LTK) | A 128-bit key generated during pairing (LESC) or derived from STK (Legacy). | Used to encrypt and decrypt all communications on subsequent connections with the bonded device. Essential for confidentiality. |
Identity Resolving Key (IRK) | A 128-bit key used to generate and resolve Resolvable Private Addresses (RPAs). | Enhances device privacy by preventing unauthorized tracking. Allows bonded devices to recognize each other even when using RPAs. |
Identity Address & Address Type | The peer device’s fixed public or static random address and its type. | Used in conjunction with the IRK for address resolution and to uniquely identify the bonded peer. |
Connection Signature Resolving Key (CSRK) (Optional) | A 128-bit key used for signing data at the ATT layer. | Ensures data integrity for unencrypted characteristics. Less critical if the link is already encrypted using LTK. |
Other Security Attributes | May include information about the authentication level achieved during pairing (e.g., MITM protection status, LESC usage). | Provides context about the security of the bond, potentially influencing future security decisions or trust levels. |
Non-Volatile Storage (NVS) and Bonding
On the ESP32, this bonding information needs to persist across power cycles. The ESP-IDF utilizes the Non-Volatile Storage (NVS) library (which we briefly encountered in Chapter 22) for this purpose.
- The BLE stack (Bluedroid) automatically handles the storage and retrieval of bonding data in a dedicated NVS namespace (often named
ble_sec
or similar, though this is an internal detail of the stack). - When bonding is enabled (via
ESP_LE_AUTH_BOND
or similar flags inauth_req
during security parameter setup), and pairing completes successfully with theis_bond
flag set inESP_GAP_BLE_AUTH_CMPL_EVT
, the stack writes the necessary keys and peer information to NVS. - Upon a reconnection attempt from a device whose address is found in the NVS bond list, the stack attempts to use the stored LTK to encrypt the link. If an RPA is used by the peer, the stored IRK is used to resolve it.
Identity Resolving Key (IRK) and Privacy
The IRK is a cornerstone of BLE privacy. Without it, devices using fixed public or static random addresses would be easily trackable by any nearby scanner.
- Resolvable Private Addresses (RPAs): A device with an IRK can generate RPAs. An RPA consists of two parts:
- A random part (changes frequently, e.g., every 15 minutes).
- A hash part, generated using the IRK and the random part.
- Address Resolution: When a device receives an RPA, if it has the sender’s IRK in its bond list, it can perform a cryptographic operation to check if the RPA was generated using that IRK. If it matches, the device knows the identity of the sender.
- Benefits:
- Untrackability by third parties: Scanners without the IRK cannot link different RPAs from the same device over time.
- Recognizability by bonded peers: Bonded devices can still recognize each other despite the changing address.
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%% sequenceDiagram participant DeviceA as Device A (Peripheral, e.g., ESP32 Sensor) participant DeviceB as Device B (Central, e.g., ESP32 Collector) participant OtherScanner as Unauthorized Scanner Note over DeviceA: Device A has its own IRK_A. DeviceA->>DeviceA: 1. Generate Random Number (rand) DeviceA->>DeviceA: 2. Generate RPA_A = hash(IRK_A, rand) Note over DeviceA, OtherScanner: Device A advertises using RPA_A DeviceA-->>OtherScanner: 3. ADV Packet (SourceAddr: RPA_A) OtherScanner-->>OtherScanner: 4. Sees RPA_A. Cannot link to Device A's identity without IRK_A. Note over DeviceA, DeviceB: Device B has previously bonded with Device A and stored IRK_A. DeviceA-->>DeviceB: 5. ADV Packet (SourceAddr: RPA_A) activate DeviceB DeviceB->>DeviceB: 6. Attempt to resolve RPA_A using stored IRK_A: <br> check(IRK_A, RPA_A) alt RPA_A matches IRK_A DeviceB->>DeviceB: 7. Success! RPA_A resolved to Device A's Identity Address. Note right of DeviceB: Device B now knows the advertisement is from Device A. else RPA_A does not match IRK_A (or any other stored IRK) DeviceB->>DeviceB: 8. Failure. RPA_A is from an unknown device or a known device using a different RPA. end deactivate DeviceB
Managing the Bond List
ESP-IDF provides APIs to manage the list of bonded devices stored in NVS.
- Maximum Number of Bonded Devices:
- The BLE stack has a configurable limit on the number of devices it can store bonding information for. This is typically defined in
menuconfig
underComponent config -> Bluetooth -> Bluedroid (or Bluetooth Controller) -> BLE Max Bonds number
. The default is often around 8-16 devices. - If this limit is reached and a new device attempts to bond, the behavior might depend on stack configuration (e.g., oldest bond replaced, or new bond fails). It’s good practice to manage the bond list to avoid hitting this limit unexpectedly.
- The BLE stack has a configurable limit on the number of devices it can store bonding information for. This is typically defined in
- Retrieving the List of Bonded Devices:
esp_err_t esp_ble_get_bond_device_num(void)
: Returns the number of currently bonded devices.esp_err_t esp_ble_get_bond_device_list(int *dev_num, esp_ble_bond_dev_t *dev_list)
:dev_num
: Input: size ofdev_list
array. Output: actual number of devices copied.dev_list
: Pointer to an array ofesp_ble_bond_dev_t
structures to be filled.- The
esp_ble_bond_dev_t
structure contains thebd_addr
(Bluetooth Device Address) of the bonded device.
- Removing a Specific Bonded Device:
esp_err_t esp_ble_remove_bond_device(esp_bd_addr_t bd_addr)
: Removes the bonding information associated with the specified Bluetooth Device Address.- After removal, if the same device reconnects, it will need to go through the full pairing process again to establish a secure connection and (optionally) re-bond.
- This function triggers an
ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT
in the GAP callback, indicating the status of the removal operation.
- Clearing All Bonded Devices:
- ESP-IDF does not provide a single API call to “clear all bonds” directly from the BLE API layer in the same way it does for removing a single device.
- To clear all bonds, you typically need to:
- Get the list of all bonded devices.
- Iterate through the list and call
esp_ble_remove_bond_device()
for each one.
- Alternatively, for a more drastic “factory reset” of bonding information, one could erase the specific NVS namespace used by the BLE stack for security keys. However, this is a lower-level approach and should be done with caution, as the exact namespace name can be an internal stack detail. The iterative removal using BLE APIs is the recommended and safer method.
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%% graph TD A["Start: <b>Clear All Bonds</b> Requested"] --> B{"Get Number of Bonded Devices<br><span class='func-small'>esp_ble_get_bond_device_num()</span>"}; B -- Number > 0 --> C{Allocate Memory for Device List}; C -- Success --> D{"Get List of Bonded Devices<br><span class='func-small'>esp_ble_get_bond_device_list()</span>"}; D -- Success --> E{Loop: For Each Device in List}; E -- Next Device --> F["Remove Bond for Current Device<br><span class='func-small'>esp_ble_remove_bond_device(device_bda)</span>"]; F --> G{Wait for Removal Completion<br><span class='event-small'>ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT</span>}; G -- Success --> E; G -- Failure to Remove --> H["Log Error, Continue<br>(Optional: Retry or Stop)"]; H --> E; E -- All Devices Processed --> I[Free Allocated Memory for List]; I --> J["End: All Bonds Cleared (or attempted)"]; B -- Number == 0 --> K[No Bonded Devices to Clear. End.]; C -- Failure (Memory Allocation) --> L[Error: Cannot Allocate Memory. End.]; D -- Failure (Get List) --> L; classDef func-small fill:#fff,stroke:#333,color:#333,fontSize:10px; classDef event-small fill:#fff,stroke:#333,color:#333,fontSize:10px,fontStyle:italic; classDef startNode fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef processNode fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef decisionNode fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef endNode fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; classDef errorNode fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B; class A startNode; class B,C,D,E,G decisionNode; class F,H,I processNode; class J,K endNode; class L errorNode;
API Function | Description | Key Parameters / Return Value | Related GAP Event |
---|---|---|---|
esp_ble_get_bond_device_num() | Returns the number of currently bonded BLE devices stored in NVS. | Return: int (number of bonded devices). | N/A |
esp_ble_get_bond_device_list(int *dev_num, esp_ble_bond_dev_t *dev_list) | Retrieves the list of bonded devices (their Bluetooth Device Addresses). | Input *dev_num: size of dev_list array. Output *dev_num: actual number of devices copied. dev_list: array to be filled with esp_ble_bond_dev_t. Return: esp_err_t. |
N/A |
esp_ble_remove_bond_device(esp_bd_addr_t bd_addr) | Removes the bonding information for a specific device identified by its BD_ADDR. | bd_addr: Address of the device to unbond. Return: esp_err_t (indicates if command was accepted). |
ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT (indicates actual removal status) |
Reconnection with Bonded Devices
When a central device attempts to connect to a peripheral it has previously bonded with (or vice-versa if the peripheral initiates security):
- The connecting device (usually the central) may use the peer’s identity address (if known and public) or try to resolve an RPA if the peripheral is advertising with one (using its stored IRK for that peripheral).
- Once the physical link is established, if both devices have valid bonding information for each other, they can use the stored LTK to encrypt the link. This is often referred to as “link re-encryption” or “security re-establishment.”
- This process is much faster than full pairing and does not require user interaction.
- If the LTK is compromised or one side has lost the bonding information, link encryption will fail. The devices might then need to re-pair.
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%% sequenceDiagram participant Central as Central Device (e.g., ESP32 Client) participant Peripheral as Peripheral Device (e.g., ESP32 Server) Note over Central, Peripheral: Both devices have previously bonded and stored LTK and IRK (if used). Peripheral->>Peripheral: Starts Advertising (possibly with RPA generated using its IRK) Central->>Central: Scans for devices alt Peripheral uses RPA Central->>Central: Receives ADV with RPA. Uses stored IRK for Peripheral to resolve RPA. Note right of Central: If RPA resolved, Central knows it's the bonded Peripheral. else Peripheral uses Public/Static Address Central->>Central: Receives ADV with known public/static address. end Central->>Peripheral: 1. Initiate Connection Request (to resolved/known address) activate Peripheral Peripheral-->>Central: 2. Connection Established deactivate Peripheral Note over Central, Peripheral: Security Re-establishment Phase Central->>Peripheral: 3. Start Link Encryption using stored LTK activate Peripheral Peripheral->>Central: 4. Respond to Encryption Request using its stored LTK deactivate Peripheral alt LTKs match and encryption successful Note over Central, Peripheral: 5. Secure Encrypted Link Established (Faster than full pairing) Central->>Peripheral: 6. Can now access secured GATT services/characteristics. else LTKs do not match or one side lost bond Note over Central, Peripheral: 5. Encryption Fails. Central->>Peripheral: 6. May attempt to re-pair (full pairing process). end
Impact of Security Settings on Bonding
The security settings chosen during the initial pairing (as discussed in Chapter 60) influence what is bonded and how reconnections behave:
- Bonding Flag: Must be set in the
auth_req
by both devices if bonding is desired. - MITM Protection: If pairing was done with MITM protection, the established LTK is considered stronger.
- LE Secure Connections (LESC): LESC generates stronger LTKs.
- Key Distribution (INIT_KEY, RSP_KEY): Determines which keys (LTK, IRK, CSRK) are actually exchanged and stored. If IRK is not exchanged, RPA resolution for that peer won’t be possible.
Practical Examples
Prerequisites:
- Two ESP32 boards (one server, one client, as used in Chapter 60 examples).
- Ensure they have successfully paired and bonded in a previous run.
- VS Code with the Espressif IDF Extension.
Example 1: Retrieving and Displaying the List of Bonded Devices
This example shows how a device (either client or server) can check and display its current list of bonded peers.
Code Snippet (to be added to an existing BLE application, e.g., in app_main
or triggered by a command):
#include "esp_gap_ble_api.h"
#include "esp_log.h"
#define BOND_MGMT_TAG "BOND_MGMT"
void display_bonded_devices(void) {
ESP_LOGI(BOND_MGMT_TAG, "Retrieving bonded device list...");
int dev_num = esp_ble_get_bond_device_num();
if (dev_num == 0) {
ESP_LOGI(BOND_MGMT_TAG, "No bonded devices found.");
return;
}
ESP_LOGI(BOND_MGMT_TAG, "Number of bonded devices: %d", dev_num);
esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num);
if (!dev_list) {
ESP_LOGE(BOND_MGMT_TAG, "Failed to allocate memory for bond device list");
return;
}
esp_err_t ret = esp_ble_get_bond_device_list(&dev_num, dev_list);
if (ret != ESP_OK) {
ESP_LOGE(BOND_MGMT_TAG, "Failed to get bond device list: %s", esp_err_to_name(ret));
free(dev_list);
return;
}
ESP_LOGI(BOND_MGMT_TAG, "Bonded Devices:");
for (int i = 0; i < dev_num; i++) {
ESP_LOGI(BOND_MGMT_TAG, " Device %d: %02x:%02x:%02x:%02x:%02x:%02x", i + 1,
dev_list[i].bd_addr[0], dev_list[i].bd_addr[1],
dev_list[i].bd_addr[2], dev_list[i].bd_addr[3],
dev_list[i].bd_addr[4], dev_list[i].bd_addr[5]);
}
free(dev_list);
}
// In your app_main or a suitable place:
// display_bonded_devices();
Build, Flash, Observe:
- Ensure your ESP32 has previously bonded with at least one other BLE device.
- Add the
display_bonded_devices()
function and call it. - Build, flash, and monitor.
- The serial monitor should output the number of bonded devices and their MAC addresses.
Example 2: Removing a Specific Bonded Device
This example demonstrates how to remove bonding information for a known device.
Prerequisites:
- Know the MAC address of a device your ESP32 is currently bonded with (you can get this from Example 1).
Code Snippet:
// BOND_MGMT_TAG and includes from Example 1
// In your GAP event handler (e.g., esp_gap_cb):
// case ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT:
// if (param->remove_bond_dev_cmpl.status == ESP_BT_STATUS_SUCCESS) {
// ESP_LOGI(BOND_MGMT_TAG, "Successfully removed bond for device: %02x:%02x:%02x:%02x:%02x:%02x",
// param->remove_bond_dev_cmpl.bd_addr[0], param->remove_bond_dev_cmpl.bd_addr[1],
// param->remove_bond_dev_cmpl.bd_addr[2], param->remove_bond_dev_cmpl.bd_addr[3],
// param->remove_bond_dev_cmpl.bd_addr[4], param->remove_bond_dev_cmpl.bd_addr[5]);
// } else {
// ESP_LOGE(BOND_MGMT_TAG, "Failed to remove bond, status: %d", param->remove_bond_dev_cmpl.status);
// }
// // You might want to display the updated list of bonded devices here
// display_bonded_devices();
// break;
void remove_specific_bond(esp_bd_addr_t device_to_remove) {
ESP_LOGI(BOND_MGMT_TAG, "Attempting to remove bond for device: %02x:%02x:%02x:%02x:%02x:%02x",
device_to_remove[0], device_to_remove[1], device_to_remove[2],
device_to_remove[3], device_to_remove[4], device_to_remove[5]);
esp_err_t ret = esp_ble_remove_bond_device(device_to_remove);
if (ret == ESP_OK) {
ESP_LOGI(BOND_MGMT_TAG, "Remove bond command sent. Waiting for completion event...");
} else {
ESP_LOGE(BOND_MGMT_TAG, "Failed to send remove bond command: %s", esp_err_to_name(ret));
}
}
// Example usage (e.g., triggered by a serial command or button):
// esp_bd_addr_t target_device_bda = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; // Replace with actual BDA
// remove_specific_bond(target_device_bda);
Build, Flash, Observe:
- Flash the code.
- Trigger the
remove_specific_bond()
function (e.g., by hardcoding a call or via a serial command you implement). - Observe the logs for
ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT
. - Call
display_bonded_devices()
again to confirm the device is no longer listed. - Try reconnecting the removed device; it should now require full re-pairing.
Example 3: Implementing “Clear All Bonds” Functionality
This example shows how to iterate and remove all existing bonds.
Code Snippet:
// BOND_MGMT_TAG and includes from Example 1
// Ensure your GAP callback handles ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT as in Example 2.
// Global or static variable to track removal progress if needed for complex logic
// static int bonds_to_remove_count = 0;
// static int bonds_removed_count = 0;
void clear_all_bonded_devices(void) {
ESP_LOGI(BOND_MGMT_TAG, "Attempting to clear all bonded devices...");
int dev_num = esp_ble_get_bond_device_num();
if (dev_num == 0) {
ESP_LOGI(BOND_MGMT_TAG, "No bonded devices to clear.");
return;
}
esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num);
if (!dev_list) {
ESP_LOGE(BOND_MGMT_TAG, "Failed to allocate memory for bond device list");
return;
}
esp_err_t ret = esp_ble_get_bond_device_list(&dev_num, dev_list);
if (ret != ESP_OK) {
ESP_LOGE(BOND_MGMT_TAG, "Failed to get bond device list for clearing: %s", esp_err_to_name(ret));
free(dev_list);
return;
}
ESP_LOGI(BOND_MGMT_TAG, "Found %d devices to remove.", dev_num);
// bonds_to_remove_count = dev_num; // If tracking completion
// bonds_removed_count = 0;
for (int i = 0; i < dev_num; i++) {
ESP_LOGI(BOND_MGMT_TAG, "Removing bond for device: %02x:%02x:%02x:%02x:%02x:%02x",
dev_list[i].bd_addr[0], dev_list[i].bd_addr[1], dev_list[i].bd_addr[2],
dev_list[i].bd_addr[3], dev_list[i].bd_addr[4], dev_list[i].bd_addr[5]);
ret = esp_ble_remove_bond_device(dev_list[i].bd_addr);
if (ret != ESP_OK) {
ESP_LOGE(BOND_MGMT_TAG, "Failed to send remove bond command for device %d: %s", i, esp_err_to_name(ret));
// Continue trying to remove others
}
// Note: ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT will fire for each.
// For simplicity, we are not waiting for each completion event here before sending the next command.
// In a robust app, you might queue removals or wait for completion if strict sequential removal is needed.
}
free(dev_list);
ESP_LOGI(BOND_MGMT_TAG, "All remove bond commands sent. Check logs for completion events.");
}
// In your GAP callback, after a successful ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT:
// bonds_removed_count++;
// if (bonds_removed_count == bonds_to_remove_count) {
// ESP_LOGI(BOND_MGMT_TAG, "All bonds successfully cleared.");
// bonds_to_remove_count = 0; // Reset counters
// bonds_removed_count = 0;
// }
Build, Flash, Observe:
- Ensure several devices are bonded.
- Call
clear_all_bonded_devices()
. - Monitor the logs. You should see
ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT
for each device being removed. - After all removal commands are sent (and ideally, completion events received), call
display_bonded_devices()
to confirm the list is empty.
Variant Notes
- ESP32-S2: This variant does not have Bluetooth hardware, so BLE bonding and device management are not applicable.
- ESP32, ESP32-C3, ESP32-S3, ESP32-C6, ESP32-H2: The BLE bonding and device management APIs (
esp_ble_get_bond_device_num
,esp_ble_get_bond_device_list
,esp_ble_remove_bond_device
) and their reliance on NVS for storage are consistent across these BLE-enabled ESP32 variants. The underlying NVS system works similarly on all these chips. The maximum number of bonds might be configurable viamenuconfig
and could potentially differ by default based on available flash or RAM for a given SoC target, but the API usage remains the same.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
NVS Not Initialized or Erased | Bonding info not saved across reboots. esp_ble_get_bond_device_list() fails or returns empty list. Devices always re-pair. | Ensure nvs_flash_init() is called and succeeds early in app_main(). Be cautious with flash erase commands during development. |
Incorrect Handling of esp_ble_get_bond_device_list() | Buffer overflows, crashes, or incomplete list of bonded devices. Misinterpreting dev_num parameter. | 1. Get count with esp_ble_get_bond_device_num(). 2. Allocate dev_list buffer: count * sizeof(esp_ble_bond_dev_t). 3. Call esp_ble_get_bond_device_list() with *dev_num set to allocated count. It updates *dev_num to actual copied count. |
Removing Bonds Without Re-pairing Logic | Device fails secure connection or secured characteristic access after bond removal; app doesn’t guide re-pairing. | After removing a bond, be prepared to handle the full pairing process (security requests, passkey, etc.) if a secure connection is needed again with that device. |
Exceeding Max Bondable Devices Limit | New pairing attempts fail, or stack might remove oldest bond. (Default ESP-IDF limit is often 8-16). | Monitor bonded device count. Implement strategy if limit is near (e.g., prompt user to remove old bond, LRU cache). Check/adjust BLE Max Bonds number in menuconfig. |
Race Conditions After Bond Removal | Operations fail if attempting to use device info immediately after esp_ble_remove_bond_device() but before ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT. | Wait for ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT for the specific device before considering its bond fully removed and updating app state. |
Forgetting Bonding Flags During Pairing | Pairing succeeds but no keys are stored. Devices re-pair on every connection. | Ensure bonding flags (e.g., ESP_LE_AUTH_BOND or part of ESP_LE_AUTH_REQ_SC_BOND) are set in auth_req during security parameter setup on both devices. |
IRK Not Exchanged/Stored | Device cannot resolve RPAs from a bonded peer, potentially leading to connection issues if peer uses RPAs and client relies on address resolution. Privacy features are diminished. | Ensure ESP_BLE_ID_KEY_MASK is included in ESP_BLE_SM_SET_INIT_KEY and ESP_BLE_SM_SET_RSP_KEY during security setup if IRK exchange for RPA resolution is desired. |
Exercises
- Bond List Full Management:
- Modify one of your BLE client or server applications. Before attempting a new bond, check if the number of currently bonded devices (
esp_ble_get_bond_device_num()
) has reached a predefined limit (e.g., 3 for testing). - If the limit is reached:
- Display the list of currently bonded devices.
- Prompt the user (e.g., via serial input) to select one of the existing bonded devices to remove.
- Remove the selected device’s bond.
- Then, allow the new device to proceed with pairing and bonding.
- Modify one of your BLE client or server applications. Before attempting a new bond, check if the number of currently bonded devices (
- Automatic Re-pairing on Authentication Failure with Bonded Device:
- Simulate a scenario where a bonded device’s LTK might have become invalid (e.g., the peer device cleared its bonds).
- On the client side, if an attempt to encrypt the link with a supposedly bonded device (
esp_ble_set_encryption()
) fails with an authentication error afterESP_GATTC_OPEN_EVT
, assume the bond is stale. - Automatically remove the local bond for that peer device and then re-initiate the connection and pairing process from scratch.
- Investigate NVS Storage for Bonds (Advanced):
- Caution: This is for educational understanding; do not directly modify these NVS entries unless you are an expert, as it can corrupt BLE stack state.
- After bonding a few devices, use the NVS APIs (
nvs_entry_find
,nvs_entry_next
,nvs_get_str
,nvs_get_blob
) to iterate through the NVS namespace typically used by Bluedroid for bonding (e.g., try namespace “BT_CFG” or “ble_sec” – this might require some experimentation or checking ESP-IDF source). - Attempt to identify keys that correspond to bonded device MAC addresses or stored security keys (like LTK, IRK). Log the key names and their types/sizes. This exercise helps understand where the data physically resides.
Summary
- Bonding allows BLE devices to store security keys (LTK, IRK, CSRK) and peer identity information, enabling quick and secure reconnections.
- ESP-IDF uses Non-Volatile Storage (NVS) to persist bonding data across power cycles.
- The Identity Resolving Key (IRK) is crucial for privacy, allowing devices to use Resolvable Private Addresses (RPAs) while still being recognizable to bonded peers.
- ESP-IDF provides APIs to manage the bond list:
esp_ble_get_bond_device_num()
: Get the count of bonded devices.esp_ble_get_bond_device_list()
: Retrieve the list of bonded device addresses.esp_ble_remove_bond_device()
: Remove bonding information for a specific device.
- Clearing all bonds typically involves iterating through the bond list and removing each entry.
- Properly managing the bond list is essential for handling device limits, ensuring security, and providing a good user experience.
- Reconnection with bonded devices leverages stored keys to quickly re-encrypt the link.
Further Reading
- ESP-IDF API Reference –
esp_gap_ble_api.h
: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/bluetooth/esp_gap_ble.html - ESP-IDF NVS Library Documentation: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/storage/nvs_flash.html
- Bluetooth Core Specification: Volume 3, Part C (Generic Access Profile – for addressing and privacy) and Part H (Security Manager – for key storage and management): https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host/generic-access-profile.html
- ESP-IDF Security Examples: (e.g.,
ble_security_gatts_demo
,ble_security_gattc_demo
) often demonstrate bonding by default and can be extended for management tasks.
