diff --git a/nimble/host/include/host/ble_store.h b/nimble/host/include/host/ble_store.h index a6d8bdf8a..60debeef3 100644 --- a/nimble/host/include/host/ble_store.h +++ b/nimble/host/include/host/ble_store.h @@ -451,8 +451,71 @@ int ble_store_util_delete_all(int type, const union ble_store_key *key); int ble_store_util_delete_peer(const ble_addr_t *peer_id_addr); int ble_store_util_delete_oldest_peer(void); int ble_store_util_count(int type, int *out_count); + +/** + * @brief Round-robin status callback for handling store status events. + * + * This function handles store status events, particularly in cases where there + * is insufficient storage capacity for new records. + * It attempts to resolve overflow issues by deleting the oldest bond and + * proceeds with the persist operation. + * + * @note This behavior may not be suitable for production use as it may lead to + * removal of important bonds by less relevant peers. It is more useful for + * demonstration purposes and sample applications. + * + * @note Retained for backward compatibility. It follows the eviction policy + * selected at build time, so it removes the least recently used bond instead + * of the oldest one when the least-recently-used policy is enabled. New + * applications should use ble_store_util_status_overflow(). + * + * @param event A pointer to the store status event. + * @param arg A pointer to additional user-defined arguments. + * + * @return 0 on success; + * Non-zero on error. + */ int ble_store_util_status_rr(struct ble_store_status_event *event, void *arg); +#if MYNEWT_VAL(BLE_STORE_OVERFLOW_LFU) && MYNEWT_VAL(BLE_STORE_MAX_BONDS) +/** + * @brief Least-recently-used status callback for store overflow events. + * + * Removes the least recently used, disconnected bond when storage is full. + * The peer associated with the record being written is never selected. + * + * @param event A pointer to the store status event. + * @param arg A pointer to additional user-defined arguments. + * + * @return 0 on success; + * Non-zero on error. + */ +int ble_store_util_status_lfu(struct ble_store_status_event *event, void *arg); + +/** + * @brief Mark a bonded peer as recently used. + * + * Updates OUR_SEC recency so least-recently-used overflow eviction preserves + * this bond. Safe to call when no bond exists yet (returns 0). + */ +int ble_store_util_touch_peer(const ble_addr_t *peer_id_addr); +#endif + +/** + * @brief Configured status callback for handling store status events. + * + * Dispatches to the bond eviction policy selected at build time: round-robin + * by default, or least recently used when BLE_STORE_OVERFLOW_LFU is enabled. + * + * @param event A pointer to the store status event. + * @param arg A pointer to additional user-defined arguments. + * + * @return 0 on success; + * Non-zero on error. + */ +int ble_store_util_status_overflow(struct ble_store_status_event *event, + void *arg); + #ifdef __cplusplus } #endif diff --git a/nimble/host/src/ble_hs_resolv_priv.h b/nimble/host/src/ble_hs_resolv_priv.h index 0afd027f2..a3a89f901 100644 --- a/nimble/host/src/ble_hs_resolv_priv.h +++ b/nimble/host/src/ble_hs_resolv_priv.h @@ -41,6 +41,12 @@ struct ble_hs_resolv_entry { #if MYNEWT_VAL(BLE_STORE_CONFIG_PERSIST) /* Persist peer records in NVS. XXX Need to handle this in `store` module */ int ble_store_persist_peer_records(void); +#else +static inline int +ble_store_persist_peer_records(void) +{ + return 0; +} #endif struct ble_hs_peer_sec { diff --git a/nimble/host/src/ble_sm.c b/nimble/host/src/ble_sm.c index 5480f8e75..7b24a8074 100644 --- a/nimble/host/src/ble_sm.c +++ b/nimble/host/src/ble_sm.c @@ -49,7 +49,6 @@ #include "host/ble_store.h" #include "ble_hs_priv.h" #include "ble_hs_resolv_priv.h" -#include "../store/config/src/ble_store_config_priv.h" #include "esp_nimble_mem.h" #include "host/ble_hs_log.h" @@ -1449,6 +1448,10 @@ ble_sm_enc_event_rx(uint16_t conn_handle, uint8_t evt_status, int encrypted) struct ble_sm_result res; struct ble_sm_proc *proc; bool terminate_conn; +#if MYNEWT_VAL(BLE_STORE_OVERFLOW_LFU) && MYNEWT_VAL(BLE_STORE_MAX_BONDS) + struct ble_gap_conn_desc desc; + int rc; +#endif int authenticated; int bonded; int key_size; @@ -1560,6 +1563,16 @@ ble_sm_enc_event_rx(uint16_t conn_handle, uint8_t evt_status, int encrypted) ble_gap_terminate(conn_handle, BLE_ERR_AUTH_FAIL); } +#if MYNEWT_VAL(BLE_STORE_OVERFLOW_LFU) && MYNEWT_VAL(BLE_STORE_MAX_BONDS) + if (evt_status == 0 && encrypted && + ble_gap_conn_find(conn_handle, &desc) == 0) { + rc = ble_store_util_touch_peer(&desc.peer_id_addr); + if (rc != 0) { + BLE_HS_LOG(WARN, "failed to update bond recency; rc=%d\n", rc); + } + } +#endif + res.bonded = bonded; ble_sm_process_result(conn_handle, &res, true); } diff --git a/nimble/host/src/ble_store_util.c b/nimble/host/src/ble_store_util.c index 01624dea6..f22c42993 100644 --- a/nimble/host/src/ble_store_util.c +++ b/nimble/host/src/ble_store_util.c @@ -452,6 +452,7 @@ ble_store_util_delete_ead_oldest_peer(void) } #endif +#if !(MYNEWT_VAL(BLE_STORE_OVERFLOW_LFU) && MYNEWT_VAL(BLE_STORE_MAX_BONDS)) /** * Round-robin status callback. If a there is insufficient storage capacity * for a new record, delete the oldest bond and proceed with the persist @@ -461,8 +462,8 @@ ble_store_util_delete_ead_oldest_peer(void) * uninteresting peers could cause important bonds to be deleted. This is * useful for demonstrations and sample apps. */ -int -ble_store_util_status_rr(struct ble_store_status_event *event, void *arg) +static int +ble_store_util_status_rr_evict(struct ble_store_status_event *event, void *arg) { switch (event->event_code) { case BLE_STORE_EVENT_OVERFLOW: @@ -496,3 +497,181 @@ ble_store_util_status_rr(struct ble_store_status_event *event, void *arg) return BLE_HS_EUNKNOWN; } } +#endif + +#if MYNEWT_VAL(BLE_STORE_OVERFLOW_LFU) && MYNEWT_VAL(BLE_STORE_MAX_BONDS) +struct ble_store_util_lfu { + const ble_addr_t *except_addr; + ble_addr_t peer_addr; + uint16_t bond_count; + bool found; +}; + +struct ble_store_util_conn_check { + const ble_addr_t *peer_id_addr; + bool found; +}; + +static int +ble_store_util_conn_check(struct ble_hs_conn *conn, void *arg) +{ + struct ble_store_util_conn_check *check; + struct ble_hs_conn_addrs addrs; + + check = arg; + ble_hs_conn_addrs(conn, &addrs); + if (ble_addr_cmp(&addrs.peer_id_addr, check->peer_id_addr) == 0) { + check->found = true; + return 1; + } + + return 0; +} + +static bool +ble_store_util_peer_connected(const ble_addr_t *peer_id_addr) +{ + struct ble_store_util_conn_check check = { + .peer_id_addr = peer_id_addr, + }; + + ble_hs_lock(); + ble_hs_conn_foreach(ble_store_util_conn_check, &check); + ble_hs_unlock(); + + return check.found; +} + +static int +ble_store_util_find_lfu(int obj_type, union ble_store_value *val, void *arg) +{ + struct ble_store_util_lfu *lfu; + + BLE_HS_DBG_ASSERT(obj_type == BLE_STORE_OBJ_TYPE_OUR_SEC); + + lfu = arg; + if (lfu->except_addr != NULL && + ble_addr_cmp(&val->sec.peer_addr, lfu->except_addr) == 0) { + return 0; + } + + /* Never remove a bond which belongs to an active connection. */ + if (ble_store_util_peer_connected(&val->sec.peer_addr)) { + return 0; + } + + if (!lfu->found || val->sec.bond_count < lfu->bond_count) { + lfu->peer_addr = val->sec.peer_addr; + lfu->bond_count = val->sec.bond_count; + lfu->found = true; + } + + return 0; +} + +static int +ble_store_util_unpair_lfu(const ble_addr_t *except_addr) +{ + struct ble_store_util_lfu lfu = { + .except_addr = except_addr, + }; + int rc; + + rc = ble_store_iterate(BLE_STORE_OBJ_TYPE_OUR_SEC, + ble_store_util_find_lfu, &lfu); + if (rc != 0) { + return rc; + } + + if (!lfu.found) { + return BLE_HS_ESTORE_CAP; + } + + return ble_gap_unpair(&lfu.peer_addr); +} + +int +ble_store_util_status_lfu(struct ble_store_status_event *event, void *arg) +{ + const ble_addr_t *except_addr; + + switch (event->event_code) { + case BLE_STORE_EVENT_OVERFLOW: + except_addr = NULL; + switch (event->overflow.obj_type) { + case BLE_STORE_OBJ_TYPE_OUR_SEC: + case BLE_STORE_OBJ_TYPE_PEER_SEC: + except_addr = &event->overflow.value->sec.peer_addr; + break; + case BLE_STORE_OBJ_TYPE_PEER_ADDR: + except_addr = &event->overflow.value->rpa_rec.peer_addr; + break; + case BLE_STORE_OBJ_TYPE_CCCD: + except_addr = &event->overflow.value->cccd.peer_addr; + break; + case BLE_STORE_OBJ_TYPE_CSFC: + except_addr = &event->overflow.value->csfc.peer_addr; + break; +#if MYNEWT_VAL(ENC_ADV_DATA) + case BLE_STORE_OBJ_TYPE_ENC_ADV_DATA: + return ble_store_util_delete_ead_oldest_peer(); +#endif + default: + BLE_HS_LOG(ERROR, "%s rc=%d\n", __func__, BLE_HS_EUNKNOWN); + return BLE_HS_EUNKNOWN; + } + + return ble_store_util_unpair_lfu(except_addr); + + case BLE_STORE_EVENT_FULL: + return 0; + + default: + BLE_HS_LOG(ERROR, "%s rc=%d\n", __func__, BLE_HS_EUNKNOWN); + return BLE_HS_EUNKNOWN; + } +} +#endif + +#if MYNEWT_VAL(BLE_STORE_OVERFLOW_LFU) && MYNEWT_VAL(BLE_STORE_MAX_BONDS) +int +ble_store_util_touch_peer(const ble_addr_t *peer_id_addr) +{ + struct ble_store_key_sec key_sec = {0}; + struct ble_store_value_sec value_sec; + int rc; + + if (peer_id_addr == NULL || + ble_addr_cmp(peer_id_addr, BLE_ADDR_ANY) == 0) { + return BLE_HS_EINVAL; + } + + key_sec.peer_addr = *peer_id_addr; + rc = ble_store_read_our_sec(&key_sec, &value_sec); + if (rc == BLE_HS_ENOENT) { + /* Encryption can complete before bond keys are persisted. */ + return 0; + } + if (rc != 0) { + return rc; + } + + return ble_store_write_our_sec(&value_sec); +} +#endif + +int +ble_store_util_status_overflow(struct ble_store_status_event *event, void *arg) +{ +#if MYNEWT_VAL(BLE_STORE_OVERFLOW_LFU) && MYNEWT_VAL(BLE_STORE_MAX_BONDS) + return ble_store_util_status_lfu(event, arg); +#else + return ble_store_util_status_rr_evict(event, arg); +#endif +} + +int +ble_store_util_status_rr(struct ble_store_status_event *event, void *arg) +{ + return ble_store_util_status_overflow(event, arg); +} diff --git a/nimble/host/store/config/src/ble_store_config.c b/nimble/host/store/config/src/ble_store_config.c index 019295342..d3b9f62ce 100644 --- a/nimble/host/store/config/src/ble_store_config.c +++ b/nimble/host/store/config/src/ble_store_config.c @@ -19,6 +19,7 @@ #include #include +#include #include #include "sysinit/sysinit.h" @@ -284,6 +285,10 @@ ble_store_config_write_our_sec(const struct ble_store_value_sec *value_sec) { #if MYNEWT_VAL(BLE_STORE_MAX_BONDS) struct ble_store_key_sec key_sec; +#if MYNEWT_VAL(BLE_STORE_OVERFLOW_LFU) + struct ble_store_value_sec persisted_sec; + int updated_existing; +#endif int idx; int rc; @@ -293,6 +298,9 @@ ble_store_config_write_our_sec(const struct ble_store_value_sec *value_sec) ble_store_key_from_value_sec(&key_sec, value_sec); idx = ble_store_config_find_sec(&key_sec, ble_store_config_our_secs, ble_store_config_num_our_secs); +#if MYNEWT_VAL(BLE_STORE_OVERFLOW_LFU) + updated_existing = idx != -1; +#endif if (idx == -1) { if (ble_store_config_num_our_secs >= MYNEWT_VAL(BLE_STORE_MAX_BONDS)) { BLE_HS_LOG(DEBUG, "error persisting our sec; too many entries " @@ -308,12 +316,25 @@ ble_store_config_write_our_sec(const struct ble_store_value_sec *value_sec) ble_store_config_our_secs[idx] = *value_sec; ble_store_config_our_secs[idx].bond_count = ++ble_store_config_our_bond_count; +#if MYNEWT_VAL(BLE_STORE_OVERFLOW_LFU) + persisted_sec = ble_store_config_our_secs[idx]; +#endif /* Ensure entries are sorted at all times */ qsort(ble_store_config_our_secs, ble_store_config_num_our_secs, sizeof(struct ble_store_value_sec), ble_store_config_compare_bond_count); - rc = ble_store_config_persist_our_secs(); +#if MYNEWT_VAL(BLE_STORE_OVERFLOW_LFU) + if (updated_existing) { + rc = ble_store_config_persist_our_sec_value(&persisted_sec); + if (rc == BLE_HS_ENOENT) { + rc = ble_store_config_persist_our_secs(); + } + } else +#endif + { + rc = ble_store_config_persist_our_secs(); + } if (rc != 0) { return rc; } diff --git a/nimble/host/store/config/src/ble_store_config_conf.c b/nimble/host/store/config/src/ble_store_config_conf.c index 3fa2b6d0c..5856d149d 100644 --- a/nimble/host/store/config/src/ble_store_config_conf.c +++ b/nimble/host/store/config/src/ble_store_config_conf.c @@ -294,6 +294,16 @@ ble_store_config_persist_our_secs(void) return 0; } +#if MYNEWT_VAL(BLE_STORE_OVERFLOW_LFU) && MYNEWT_VAL(BLE_STORE_MAX_BONDS) +int +ble_store_config_persist_our_sec_value( + const struct ble_store_value_sec *value_sec) +{ + (void)value_sec; + return ble_store_config_persist_our_secs(); +} +#endif + int ble_store_config_persist_peer_secs(void) { diff --git a/nimble/host/store/config/src/ble_store_config_priv.h b/nimble/host/store/config/src/ble_store_config_priv.h index 941bdb937..2f15c31c0 100644 --- a/nimble/host/store/config/src/ble_store_config_priv.h +++ b/nimble/host/store/config/src/ble_store_config_priv.h @@ -151,6 +151,10 @@ extern int ble_store_config_num_local_irks; #if MYNEWT_VAL(BLE_STORE_MAX_BONDS) int ble_store_config_persist_our_secs(void); int ble_store_config_persist_peer_secs(void); +#if MYNEWT_VAL(BLE_STORE_OVERFLOW_LFU) +int ble_store_config_persist_our_sec_value( + const struct ble_store_value_sec *value_sec); +#endif #endif #if MYNEWT_VAL(BLE_STORE_MAX_CCCDS) int ble_store_config_persist_cccds(void); @@ -175,6 +179,15 @@ void ble_store_config_deinit(void); static inline int ble_store_config_persist_our_secs(void) { return 0; } static inline int ble_store_config_persist_peer_secs(void) { return 0; } +#if MYNEWT_VAL(BLE_STORE_OVERFLOW_LFU) +static inline int +ble_store_config_persist_our_sec_value( + const struct ble_store_value_sec *value_sec) +{ + (void)value_sec; + return 0; +} +#endif static inline int ble_store_config_persist_cccds(void) { return 0; } static inline int ble_store_config_persist_csfcs(void) { return 0; } #if MYNEWT_VAL(ENC_ADV_DATA) @@ -184,9 +197,6 @@ static inline int ble_store_config_persist_rpa_recs(void) { return 0; } static inline int ble_store_config_persist_local_irk(void) { return 0; } static inline void ble_store_config_conf_init(void) { } //static inline void ble_store_config_deinit(void) { } -#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY) -static inline int ble_store_persist_peer_records(void) { return 0; } -#endif #endif /* MYNEWT_VAL(BLE_STORE_CONFIG_PERSIST) */ #ifdef __cplusplus diff --git a/nimble/host/store/config/src/ble_store_nvs.c b/nimble/host/store/config/src/ble_store_nvs.c index 5eb688cca..3e9f47fc7 100644 --- a/nimble/host/store/config/src/ble_store_nvs.c +++ b/nimble/host/store/config/src/ble_store_nvs.c @@ -25,6 +25,7 @@ #if MYNEWT_VAL(BLE_STORE_CONFIG_PERSIST) +#include #include #include #include "sysinit/sysinit.h" @@ -1489,6 +1490,40 @@ end: return rc; } +#if MYNEWT_VAL(BLE_STORE_OVERFLOW_LFU) && MYNEWT_VAL(BLE_STORE_MAX_BONDS) +int +ble_store_config_persist_our_sec_value( + const struct ble_store_value_sec *value_sec) +{ + union ble_store_value val; + nvs_handle_t nimble_handle; + int nvs_idx; + int rc; + + rc = nvs_open(NIMBLE_NVS_NAMESPACE, NVS_READWRITE, &nimble_handle); + if (rc != ESP_OK) { + ESP_LOGE(TAG, "NVS open operation failed"); + return BLE_HS_ESTORE_FAIL; + } + + nvs_idx = get_nvs_sec_identity_index(nimble_handle, value_sec, + BLE_STORE_OBJ_TYPE_OUR_SEC); + if (nvs_idx == -1) { + nvs_close(nimble_handle); + return BLE_HS_ENOENT; + } + + val.sec = *value_sec; + rc = ble_store_nvs_update(nimble_handle, BLE_STORE_OBJ_TYPE_OUR_SEC, + nvs_idx, &val); + if (rc == 0) { + rc = ble_nvs_commit_checked(nimble_handle); + } + nvs_close(nimble_handle); + return rc; +} +#endif + int ble_store_config_persist_our_secs(void) { int nvs_count, nvs_idx;