Recover stale BLE bonds

This commit is contained in:
mikecarper committed 2026-07-20 14:26:16 -07:00
1 parent 57cc977e58
commit 1a9b37d62b
3 files changed
+88 -5

No files matched your search

+14 -2
View File
@@ -1,5 +1,6 @@
#include "SerialBLEInterface.h"
#include "esp_mac.h"
#include "esp_gap_ble_api.h"
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
@@ -92,9 +93,20 @@ void SerialBLEInterface::onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) {
BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Success");
deviceConnected = true;
} else {
BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Failure*");
BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Failure, reason=%u", (unsigned)cmpl.fail_reason);
//pServer->removePeerDevice(pServer->getConnId(), true);
// Firmware flashing normally preserves the Bluetooth bond database. If
// either side has forgotten or replaced its key, retaining the local key
// makes every reconnect fail until the device is fully erased. Remove only
// the peer whose authentication just failed; successful bonds are kept.
esp_err_t remove_result = esp_ble_remove_bond_device(cmpl.bd_addr);
if (remove_result == ESP_OK) {
BLE_DEBUG_PRINTLN(" - SecurityCallback - Cleared failed peer bond");
} else {
BLE_DEBUG_PRINTLN(" - SecurityCallback - No peer bond cleared, err=%d", (int)remove_result);
}
deviceConnected = false;
pServer->disconnect(pServer->getConnId());
adv_restart_time = millis() + ADVERT_RESTART_DELAY;
}
+68 -3
View File
@@ -3,6 +3,7 @@
#include <string.h>
#include "ble_gap.h"
#include "ble_hci.h"
#include <utility/bonding.h>
// Magic numbers came from actual testing
#define BLE_HEALTH_CHECK_INTERVAL 10000 // Advertising watchdog check every 10 seconds
@@ -24,6 +25,12 @@
static SerialBLEInterface* instance = nullptr;
static bool isBondAuthenticationFailure(uint8_t reason) {
return reason == BLE_HCI_AUTHENTICATION_FAILURE ||
reason == BLE_HCI_STATUS_CODE_PIN_OR_KEY_MISSING ||
reason == BLE_HCI_CONN_TERMINATED_DUE_TO_MIC_FAILURE;
}
void SerialBLEInterface::onConnect(uint16_t connection_handle) {
BLE_DEBUG_PRINTLN("SerialBLEInterface: connected handle=0x%04X", connection_handle);
if (instance) {
@@ -48,6 +55,17 @@ void SerialBLEInterface::onSecured(uint16_t connection_handle) {
BLE_DEBUG_PRINTLN("SerialBLEInterface: onSecured handle=0x%04X", connection_handle);
if (instance) {
if (instance->isValidConnection(connection_handle, true)) {
BLEConnection* conn = Bluefruit.Connection(connection_handle);
if (conn == nullptr || !conn->secured()) {
BLE_DEBUG_PRINTLN("SerialBLEInterface: security update did not secure the link");
instance->_isDeviceConnected = false;
if (conn != nullptr && conn->bonded()) {
instance->removeStoredBondForPeer("unsecured link");
}
instance->disconnect();
return;
}
instance->_isDeviceConnected = true;
// Connection interval units: 1.25ms, supervision timeout units: 10ms
@@ -89,7 +107,12 @@ void SerialBLEInterface::onPairingComplete(uint16_t connection_handle, uint8_t a
if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) {
BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing successful");
} else {
BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing failed, disconnecting");
BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing failed, clearing stale bond and disconnecting");
BLEConnection* conn = Bluefruit.Connection(connection_handle);
if (conn != nullptr && conn->bonded()) {
instance->removeStoredBondForPeer("pairing failure");
}
instance->_isDeviceConnected = false;
instance->disconnect();
}
} else {
@@ -100,8 +123,32 @@ void SerialBLEInterface::onPairingComplete(uint16_t connection_handle, uint8_t a
void SerialBLEInterface::onBLEEvent(ble_evt_t* evt) {
if (!instance) return;
if (evt->header.evt_id == BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST) {
if (evt->header.evt_id == BLE_GAP_EVT_CONNECTED) {
ble_gap_evt_connected_t const* connected = &evt->evt.gap_evt.params.connected;
if (connected->role == BLE_GAP_ROLE_PERIPH) {
instance->_peer_address = connected->peer_addr;
instance->_peer_address_valid = true;
instance->_bond_removed_for_connection = false;
}
} else if (evt->header.evt_id == BLE_GAP_EVT_CONN_SEC_UPDATE) {
uint16_t conn_handle = evt->evt.gap_evt.conn_handle;
BLEConnection* conn = Bluefruit.Connection(conn_handle);
if (conn != nullptr && conn->connected() && !conn->secured()) {
BLE_DEBUG_PRINTLN("CONN_SEC_UPDATE: link is not secured, bonded=%d", conn->bonded());
if (conn->bonded()) {
instance->removeStoredBondForPeer("failed bond encryption");
}
instance->_isDeviceConnected = false;
sd_ble_gap_disconnect(conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
}
} else if (evt->header.evt_id == BLE_GAP_EVT_DISCONNECTED) {
ble_gap_evt_disconnected_t const* disconnected = &evt->evt.gap_evt.params.disconnected;
if (isBondAuthenticationFailure(disconnected->reason)) {
instance->removeStoredBondForPeer("authentication disconnect");
}
instance->_peer_address_valid = false;
} else if (evt->header.evt_id == BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST) {
uint16_t conn_handle = evt->evt.gap_evt.conn_handle;
if (instance->isValidConnection(conn_handle)) {
BLE_DEBUG_PRINTLN("CONN_PARAM_UPDATE_REQUEST: handle=0x%04X, min_interval=%u, max_interval=%u, latency=%u, timeout=%u",
@@ -123,6 +170,24 @@ void SerialBLEInterface::onBLEEvent(ble_evt_t* evt) {
}
}
bool SerialBLEInterface::removeStoredBondForPeer(const char* cause) {
if (!_peer_address_valid || _bond_removed_for_connection) {
return false;
}
ble_gap_addr_t peer_address = _peer_address;
bond_keys_t bond_keys;
if (!bond_load_keys(BLE_GAP_ROLE_PERIPH, &peer_address, &bond_keys)) {
BLE_DEBUG_PRINTLN("SerialBLEInterface: no stored peer bond to clear (%s)", cause);
return false;
}
bond_remove_key(BLE_GAP_ROLE_PERIPH, &bond_keys.peer_id.id_addr_info);
_bond_removed_for_connection = true;
BLE_DEBUG_PRINTLN("SerialBLEInterface: cleared stale peer bond (%s)", cause);
return true;
}
void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code) {
instance = this;
+6
View File
@@ -15,6 +15,9 @@ class SerialBLEInterface : public BaseSerialInterface {
uint16_t _conn_handle;
unsigned long _last_health_check;
unsigned long _last_retry_attempt;
ble_gap_addr_t _peer_address = {};
bool _peer_address_valid;
bool _bond_removed_for_connection;
struct Frame {
uint8_t len;
@@ -32,6 +35,7 @@ class SerialBLEInterface : public BaseSerialInterface {
void clearBuffers();
void shiftSendQueueLeft();
void shiftRecvQueueLeft();
bool removeStoredBondForPeer(const char* cause);
bool isValidConnection(uint16_t handle, bool requireWaitingForSecurity = false) const;
bool isAdvertising() const;
static void onConnect(uint16_t connection_handle);
@@ -49,6 +53,8 @@ public:
_conn_handle = BLE_CONN_HANDLE_INVALID;
_last_health_check = 0;
_last_retry_attempt = 0;
_peer_address_valid = false;
_bond_removed_for_connection = false;
send_queue_len = 0;
recv_queue_len = 0;
}