From 454c2501748cb9b446994f9f1a40690f4fca907f Mon Sep 17 00:00:00 2001 From: Abhinav Kudnar Date: Thu, 23 Jul 2026 23:06:45 +0530 Subject: [PATCH] feat(nimble): add LRU store overflow callback Prefer least-recently-used bond eviction over round-robin when storage is full; apps can register ble_store_util_status_lru. --- nimble/host/include/host/ble_store.h | 1 + nimble/host/src/ble_store_util.c | 163 +++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) diff --git a/nimble/host/include/host/ble_store.h b/nimble/host/include/host/ble_store.h index 83b1c4f15f..f64329aff4 100644 --- a/nimble/host/include/host/ble_store.h +++ b/nimble/host/include/host/ble_store.h @@ -743,6 +743,7 @@ int ble_store_util_count(int type, int *out_count); * Non-zero on error. */ int ble_store_util_status_rr(struct ble_store_status_event *event, void *arg); +int ble_store_util_status_lru(struct ble_store_status_event *event, void *arg); /** @} */ diff --git a/nimble/host/src/ble_store_util.c b/nimble/host/src/ble_store_util.c index 6dcbca25a1..efb6852500 100644 --- a/nimble/host/src/ble_store_util.c +++ b/nimble/host/src/ble_store_util.c @@ -211,3 +211,166 @@ ble_store_util_status_rr(struct ble_store_status_event *event, void *arg) return BLE_HS_EUNKNOWN; } } + +struct ble_store_util_lru_peer { + ble_addr_t peer_addr; + uint16_t bond_count; + int found; + const ble_addr_t *except; +}; + +/** + * Iterator callback that selects the least-recently-used bonded peer. + * + * Recency is determined by ble_store_value_sec.bond_count, which the config + * store increments on each persist of a security record. A lower bond_count + * means the peer was used less recently. + */ +static int +ble_store_util_iter_lru_peer(int obj_type, + union ble_store_value *val, + void *arg) +{ + struct ble_store_util_lru_peer *lru; + + BLE_HS_DBG_ASSERT(obj_type == BLE_STORE_OBJ_TYPE_OUR_SEC); + + lru = arg; + + if (lru->except != NULL && + ble_addr_cmp(lru->except, &val->sec.peer_addr) == 0) { + return 0; + } + + if (!lru->found || val->sec.bond_count < lru->bond_count) { + lru->peer_addr = val->sec.peer_addr; + lru->bond_count = val->sec.bond_count; + lru->found = 1; + } + + return 0; +} + +/** + * Finds the least-recently-used bonded peer. + * + * @param out_peer_addr On success, identity address of the LRU peer. + * @param except Optional peer to exclude from selection; + * may be NULL. + * + * @return 0 on success; + * BLE_HS_ENOENT if no suitable peer exists; + * Other nonzero on error. + */ +static int +ble_store_util_find_lru_peer(ble_addr_t *out_peer_addr, + const ble_addr_t *except) +{ + struct ble_store_util_lru_peer lru = { + .found = 0, + .except = except, + }; + int rc; + + rc = ble_store_iterate(BLE_STORE_OBJ_TYPE_OUR_SEC, + ble_store_util_iter_lru_peer, + &lru); + if (rc != 0) { + return rc; + } + + if (!lru.found) { + return BLE_HS_ENOENT; + } + + *out_peer_addr = lru.peer_addr; + return 0; +} + +/** + * Unpairs the least-recently-used bonded peer. + * + * @return 0 on success; + * Other nonzero on error. + */ +static int +ble_store_util_unpair_lru_peer(void) +{ + ble_addr_t peer_addr; + int rc; + + rc = ble_store_util_find_lru_peer(&peer_addr, NULL); + if (rc != 0) { + return rc; + } + + return ble_gap_unpair(&peer_addr); +} + +/** + * Unpairs the least-recently-used bonded peer, excluding the specified peer. + * + * @param except Peer address that must not be unpaired. + * + * @return 0 on success; + * Other nonzero on error. + */ +static int +ble_store_util_unpair_lru_except(const ble_addr_t *except) +{ + ble_addr_t peer_addr; + int rc; + + rc = ble_store_util_find_lru_peer(&peer_addr, except); + if (rc != 0) { + return rc; + } + + return ble_gap_unpair(&peer_addr); +} + +/** + * LRU status callback. If there is insufficient storage capacity for a new + * record, delete the least-recently-used bond and proceed with the persist + * operation. + * + * Recency is tracked via bond_count in persisted security records (updated on + * each write by the config store). Prefer this over ble_store_util_status_rr + * when recently used bonds should be retained. + * + * Register from the application with: + * ble_hs_cfg.store_status_cb = ble_store_util_status_lru; + */ +int +ble_store_util_status_lru(struct ble_store_status_event *event, void *arg) +{ + switch (event->event_code) { + case BLE_STORE_EVENT_OVERFLOW: + switch (event->overflow.obj_type) { + case BLE_STORE_OBJ_TYPE_OUR_SEC: + case BLE_STORE_OBJ_TYPE_PEER_SEC: + case BLE_STORE_OBJ_TYPE_PEER_ADDR: + return ble_store_util_unpair_lru_peer(); + case BLE_STORE_OBJ_TYPE_CCCD: + case BLE_STORE_OBJ_TYPE_CSFC: + /* Try unpairing LRU peer except current peer */ + return ble_store_util_unpair_lru_except( + &event->overflow.value->cccd.peer_addr); +#if MYNEWT_VAL(ENC_ADV_DATA) + case BLE_STORE_OBJ_TYPE_ENC_ADV_DATA: + return ble_store_util_delete_ead_oldest_peer(); +#endif + default: + return BLE_HS_EUNKNOWN; + } + + case BLE_STORE_EVENT_FULL: + /* Just proceed with the operation. If it results in an overflow, + * we'll delete a record when the overflow occurs. + */ + return 0; + + default: + return BLE_HS_EUNKNOWN; + } +}