Merge branch 'feat/opt_prefer_bond_device_when_evicting_overflow_bonds_v5.5' into 'release/v5.5'

fix(bt/bluedroid): prefer disconnected peers when evicting overflow bonds (5.5)

See merge request espressif/esp-idf!52609
This commit is contained in:
Jiang Jiang Jian
2026-09-10 11:00:18 +08:00
5 changed files with 405 additions and 84 deletions

View File

@@ -937,6 +937,39 @@ esp_err_t esp_ble_get_bond_device_list(int *dev_num, esp_ble_bond_dev_t *dev_lis
return (ret == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL);
}
esp_err_t esp_ble_gap_set_bond_device_except(esp_bd_addr_t bd_addr, bool except)
{
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
if (bd_addr == NULL) {
return ESP_ERR_INVALID_ARG;
}
#if (SMP_INCLUDED == TRUE)
return (btc_storage_set_bond_except((bt_bdaddr_t *)bd_addr, except) == BT_STATUS_SUCCESS)
? ESP_OK : ESP_FAIL;
#else
(void)except;
return ESP_FAIL;
#endif
}
esp_err_t esp_ble_gap_is_bond_device_excepted(esp_bd_addr_t bd_addr, bool *excepted)
{
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
if (bd_addr == NULL || excepted == NULL) {
return ESP_ERR_INVALID_ARG;
}
#if (SMP_INCLUDED == TRUE)
return (btc_storage_get_bond_except((bt_bdaddr_t *)bd_addr, excepted) == BT_STATUS_SUCCESS)
? ESP_OK : ESP_FAIL;
#else
return ESP_FAIL;
#endif
}
esp_err_t esp_ble_oob_req_reply(esp_bd_addr_t bd_addr, uint8_t *TK, uint8_t len)
{
if(len != ESP_BT_OCTET16_LEN) {

View File

@@ -3936,6 +3936,52 @@ int esp_ble_get_bond_device_num(void);
*/
esp_err_t esp_ble_get_bond_device_list(int *dev_num, esp_ble_bond_dev_t *dev_list);
/**
* @brief Protect or unprotect a bonded device from automatic eviction
* when the bond list is full.
*
* `bd_addr` must already be in the BLE bond list (the same
* address reported by `ESP_GAP_BLE_AUTH_CMPL_EVT` or
* `esp_ble_get_bond_device_list()`). Do not pass an unbound
* address: this API will not create a new bond section and
* returns `ESP_FAIL`.
*
* Excepted devices are never selected by the overflow cleanup
* in NVS (they stay bonded even if they are idle/disconnected).
* `esp_ble_remove_bond_device()` still removes them explicitly.
*
* The flag is stored on that device's NVS bond record and
* survives reboot. Keep the number of excepted devices
* strictly less than the configured max bond count, otherwise
* a new pairing may have no evictable candidate.
*
* @param[in] bd_addr : BD address of an already bonded peer
* @param[in] except : true to protect, false to clear the protection
*
* @return - ESP_OK : success
* - ESP_ERR_INVALID_ARG : bd_addr is NULL
* - ESP_FAIL : device is not in the BLE bond list, the excepted bond
* count would exceed (max bond count - 1), or storage write failed
*
*/
esp_err_t esp_ble_gap_set_bond_device_except(esp_bd_addr_t bd_addr, bool except);
/**
* @brief Query whether a device's bond is protected from overflow eviction.
*
* This reads a per-device flag on that bond, not a separate except list.
* If the address is not bonded, `*excepted` is set to false.
*
* @param[in] bd_addr : BD address of the peer
* @param[out] excepted : set to true only if this device is bonded and excepted
*
* @return - ESP_OK : success
* - ESP_ERR_INVALID_ARG : bd_addr or excepted is NULL
* - ESP_FAIL : Bluedroid is not enabled
*
*/
esp_err_t esp_ble_gap_is_bond_device_excepted(esp_bd_addr_t bd_addr, bool *excepted);
/**
* @brief This function is called to provide the OOB data for
* SMP in response to ESP_GAP_BLE_OOB_REQ_EVT

View File

@@ -12,6 +12,7 @@
#include "btc/btc_ble_storage.h"
#include "bta/bta_gatts_co.h"
#include "btc/btc_util.h"
#include "stack/btm_api.h"
#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE)
#include "stack/btm_ble_api.h"
#endif
@@ -21,12 +22,192 @@
//the maximum number of bonded devices
#define BONED_DEVICES_MAX_COUNT (BTM_SEC_MAX_BONDS)
static bool btc_storage_bond_is_excepted(const char *section)
{
int except = 0;
return btc_config_get_int(section, BTC_BLE_STORAGE_EXCEPT_STR, &except) && (except != 0);
}
#if (BLE_INCLUDED == TRUE)
/* Caller must hold btc_config_lock(). Count BLE bonds with ExceptBond set. */
static uint16_t btc_storage_count_excepted_ble_bonds(void)
{
uint16_t count = 0;
for (const btc_config_section_iter_t *iter = btc_config_section_begin();
iter != btc_config_section_end();
iter = btc_config_section_next(iter)) {
const char *section = btc_config_section_name(iter);
int device_type = 0;
if (!string_is_bdaddr(section)) {
continue;
}
if (!btc_config_get_int(section, BTC_BLE_STORAGE_DEV_TYPE_STR, &device_type) ||
!(device_type & BT_DEVICE_TYPE_BLE)) {
continue;
}
if (btc_storage_bond_is_excepted(section)) {
count++;
}
}
return count;
}
/* Caller must hold btc_config_lock(). Warn if NVS has too many excepted bonds. */
void btc_storage_check_excepted_bond_limit(void)
{
if (BONED_DEVICES_MAX_COUNT == 0) {
return;
}
uint16_t except_count = btc_storage_count_excepted_ble_bonds();
uint16_t except_max = (uint16_t)(BONED_DEVICES_MAX_COUNT - 1);
if (except_count > except_max) {
BTC_TRACE_WARNING("ExceptBond count %u exceeds limit %u (max bonds %u)",
except_count, except_max, BONED_DEVICES_MAX_COUNT);
}
}
#endif /* BLE_INCLUDED == TRUE */
static bool btc_storage_bdaddr_is_connected(BD_ADDR addr)
{
/* Use BTM ACL only. BTA_DmGetConnectionState() pulls bta_dm_find_peer_device
* from bta_dm_pm.c, which is not linked on BLE-only / no-PM builds. */
if (BTM_IsAclConnectionUp(addr, BT_TRANSPORT_LE)) {
return true;
}
if (BTM_IsAclConnectionUp(addr, BT_TRANSPORT_BR_EDR)) {
return true;
}
return false;
}
static bool btc_storage_bond_is_connected(const char *section)
{
bt_bdaddr_t bd_addr;
tBTM_LE_PID_KEYS pid;
size_t pid_len = sizeof(pid);
if (!string_to_bdaddr(section, &bd_addr)) {
return false;
}
if (btc_storage_bdaddr_is_connected(bd_addr.address)) {
return true;
}
/* Section may be keyed by RPA while the live ACL uses the identity. */
memset(&pid, 0, sizeof(pid));
if (btc_config_get_bin(section, BTC_BLE_STORAGE_LE_KEY_PID_STR, (uint8_t *)&pid, &pid_len) &&
pid_len == sizeof(pid) &&
memcmp(pid.static_addr, bd_addr.address, sizeof(bd_addr.address)) != 0) {
if (btc_storage_bdaddr_is_connected(pid.static_addr)) {
return true;
}
}
return false;
}
static uint16_t btc_storage_count_bdaddr_sections(void)
{
uint16_t count = 0;
for (const btc_config_section_iter_t *iter = btc_config_section_begin();
iter != btc_config_section_end();
iter = btc_config_section_next(iter)) {
if (string_is_bdaddr(btc_config_section_name(iter))) {
count++;
}
}
return count;
}
/* NVS sections are newest-first. Keep updating the match so the last one is oldest.
* The newest bdaddr section is never evicted (it is the bond just written / most recent). */
static const char *btc_storage_find_evict_candidate(bool connected_ok)
{
const char *candidate = NULL;
const char *newest = NULL;
for (const btc_config_section_iter_t *iter = btc_config_section_begin();
iter != btc_config_section_end();
iter = btc_config_section_next(iter)) {
const char *section = btc_config_section_name(iter);
if (!string_is_bdaddr(section)) {
continue;
}
if (newest == NULL) {
newest = section;
continue;
}
if (btc_storage_bond_is_excepted(section)) {
continue;
}
if (!connected_ok && btc_storage_bond_is_connected(section)) {
continue;
}
candidate = section;
}
return candidate;
}
static bool btc_storage_evict_bond_section(const char *section)
{
bt_bdaddr_t bd_addr;
bdstr_t section_copy;
if (!section || !string_to_bdaddr(section, &bd_addr)) {
return false;
}
/* Section name is freed by remove_section; copy before mutating the list. */
{
size_t name_len = strlen(section);
if (name_len >= sizeof(section_copy)) {
return false;
}
memcpy(section_copy, section, name_len + 1);
}
BTA_DmRemoveDevice(bd_addr.address, BT_TRANSPORT_LE);
BTA_DmRemoveDevice(bd_addr.address, BT_TRANSPORT_BR_EDR);
if (btc_config_remove_section(section_copy)) {
BTIF_TRACE_WARNING("Exceeded the maximum number of bonded devices. Deleting device info: %02x:%02x:%02x:%02x:%02x:%02x",
bd_addr.address[0], bd_addr.address[1], bd_addr.address[2],
bd_addr.address[3], bd_addr.address[4], bd_addr.address[5]);
return true;
}
return false;
}
void btc_storage_evict_overflow_bonded_devices(uint16_t max_keep)
{
uint16_t count = btc_storage_count_bdaddr_sections();
while (count > max_keep) {
const char *victim = btc_storage_find_evict_candidate(false);
if (!victim) {
/* No disconnected, non-excepted bond; fall back to the oldest connected one. */
victim = btc_storage_find_evict_candidate(true);
}
if (!victim) {
BTIF_TRACE_WARNING("Cannot evict bonded devices: remaining bonds are excepted (count=%u max=%u)",
count, max_keep);
break;
}
if (!btc_storage_evict_bond_section(victim)) {
break;
}
count--;
}
}
static void _btc_storage_save(void)
{
uint16_t addr_section_count = 0;
bt_bdaddr_t bd_addr;
const btc_config_section_iter_t *need_remove_iter = NULL;
const btc_config_section_iter_t *iter = btc_config_section_begin();
while (iter != btc_config_section_end()) {
@@ -55,33 +236,12 @@ static void _btc_storage_save(void)
continue;
}
if(addr_section_count == BONED_DEVICES_MAX_COUNT) {
need_remove_iter = iter;
}
addr_section_count ++;
iter = btc_config_section_next(iter);
}
/*exceeded the maximum number of bonded devices, delete them */
if (need_remove_iter) {
while(need_remove_iter != btc_config_section_end()) {
const char *need_remove_section = btc_config_section_name(need_remove_iter);
if (!string_is_bdaddr(need_remove_section)) {
need_remove_iter = btc_config_section_next(need_remove_iter);
continue;
}
need_remove_iter = btc_config_section_next(need_remove_iter);
//delete device info
string_to_bdaddr(need_remove_section, &bd_addr);
BTA_DmRemoveDevice(bd_addr.address, BT_TRANSPORT_LE);
BTA_DmRemoveDevice(bd_addr.address, BT_TRANSPORT_BR_EDR);
//delete config info
if (btc_config_remove_section(need_remove_section)) {
// The need_remove_section has been freed
BTIF_TRACE_WARNING("Exceeded the maximum number of bonded devices. Deleting the last device info: %02x:%02x:%02x:%02x:%02x:%02x",
bd_addr.address[0], bd_addr.address[1], bd_addr.address[2], bd_addr.address[3], bd_addr.address[4], bd_addr.address[5]);
}
}
}
/* Bond list is newest-first in NVS. Evict oldest disconnected (then oldest
* connected) non-excepted devices until the count fits. */
btc_storage_evict_overflow_bonded_devices(BONED_DEVICES_MAX_COUNT);
btc_config_flush();
}
@@ -282,6 +442,9 @@ static bt_status_t _btc_storage_remove_all_ble_keys(const char *name)
ret |= btc_config_remove(name, BTC_BLE_STORAGE_PSEUDO_BOND_STR);
}
#endif
if (btc_config_exist(name, BTC_BLE_STORAGE_EXCEPT_STR)) {
ret |= btc_config_remove(name, BTC_BLE_STORAGE_EXCEPT_STR);
}
return ret;
}
@@ -1172,6 +1335,87 @@ int btc_storage_get_num_ble_bond_devices(void)
return num_dev;
}
bt_status_t btc_storage_set_bond_except(bt_bdaddr_t *remote_bd_addr, bool except)
{
bdstr_t bdstr;
int device_type = 0;
bool ret;
if (remote_bd_addr == NULL) {
return BT_STATUS_FAIL;
}
bdaddr_to_string(remote_bd_addr, bdstr, sizeof(bdstr));
btc_config_lock();
/* Require an existing BLE bond section so we do not create an empty
* bdaddr section that would consume a bond slot. */
if (!btc_config_has_section(bdstr)) {
btc_config_unlock();
BTC_TRACE_WARNING("%s: %s is not bonded, cannot set except=%d", __func__, bdstr, except);
return BT_STATUS_FAIL;
}
if (!btc_config_get_int(bdstr, BTC_BLE_STORAGE_DEV_TYPE_STR, &device_type)) {
btc_config_unlock();
BTC_TRACE_WARNING("%s: %s has no bond record, cannot set except=%d", __func__, bdstr, except);
return BT_STATUS_FAIL;
}
if (!(device_type & BT_DEVICE_TYPE_BLE)) {
btc_config_unlock();
BTC_TRACE_WARNING("%s: %s is not a BLE bonded device (device_type=0x%x), cannot set except=%d",
__func__, bdstr, device_type, except);
return BT_STATUS_FAIL;
}
if (except) {
if (!btc_storage_bond_is_excepted(bdstr)) {
uint16_t except_count = btc_storage_count_excepted_ble_bonds();
uint16_t except_max = (BONED_DEVICES_MAX_COUNT > 0)
? (uint16_t)(BONED_DEVICES_MAX_COUNT - 1) : 0;
if (except_count >= except_max) {
btc_config_unlock();
BTC_TRACE_WARNING("%s: cannot set except for %s: excepted bond count %u reached limit %u (max bonds %u)",
__func__, bdstr, except_count, except_max, BONED_DEVICES_MAX_COUNT);
return BT_STATUS_FAIL;
}
}
ret = btc_config_set_int(bdstr, BTC_BLE_STORAGE_EXCEPT_STR, 1);
} else if (btc_config_exist(bdstr, BTC_BLE_STORAGE_EXCEPT_STR)) {
ret = btc_config_remove(bdstr, BTC_BLE_STORAGE_EXCEPT_STR);
} else {
ret = true;
}
if (ret) {
btc_config_flush();
} else {
BTC_TRACE_ERROR("%s: failed to %s ExceptBond for %s", __func__,
except ? "set" : "clear", bdstr);
}
btc_config_unlock();
return ret ? BT_STATUS_SUCCESS : BT_STATUS_FAIL;
}
bt_status_t btc_storage_get_bond_except(bt_bdaddr_t *remote_bd_addr, bool *except)
{
bdstr_t bdstr;
int except_val = 0;
if (remote_bd_addr == NULL || except == NULL) {
return BT_STATUS_FAIL;
}
bdaddr_to_string(remote_bd_addr, bdstr, sizeof(bdstr));
btc_config_lock();
*except = btc_config_get_int(bdstr, BTC_BLE_STORAGE_EXCEPT_STR, &except_val) && (except_val != 0);
btc_config_unlock();
return BT_STATUS_SUCCESS;
}
bt_status_t btc_storage_get_gatt_cl_supp_feat(bt_bdaddr_t *remote_bd_addr, uint8_t *value, int len)
{
bdstr_t bdstr;

View File

@@ -36,42 +36,8 @@ bt_status_t btc_storage_add_bonded_device(bt_bdaddr_t *remote_bd_addr,
BOOLEAN sc_support)
{
bdstr_t bdstr;
bt_bdaddr_t bd_addr;
bdaddr_to_string(remote_bd_addr, bdstr, sizeof(bdstr));
/* device not in bond list and exceed the maximum number of bonded devices, delete the inactive bonded device */
if (btc_storage_get_num_all_bond_devices() >= BTM_SEC_MAX_BONDS && !btc_config_has_section(bdstr)) {
const btc_config_section_iter_t *iter = btc_config_section_begin();
const btc_config_section_iter_t *remove_iter = NULL;
/* find the last bdaddr-formatted device section */
while (iter != btc_config_section_end()) {
const char *name = btc_config_section_name(iter);
if (name && string_is_bdaddr(name)) {
remove_iter = iter;
}
iter = btc_config_section_next(iter);
}
if (remove_iter != NULL) {
const char *remove_section = btc_config_section_name(remove_iter);
if (string_to_bdaddr(remove_section, &bd_addr)) {
// delete device info
BTA_DmRemoveDevice(bd_addr.address, BT_TRANSPORT_BR_EDR);
BTA_DmRemoveDevice(bd_addr.address, BT_TRANSPORT_LE);
// delete config info
if (btc_config_remove_section(remove_section)) {
BTC_TRACE_WARNING("exceeded the maximum number of bonded devices, delete the first device info : %02x:%02x:%02x:%02x:%02x:%02x",
bd_addr.address[0], bd_addr.address[1], bd_addr.address[2], bd_addr.address[3], bd_addr.address[4], bd_addr.address[5]);
}
} else {
BTC_TRACE_ERROR("Failed to convert section name to bdaddr: %s", remove_section);
}
} else {
BTC_TRACE_WARNING("No bdaddr-formatted section found to remove");
}
}
BTC_TRACE_DEBUG("add to storage: Remote device:%s\n", bdstr);
btc_config_lock();
@@ -79,6 +45,33 @@ bt_status_t btc_storage_add_bonded_device(bt_bdaddr_t *remote_bd_addr,
ret &= btc_config_set_int(bdstr, BTC_STORAGE_PIN_LENGTH_STR, (int)pin_length);
ret &= btc_config_set_bin(bdstr, BTC_STORAGE_LINK_KEY_STR, link_key, sizeof(LINK_KEY));
ret &= btc_config_set_bin(bdstr, BTC_STORAGE_SC_SUPPORT, (uint8_t *)&sc_support, sizeof(sc_support));
#if (SMP_INCLUDED == TRUE)
/* Evict after the write so the new device is newest and is not selected. */
btc_storage_evict_overflow_bonded_devices(BTM_SEC_MAX_BONDS);
#else
{
uint16_t count = 0;
const btc_config_section_iter_t *iter = btc_config_section_begin();
const btc_config_section_iter_t *remove_iter = NULL;
while (iter != btc_config_section_end()) {
const char *name = btc_config_section_name(iter);
if (name && string_is_bdaddr(name)) {
count++;
remove_iter = iter;
}
iter = btc_config_section_next(iter);
}
if (count > BTM_SEC_MAX_BONDS && remove_iter != NULL) {
const char *remove_section = btc_config_section_name(remove_iter);
bt_bdaddr_t bd_addr;
if (string_to_bdaddr(remove_section, &bd_addr)) {
BTA_DmRemoveDevice(bd_addr.address, BT_TRANSPORT_BR_EDR);
BTA_DmRemoveDevice(bd_addr.address, BT_TRANSPORT_LE);
btc_config_remove_section(remove_section);
}
}
}
#endif
/* write bonded info immediately */
btc_config_flush();
btc_config_unlock();
@@ -141,10 +134,13 @@ static bt_status_t btc_in_fetch_bonded_devices(int add)
{
bt_status_t status = BT_STATUS_FAIL;
uint16_t dev_cnt = 0;
const btc_config_section_iter_t *remove_iter = NULL;
bt_bdaddr_t bd_addr;
btc_config_lock();
/* Drop oldest non-excepted overflow first so excepted bonds are not trimmed. */
btc_storage_evict_overflow_bonded_devices(BTM_SEC_MAX_BONDS);
#if (BLE_INCLUDED == TRUE)
btc_storage_check_excepted_bond_limit();
#endif
for (const btc_config_section_iter_t *iter = btc_config_section_begin(); iter != btc_config_section_end(); iter = btc_config_section_next(iter)) {
const char *name = btc_config_section_name(iter);
if (!string_is_bdaddr(name)) {
@@ -165,25 +161,10 @@ static bt_status_t btc_in_fetch_bonded_devices(int add)
#endif ///BLE_INCLUDED == TRUE
}
} else {
/* delete the exceeded device info from nvs */
remove_iter = iter;
while (remove_iter != btc_config_section_end()) {
const char *remove_section = btc_config_section_name(remove_iter);
string_to_bdaddr(remove_section, &bd_addr);
if (!string_is_bdaddr(remove_section)) {
remove_iter = btc_config_section_next(remove_iter);
continue;
}
remove_iter = btc_config_section_next(remove_iter);
/* delete config info */
if (btc_config_remove_section(remove_section)) {
BTC_TRACE_WARNING("exceeded the maximum number of bonded devices, delete the exceed device info : %02x:%02x:%02x:%02x:%02x:%02x",
bd_addr.address[0], bd_addr.address[1], bd_addr.address[2], bd_addr.address[3], bd_addr.address[4], bd_addr.address[5]);
}
}
/* write into nvs */
btc_config_flush();
break;
/* Leftover overflow is excepted (eviction refuses to delete them).
* Keep the NVS entries but do not load extra BTM records. */
BTC_TRACE_WARNING("skip loading overflow bonded device %s (excepted or no evictable candidate)", name);
continue;
}
}
btc_config_unlock();

View File

@@ -38,6 +38,9 @@
* identity feature). Such sections legitimately share the peer Identity with
* another (local,peer) bond and must be exempt from identity-based de-dup. */
#define BTC_BLE_STORAGE_PSEUDO_BOND_STR "PseudoBond"
/* Marks a bond that must not be auto-evicted when the bond list is full.
* Explicit removal via esp_ble_remove_bond_device() is still allowed. */
#define BTC_BLE_STORAGE_EXCEPT_STR "ExceptBond"
#define BTC_BLE_STORAGE_LOCAL_ADAPTER_STR "Adapter"
#define BTC_BLE_STORAGE_LE_LOCAL_KEY_IR_STR "LE_LOCAL_KEY_IR"
@@ -98,6 +101,20 @@ void btc_storage_delete_duplicate_ble_devices(void);
void btc_storage_remove_unused_sections(uint8_t *cur_addr, tBTM_LE_PID_KEYS *del_pid_key);
/* Caller must hold btc_config_lock(). Evict bonds until bdaddr section count
* is <= max_keep. Excepted bonds are never evicted. Disconnected bonds are
* preferred over connected ones; NVS list order (oldest last) is the age key. */
void btc_storage_evict_overflow_bonded_devices(uint16_t max_keep);
#if (BLE_INCLUDED == TRUE)
/* Caller must hold btc_config_lock(). Check except count against bond limit. */
void btc_storage_check_excepted_bond_limit(void);
#endif /* BLE_INCLUDED == TRUE */
bt_status_t btc_storage_set_bond_except(bt_bdaddr_t *remote_bd_addr, bool except);
bt_status_t btc_storage_get_bond_except(bt_bdaddr_t *remote_bd_addr, bool *except);
#endif ///SMP_INCLUDED == TRUE
#define BTC_BLE_STORAGE_GATT_CL_SUPP_FEAT_STR "GATT_CL_SUPP_FEAT"