From 0acecea98a521ec30ad676e8b6121dec9bed7918 Mon Sep 17 00:00:00 2001 From: Piotr Narajowski Date: Wed, 23 Jul 2025 13:20:03 +0200 Subject: [PATCH 1/6] nimble/host: implement robust gatt caching (server side) Add support for Robust GATT Caching. --- nimble/host/include/host/ble_att.h | 3 + nimble/host/include/host/ble_gatt.h | 10 + nimble/host/include/host/ble_store.h | 102 ++++++ .../gatt/include/services/gatt/ble_svc_gatt.h | 1 + nimble/host/services/gatt/src/ble_svc_gatt.c | 66 +++- nimble/host/src/ble_att_priv.h | 6 + nimble/host/src/ble_att_svr.c | 106 ++++++- nimble/host/src/ble_gatt_priv.h | 18 ++ nimble/host/src/ble_gattc.c | 37 +++ nimble/host/src/ble_gatts.c | 300 +++++++++++++++++- nimble/host/src/ble_store.c | 57 ++++ nimble/host/src/ble_store_util.c | 9 + .../host/store/config/src/ble_store_config.c | 122 +++++++ .../store/config/src/ble_store_config_priv.h | 9 + nimble/host/store/ram/src/ble_store_ram.c | 122 +++++++ nimble/host/syscfg.yml | 4 + 16 files changed, 963 insertions(+), 9 deletions(-) diff --git a/nimble/host/include/host/ble_att.h b/nimble/host/include/host/ble_att.h index 8323c9d764..eae50be300 100644 --- a/nimble/host/include/host/ble_att.h +++ b/nimble/host/include/host/ble_att.h @@ -110,6 +110,9 @@ struct os_mbuf; /**Insufficient Resources to complete the request. */ #define BLE_ATT_ERR_INSUFFICIENT_RES 0x11 +/** The server requests the client to rediscover the database. */ +#define BLE_ATT_ERR_DB_OUT_OF_SYNC 0x12 + /**Requested value is not allowed. */ #define BLE_ATT_ERR_VALUE_NOT_ALLOWED 0x13 diff --git a/nimble/host/include/host/ble_gatt.h b/nimble/host/include/host/ble_gatt.h index a8dafeba60..0ba3d26e09 100644 --- a/nimble/host/include/host/ble_gatt.h +++ b/nimble/host/include/host/ble_gatt.h @@ -1253,6 +1253,16 @@ int ble_gatts_peer_cl_sup_feat_get(uint16_t conn_handle, uint8_t *out_supported_ int ble_gatts_read_cccd(uint16_t conn_handle, uint16_t chr_val_handle, uint8_t *cccd_value); +/** + * Calculates Database Hash characteristic value based on service definitions + * in the GATT database. + * + * @return 0 on success; + * BLE_HS_EUNKNOWN if initializing CMAC session + * or computing CMAC tag value fails. + * + */ +int ble_gatts_calculate_db_hash(void); #ifdef __cplusplus } #endif diff --git a/nimble/host/include/host/ble_store.h b/nimble/host/include/host/ble_store.h index 83b1c4f15f..a57bebb5e1 100644 --- a/nimble/host/include/host/ble_store.h +++ b/nimble/host/include/host/ble_store.h @@ -48,6 +48,9 @@ extern "C" { /** Object type: Client Characteristic Configuration Descriptor. */ #define BLE_STORE_OBJ_TYPE_CCCD 3 +/** Object type: Peer last known database hash. */ +#define BLE_STORE_OBJ_TYPE_DB_HASH 4 + /** @} */ /** @@ -154,6 +157,34 @@ struct ble_store_value_cccd { unsigned value_changed:1; }; +/** + * Used as key for lookups of stored database hash. + * This struct corresponds to the BLE_STORE_OBJ_TYPE_DB_HASH store object + * type. + */ +struct ble_store_key_db_hash { + /** + * Key by peer identity address; + * peer_addr=BLE_ADDR_NONE means don't key off peer. + */ + ble_addr_t peer_addr; + + /** Number of results to skip; 0 means retrieve the first match. */ + uint8_t idx; +}; + +/** + * Represents a stored database hash of a peer. This struct + * corresponds to the BLE_STORE_OBJ_TYPE_DB_HASH store object type. + */ +struct ble_store_value_db_hash { + /** The peer address associated with the stored database hash. */ + ble_addr_t peer_addr; + /** Database hash; can be used to determine whether the peer + * is change aware/unaware. */ + const uint8_t *db_hash; + }; + /** * Used as a key for store lookups. This union must be accompanied by an * object type code to indicate which field is valid. @@ -163,6 +194,8 @@ union ble_store_key { struct ble_store_key_sec sec; /** Key for Client Characteristic Configuration Descriptor store lookups. */ struct ble_store_key_cccd cccd; + /** Key for database hash store lookups */ + struct ble_store_key_db_hash db_hash; }; /** @@ -174,6 +207,8 @@ union ble_store_value { struct ble_store_value_sec sec; /** Stored Client Characteristic Configuration Descriptor. */ struct ble_store_value_cccd cccd; + /** Stored database hash. */ + struct ble_store_value_db_hash db_hash; }; /** Represents an event associated with the BLE Store. */ @@ -556,6 +591,73 @@ int ble_store_write_cccd(const struct ble_store_value_cccd *value); */ int ble_store_delete_cccd(const struct ble_store_key_cccd *key); +/** + * @brief Writes a database hash to a storage. + * + * This function writes database hash value to a storage based on the + * provided value. + * + * @param value A pointer to a `ble_store_value_db_hash` + * structure representing the database hash + * to be written to a storage. + * + * @return 0 if the database hash was successfully + * written to a storage; + * Non-zero on error. + */ +int ble_store_write_db_hash(const struct ble_store_value_db_hash *value); + +/** + * @brief Reads a database hash from a storage. + * + * This function reads a database hash from a storage based on the provided key + * and stores the retrieved value in the specified output structure. + * + * @param key A pointer to a `ble_store_key_db_hash` + * structure representing the key to identify + * the database hash to be read. + * @param out_value A pointer to a `ble_store_value_db_hash` + * structure to store the database hash value + * read from a storage. + * + * @return 0 if the database hash was successfully read + * and stored in the `out_value` structure; + * Non-zero on error. + */ +int ble_store_read_db_hash(const struct ble_store_key_db_hash *key, + struct ble_store_value_db_hash *out_value); + +/** + * @brief Deletes a database hash from a storage. + * + * This function deletes a database hash from a storage based on the provided + * key. + * + * @param key A pointer to a `ble_store_key_db_hash` + * structure identifying the database hash to + * be deleted from a storage. + * + * @return 0 if the database hash was successfully deleted + * from a storage; + * Non-zero on error. + */ +int ble_store_delete_db_hash(const struct ble_store_value_db_hash *key); + +/** + * @brief Generates a storage key for a database hash entry from its value. + * + * This function generates a storage key for a database hash entry based on + * the provided database hash value. + * + * @param out_key A pointer to a `ble_store_key_db_hash` + * structure where the generated key will be + * stored. + * @param value A pointer to a `ble_store_value_db_hash` + * structure containing the security material + * value from which the key will be generated. + */ +void ble_store_key_from_value_db_hash(struct ble_store_key_db_hash *out_key, + const struct ble_store_value_db_hash *value); /** * @brief Generates a storage key for a security material entry from its value. diff --git a/nimble/host/services/gatt/include/services/gatt/ble_svc_gatt.h b/nimble/host/services/gatt/include/services/gatt/ble_svc_gatt.h index eefd412f57..f293e8a246 100644 --- a/nimble/host/services/gatt/include/services/gatt/ble_svc_gatt.h +++ b/nimble/host/services/gatt/include/services/gatt/ble_svc_gatt.h @@ -31,6 +31,7 @@ struct ble_hs_cfg; #define BLE_SVC_GATT_CHR_SERVICE_CHANGED_UUID16 0x2a05 #define BLE_SVC_GATT_CHR_SERVER_SUPPORTED_FEAT_UUID16 0x2b3a #define BLE_SVC_GATT_CHR_CLIENT_SUPPORTED_FEAT_UUID16 0x2b29 +#define BLE_SVC_GATT_CHR_DATABASE_HASH_UUID16 0x2b2a uint8_t ble_svc_gatt_get_local_cl_supported_feat(void); void ble_svc_gatt_changed(uint16_t start_handle, uint16_t end_handle); diff --git a/nimble/host/services/gatt/src/ble_svc_gatt.c b/nimble/host/services/gatt/src/ble_svc_gatt.c index 331d6bedd8..91d3f8f796 100644 --- a/nimble/host/services/gatt/src/ble_svc_gatt.c +++ b/nimble/host/services/gatt/src/ble_svc_gatt.c @@ -21,6 +21,7 @@ #include "sysinit/sysinit.h" #include "host/ble_hs.h" +#include "../src/ble_hs_priv.h" #include "services/gatt/ble_svc_gatt.h" #include "../src/ble_gatt_priv.h" @@ -32,13 +33,12 @@ static uint16_t ble_svc_gatt_end_handle; #define BLE_SVC_GATT_SRV_SUP_FEAT_EATT_BIT (0x00) /* Client supported features */ -#define BLE_SVC_GATT_CLI_SUP_FEAT_ROBUST_CATCHING_BIT (0x00) +#define BLE_SVC_GATT_CLI_SUP_FEAT_ROBUST_CACHING_BIT (0x00) #define BLE_SVC_GATT_CLI_SUP_FEAT_EATT_BIT (0x01) #define BLE_SVC_GATT_CLI_SUP_FEAT_MULT_NTF_BIT (0x02) static uint8_t ble_svc_gatt_local_srv_sup_feat = 0; static uint8_t ble_svc_gatt_local_cl_sup_feat = 0; - static int ble_svc_gatt_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg); @@ -51,6 +51,9 @@ static int ble_svc_gatt_cl_sup_feat_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg); +static int ble_svc_gatt_db_hash_access(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg); + static const struct ble_gatt_svc_def ble_svc_gatt_defs[] = { { /*** Service: GATT */ @@ -73,6 +76,13 @@ static const struct ble_gatt_svc_def ble_svc_gatt_defs[] = { .access_cb = ble_svc_gatt_cl_sup_feat_access, .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE, }, +#if MYNEWT_VAL(BLE_ATT_SVR_ROBUST_CACHE) + { + .uuid = BLE_UUID16_DECLARE(BLE_SVC_GATT_CHR_DATABASE_HASH_UUID16), + .access_cb = ble_svc_gatt_db_hash_access, + .flags = BLE_GATT_CHR_F_READ, + }, +#endif { 0, /* No more characteristics in this service. */ } @@ -122,6 +132,43 @@ ble_svc_gatt_cl_sup_feat_access(uint16_t conn_handle, uint16_t attr_handle, return 0; } +static int +ble_svc_gatt_db_hash_access(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + + struct ble_gap_sec_state sec_state; + const uint8_t *hash; + uint8_t local_hash[BLE_GATT_DB_HASH_SZ]; + int rc; + int i; + + + if (ctxt->op != BLE_GATT_ACCESS_OP_READ_CHR) { + return BLE_ATT_ERR_WRITE_NOT_PERMITTED; + } + + hash = ble_gatts_get_db_hash(); + + for (i = 0; i < BLE_GATT_DB_HASH_SZ; i++) { + local_hash[i] = hash[i]; + } + + rc = os_mbuf_append(ctxt->om, local_hash, sizeof(local_hash)); + + if (rc == 0) { + /* The peer becomes change-aware once it has read the hash and + * subsequently sends an ATT request */ + set_change_aware(conn_handle, BLE_GATTS_HASH_READ_PENDING); + ble_att_svr_get_sec_state(conn_handle, &sec_state); + if (sec_state.bonded) { + db_hash_store(conn_handle); + } + } + + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; +} + static int ble_svc_gatt_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) @@ -164,9 +211,19 @@ ble_svc_gatt_get_local_cl_supported_feat(void) void ble_svc_gatt_changed(uint16_t start_handle, uint16_t end_handle) { + int rc; + ble_svc_gatt_start_handle = start_handle; ble_svc_gatt_end_handle = end_handle; + ble_hs_lock(); + rc = ble_gatts_calculate_db_hash(); + if (rc != 0) { + ble_hs_unlock(); + return; + } + ble_hs_unlock(); ble_gatts_chr_updated(ble_svc_gatt_changed_val_handle); + set_all_change_aware_action(set_change_unaware, NULL); } void @@ -194,4 +251,9 @@ ble_svc_gatt_init(void) if (MYNEWT_VAL(BLE_ATT_SVR_NOTIFY_MULTI) > 0) { ble_svc_gatt_local_cl_sup_feat |= (1 << BLE_SVC_GATT_CLI_SUP_FEAT_MULT_NTF_BIT); } + + if (MYNEWT_VAL(BLE_ATT_SVR_ROBUST_CACHE) > 0) { + ble_svc_gatt_local_cl_sup_feat |= + (1 << BLE_SVC_GATT_CLI_SUP_FEAT_ROBUST_CACHING_BIT); + } } diff --git a/nimble/host/src/ble_att_priv.h b/nimble/host/src/ble_att_priv.h index 2bc3da361b..e2e7f79a13 100644 --- a/nimble/host/src/ble_att_priv.h +++ b/nimble/host/src/ble_att_priv.h @@ -47,6 +47,7 @@ struct ble_att_prep_write_cmd; struct ble_att_exec_write_req; struct ble_att_notify_req; struct ble_att_indicate_req; +struct ble_gap_sec_state; STATS_SECT_START(ble_att_stats) STATS_SECT_ENTRY(error_rsp_rx) @@ -173,6 +174,8 @@ uint16_t ble_att_mtu_by_cid(uint16_t conn_handle, uint16_t cid); int ble_att_init(void); /*** @svr */ +/** Type definition for ATT svr entry interation callback. */ +typedef int (*ble_gatts_entry_foreach)(struct ble_att_svr_entry *entry, void *arg); int ble_att_svr_start(void); @@ -217,6 +220,7 @@ void ble_att_svr_prep_clear(struct ble_att_prep_entry_list *prep_list); int ble_att_svr_read_handle(uint16_t conn_handle, uint16_t attr_handle, uint16_t offset, struct os_mbuf *om, uint8_t *out_att_err); +void ble_att_svr_foreach(ble_gatts_entry_foreach cb, void *arg); void ble_att_svr_reset(void); int ble_att_svr_init(void); @@ -226,6 +230,8 @@ void ble_att_svr_restore_range(uint16_t start_handle, uint16_t end_handle); int ble_att_svr_tx_error_rsp(uint16_t conn_handle, uint16_t cid, struct os_mbuf *txom, uint8_t req_op, uint16_t handle, uint8_t error_code); +void ble_att_svr_get_sec_state(uint16_t conn_handle, + struct ble_gap_sec_state *out_sec_state); /*** $clt */ /** An information-data entry in a find information response. */ diff --git a/nimble/host/src/ble_att_svr.c b/nimble/host/src/ble_att_svr.c index 8fdf0e8457..9a7795eb7d 100644 --- a/nimble/host/src/ble_att_svr.c +++ b/nimble/host/src/ble_att_svr.c @@ -209,6 +209,21 @@ ble_att_svr_find_by_uuid(struct ble_att_svr_entry *prev, const ble_uuid_t *uuid, return NULL; } +void +ble_att_svr_foreach(ble_gatts_entry_foreach cb, void *arg) +{ + int rc; + struct ble_att_svr_entry *entry; + + for (entry = STAILQ_FIRST(&ble_att_svr_list); entry != NULL; + entry = STAILQ_NEXT(entry, ha_next)) { + rc = cb(entry, arg); + if (rc != 0) { + break; + } + } +} + static int ble_att_svr_pullup_req_base(struct os_mbuf **om, int base_len, uint8_t *out_att_err) @@ -230,9 +245,8 @@ ble_att_svr_pullup_req_base(struct os_mbuf **om, int base_len, return rc; } -static void -ble_att_svr_get_sec_state(uint16_t conn_handle, - struct ble_gap_sec_state *out_sec_state) +void +ble_att_svr_get_sec_state(uint16_t conn_handle, struct ble_gap_sec_state *out_sec_state) { struct ble_hs_conn *conn; @@ -1448,6 +1462,30 @@ ble_att_svr_rx_read_type(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rx goto done; } + /* If a client that has indicated support for robust caching (by setting the Robust + * Caching bit in the Client Supported Features characteristic) is change-unaware + * then the server shall send an ATT_ERROR_RSP PDU with the Error Code + * parameter set to Database Out Of Sync (0x12) when either of the following happen: + * • That client sends an ATT_READ_BY_TYPE_REQ PDU with Attribute Type + * other than «Include» or «Characteristic» and an Attribute Handle range + * other than 0x0001 to 0xFFFF. + * (Core Specification 6.1 Vol 3. Part G. 2.5.2.1 Robust Caching). + */ + if (!is_change_aware(conn_handle)) { + if ((ble_uuid_u16(&uuid.u) != BLE_ATT_UUID_CHARACTERISTIC) && + (ble_uuid_u16(&uuid.u) != BLE_ATT_UUID_INCLUDE) && + (start_handle != 0x0001 || end_handle != 0xFFFF)) { + if (!is_out_of_sync_sent(conn_handle)) { + att_err = BLE_ATT_ERR_DB_OUT_OF_SYNC; + rc = BLE_HS_EREJECT; + set_change_aware(conn_handle, BLE_GATTS_OUT_OF_SYNC_SENT); + goto done; + } else { + return 0; + } + } + } + rc = ble_att_svr_build_read_type_rsp(conn_handle, cid, start_handle, end_handle, &uuid.u, rxom, &txom, &att_err, &err_handle); @@ -1495,6 +1533,17 @@ ble_att_svr_rx_read(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom) *rxom = NULL; os_mbuf_adj(txom, OS_MBUF_PKTLEN(txom)); + if (!is_change_aware(conn_handle)) { + if (!is_out_of_sync_sent(conn_handle)) { + att_err = BLE_ATT_ERR_DB_OUT_OF_SYNC; + rc = BLE_HS_EREJECT; + set_change_aware(conn_handle, BLE_GATTS_OUT_OF_SYNC_SENT); + goto done; + } else { + return 0; + } + } + if (ble_att_cmd_prepare(BLE_ATT_OP_READ_RSP, 0, txom) == NULL) { att_err = BLE_ATT_ERR_INSUFFICIENT_RES; rc = BLE_HS_ENOMEM; @@ -1643,9 +1692,21 @@ ble_att_svr_rx_read_mult(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rx err_handle = 0; att_err = 0; + if (!is_change_aware(conn_handle)) { + if (!is_out_of_sync_sent(conn_handle)) { + att_err = BLE_ATT_ERR_DB_OUT_OF_SYNC; + rc = BLE_HS_EREJECT; + set_change_aware(conn_handle, BLE_GATTS_OUT_OF_SYNC_SENT); + goto done; + } else { + return 0; + } + } + rc = ble_att_svr_build_read_mult_rsp(conn_handle, cid, rxom, &txom, &att_err, &err_handle); +done: return ble_att_svr_tx_rsp(conn_handle, cid, rc, txom, BLE_ATT_OP_READ_MULT_REQ, att_err, err_handle); } @@ -1657,7 +1718,7 @@ ble_att_svr_build_read_mult_rsp_var(uint16_t conn_handle, uint16_t cid, uint8_t *att_err, uint16_t *err_handle) { - struct os_mbuf *txom; + struct os_mbuf *txom = NULL; uint16_t handle; uint16_t mtu; uint16_t tuple_len; @@ -1666,6 +1727,17 @@ ble_att_svr_build_read_mult_rsp_var(uint16_t conn_handle, uint16_t cid, mtu = ble_att_mtu_by_cid(conn_handle, cid); + if (!is_change_aware(conn_handle)) { + if (!is_out_of_sync_sent(conn_handle)) { + *att_err = BLE_ATT_ERR_DB_OUT_OF_SYNC; + rc = BLE_HS_EREJECT; + set_change_aware(conn_handle, BLE_GATTS_OUT_OF_SYNC_SENT); + goto done; + } else { + return 0; + } + } + rc = ble_att_svr_pkt(rxom, &txom, att_err); if (rc != 0) { *err_handle = 0; @@ -2114,6 +2186,17 @@ ble_att_svr_rx_write(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom) att_err = 0; handle = 0; + if (!is_change_aware(conn_handle)) { + if (!is_out_of_sync_sent(conn_handle)) { + att_err = BLE_ATT_ERR_DB_OUT_OF_SYNC; + rc = BLE_HS_EREJECT; + set_change_aware(conn_handle, BLE_GATTS_OUT_OF_SYNC_SENT); + goto done; + } else { + return 0; + } + } + rc = ble_att_svr_pullup_req_base(rxom, sizeof(*req), &att_err); if (rc != 0) { goto done; @@ -2164,6 +2247,12 @@ ble_att_svr_rx_write_no_rsp(uint16_t conn_handle, uint16_t cid, struct os_mbuf * return rc; } + /* If a change-unaware client sends an ATT command, the server shall + * ignore it. */ + if (!is_change_aware(conn_handle)) { + return 0; + } + req = (struct ble_att_write_req *)(*rxom)->om_data; handle = le16toh(req->bawq_handle); @@ -2349,6 +2438,15 @@ ble_att_svr_prep_write(uint16_t conn_handle, *err_handle = 0; /* Silence unnecessary warning. */ + if (!is_change_aware(conn_handle)) { + if (!is_out_of_sync_sent(conn_handle)) { + set_change_aware(conn_handle, BLE_GATTS_OUT_OF_SYNC_SENT); + return BLE_ATT_ERR_DB_OUT_OF_SYNC; + } else { + return 0; + } + } + /* First, validate the contents of the prepare queue. */ rc = ble_att_svr_prep_validate(prep_list, err_handle); if (rc != 0) { diff --git a/nimble/host/src/ble_gatt_priv.h b/nimble/host/src/ble_gatt_priv.h index 50e9a7d826..4f277c145d 100644 --- a/nimble/host/src/ble_gatt_priv.h +++ b/nimble/host/src/ble_gatt_priv.h @@ -94,11 +94,21 @@ extern STATS_SECT_DECL(ble_gatts_stats) ble_gatts_stats; * */ #define BLE_GATT_CHR_CLI_SUP_FEAT_MASK 7 +#define BLE_GATT_DB_HASH_SZ 16 typedef uint8_t ble_gatts_conn_flags; +enum ble_gatts_change_aware_state { + BLE_GATTS_CHANGE_AWARE, + BLE_GATTS_CHANGE_UNAWARE, + BLE_GATTS_HASH_READ_PENDING, + BLE_GATTS_OUT_OF_SYNC_SENT, +}; + struct ble_gatts_conn { struct ble_gatts_clt_cfg *clt_cfgs; + enum ble_gatts_change_aware_state awareness; + uint8_t *db_hash; int num_clt_cfgs; uint16_t indicate_val_handle; @@ -216,6 +226,14 @@ int ble_gatts_peer_cl_sup_feat_update(uint16_t conn_handle, int ble_gatts_conn_can_alloc(void); int ble_gatts_conn_init(struct ble_gatts_conn *gatts_conn); int ble_gatts_init(void); +const uint8_t *ble_gatts_get_db_hash(void); +bool is_change_aware(uint16_t conn_handle); +bool is_out_of_sync_sent(uint16_t conn_handle); +void set_all_change_aware_action(ble_gap_conn_foreach_handle_fn *cb, void *arg); +void set_change_aware(uint16_t, enum ble_gatts_change_aware_state); +int set_change_unaware(uint16_t conn_handle, void *arg); +void db_hash_store(uint16_t conn_handle); +bool db_hash_cmp(const uint8_t *peer_hash); #ifdef __cplusplus } diff --git a/nimble/host/src/ble_gattc.c b/nimble/host/src/ble_gattc.c index 0ff4ec28e6..55baf4bd15 100644 --- a/nimble/host/src/ble_gattc.c +++ b/nimble/host/src/ble_gattc.c @@ -4357,6 +4357,14 @@ ble_gatts_notify_custom(uint16_t conn_handle, uint16_t chr_val_handle, STATS_INC(ble_gattc_stats, notify); + /* Except for a Handle Value Indication for the Service Changed + * characteristic, the server shall not send notifications and indications + * to such a client until it becomes change-aware */ + if (!is_change_aware(conn_handle)) { + rc = BLE_HS_EREJECT; + goto done; + } + ble_gattc_log_notify(chr_val_handle); if (txom == NULL) { @@ -4530,6 +4538,13 @@ ble_gatts_notify_multiple(uint16_t conn_handle, return BLE_HS_ENOTCONN; } + /* Except for a Handle Value Indication for the Service Changed + * characteristic, the server shall not send notifications and indications + * to such a client until it becomes change-aware */ + if (!is_change_aware(conn_handle)) { + return BLE_HS_EREJECT; + } + /** Skip sending to client that doesn't support this feature */ BLE_HS_LOG_DEBUG("ble_gatts_notify_multiple: peer_cl_sup_feat %d\n", conn->bhc_gatt_svr.peer_cl_sup_feat[0]); @@ -4651,6 +4666,9 @@ ble_gatts_indicate_custom(uint16_t conn_handle, uint16_t chr_val_handle, struct ble_gattc_proc *proc; struct ble_hs_conn *conn; + struct os_mbuf *om; + ble_uuid_any_t uuid; + static const ble_uuid16_t uuid_service_changed = BLE_UUID16_INIT(0x2A05); int rc; STATS_INC(ble_gattc_stats, indicate); @@ -4661,6 +4679,25 @@ ble_gatts_indicate_custom(uint16_t conn_handle, uint16_t chr_val_handle, goto done; } + om = NULL; + + /* Except for a Handle Value Indication for the Service Changed + * characteristic, the server shall not send notifications and indications + * to such a client until it becomes change-aware */ + if (!is_change_aware(conn_handle)) { + rc = ble_att_svr_read_local(chr_val_handle - 1, &om); + if (rc != 0) { + return rc; + } + ble_uuid_init_from_att_mbuf(&uuid, om, 3, 2); + if (ble_uuid_cmp(&uuid.u, &uuid_service_changed.u) != 0) { + os_mbuf_free_chain(om); + rc = BLE_HS_EREJECT; + goto done; + } + os_mbuf_free_chain(om); + } + ble_gattc_proc_prepare(proc, conn_handle, BLE_GATT_OP_INDICATE); proc->indicate.chr_val_handle = chr_val_handle; diff --git a/nimble/host/src/ble_gatts.c b/nimble/host/src/ble_gatts.c index dcd403e120..d9085aba2e 100644 --- a/nimble/host/src/ble_gatts.c +++ b/nimble/host/src/ble_gatts.c @@ -25,6 +25,9 @@ #include "host/ble_uuid.h" #include "host/ble_store.h" #include "ble_hs_priv.h" +#include "tinycrypt/aes.h" +#include "tinycrypt/cmac_mode.h" +#include "tinycrypt/constants.h" #define BLE_GATTS_INCLUDE_SZ 6 #define BLE_GATTS_CHR_MAX_SZ 19 @@ -62,6 +65,10 @@ struct ble_gatts_clt_cfg { static struct ble_gatts_clt_cfg *ble_gatts_clt_cfgs; static int ble_gatts_num_cfgable_chrs; +#if MYNEWT_VAL(BLE_ATT_SVR_ROBUST_CACHE) +uint8_t db_hash[BLE_GATT_DB_HASH_SZ]; +#endif + STATS_SECT_DECL(ble_gatts_stats) ble_gatts_stats; STATS_NAME_START(ble_gatts_stats) STATS_NAME(ble_gatts_stats, svcs) @@ -1159,6 +1166,8 @@ ble_gatts_register_svcs(const struct ble_gatt_svc_def *svcs, int rc; int i; + num_svcs = 0; + for (i = 0; svcs[i].type != BLE_GATT_SVC_TYPE_END; i++) { idx = ble_gatts_num_svc_entries + i; if (idx >= ble_hs_max_services) { @@ -1325,6 +1334,14 @@ ble_gatts_start(void) } ble_gatts_free_svc_defs(); +#if MYNEWT_VAL(BLE_ATT_SVR_ROBUST_CACHE) + rc = ble_gatts_calculate_db_hash(); + if (rc != 0) { + /* Hash needs to be recalculated */ + goto done; + } +#endif + if (ble_gatts_num_cfgable_chrs == 0) { rc = 0; goto done; @@ -1508,8 +1525,11 @@ int ble_gatts_rx_indicate_ack(uint16_t conn_handle, uint16_t chr_val_handle) { struct ble_store_value_cccd cccd_value; + static const ble_uuid16_t uuid_service_chagned = BLE_UUID16_INIT(0x2A05); struct ble_gatts_clt_cfg *clt_cfg; struct ble_hs_conn *conn; + struct os_mbuf *om; + ble_uuid_any_t uuid; int clt_cfg_idx; int persist; int rc; @@ -1535,6 +1555,21 @@ ble_gatts_rx_indicate_ack(uint16_t conn_handle, uint16_t chr_val_handle) /* This acknowledgement is expected. */ rc = 0; + /* If this is service changed confirmation, we have to set + * peer as change-aware */ + rc = ble_att_svr_read_local(chr_val_handle - 1, &om); + if (rc != 0) { + return rc; + } + ble_uuid_init_from_att_mbuf(&uuid, om, 3, 2); + if (ble_uuid_cmp(&uuid.u, &uuid_service_chagned.u) == 0) { + if (!is_change_aware(conn_handle)) { + set_change_aware(conn_handle, BLE_GATTS_CHANGE_AWARE); + } + } + + os_mbuf_free_chain(om); + /* Mark that there is no longer an outstanding txed indicate. */ conn->bhc_gatt_svr.indicate_val_handle = 0; @@ -1757,6 +1792,32 @@ ble_gatts_peer_cl_sup_feat_update(uint16_t conn_handle, struct os_mbuf *om) memcpy(conn->bhc_gatt_svr.peer_cl_sup_feat, feat, BLE_GATT_CHR_CLI_SUP_FEAT_SZ); + if (conn->bhc_sec_state.bonded) { + feat_key.peer_addr = conn->bhc_peer_addr; + rc = ble_store_read_peer_cl_sup_feat(&feat_key, &feat_val); + if (rc == BLE_HS_ENOENT) { + /* Assume the values were not stored yet */ + store_feat.peer_addr = conn->bhc_peer_addr; + memcpy(store_feat.peer_cl_sup_feat, feat, BLE_GATT_CHR_CLI_SUP_FEAT_SZ); + if (ble_store_write_peer_cl_sup_feat(&store_feat)) { + /* XXX: How to report an error? */ + } + rc = 0; + goto done; + } + if (rc == 0) { + /* Found cl_sup_feat for this peer, check for disabling already + * enabled features */ + for (i = 0; i < BLE_GATT_CHR_CLI_SUP_FEAT_SZ; i++) { + if ((feat_val.peer_cl_sup_feat[i] & feat[i]) != + feat_val.peer_cl_sup_feat[i]) { + rc = BLE_ATT_ERR_VALUE_NOT_ALLOWED; + goto done; + } + } + } + } + done: ble_hs_unlock(); return rc; @@ -1896,12 +1957,17 @@ ble_gatts_bonding_established(uint16_t conn_handle) * o Sends all pending notifications to the connected peer. * o Sends up to one pending indication to the connected peer; schedules * any remaining pending indications. + * o Sets change (un)aware state and stores database hash if needed. */ void ble_gatts_bonding_restored(uint16_t conn_handle) { struct ble_store_value_cccd cccd_value; struct ble_store_key_cccd cccd_key; + struct ble_store_key_db_hash db_hash_key; + struct ble_store_value_db_hash db_hash_value; + struct ble_store_key_cl_sup_feat feat_key; + struct ble_store_value_cl_sup_feat feat_val; struct ble_gatts_clt_cfg *clt_cfg; struct ble_hs_conn *conn; uint8_t att_op; @@ -1913,14 +1979,37 @@ ble_gatts_bonding_restored(uint16_t conn_handle) BLE_HS_DBG_ASSERT(conn != NULL); BLE_HS_DBG_ASSERT(conn->bhc_sec_state.bonded); - cccd_key.peer_addr = conn->bhc_peer_addr; - cccd_key.peer_addr.type = + cccd_key.peer_addr = db_hash_key.peer_addr = feat_key.peer_addr = + conn->bhc_peer_addr; + cccd_key.peer_addr.type = db_hash_key.peer_addr.type = feat_key.peer_addr.type = ble_hs_misc_peer_addr_type_to_id(conn->bhc_peer_addr.type); cccd_key.chr_val_handle = 0; - cccd_key.idx = 0; + cccd_key.idx = db_hash_key.idx = feat_key.idx = 0; ble_hs_unlock(); + /* The initial state of a client with a trusted relationship is unchanged + * from the previous connection unless the database has been updated since + * the last connection, in which case the initial state is change-unaware. + * */ + if (!ble_store_read_peer_cl_sup_feat(&feat_key, &feat_val)) { + /* Check if peer supports robust caching */ + if ((feat_val.peer_cl_sup_feat[0] & (1 << 0)) != 0) { + if (conn->bhc_gatt_svr.awareness == BLE_GATTS_HASH_READ_PENDING) { + /* Peer has probably read hash before bonding was restored, + * we can now write current hash to the storage */ + db_hash_store(conn_handle); + } else if (ble_store_read_db_hash(&db_hash_key, &db_hash_value) == + BLE_HS_ENOENT) { + /* XXX: ? */ + } else { + if (!db_hash_cmp(db_hash_value.db_hash)) { + set_change_unaware(conn_handle, NULL); + } + } + } + } + while (1) { rc = ble_store_read_cccd(&cccd_key, &cccd_value); if (rc != 0) { @@ -2339,6 +2428,211 @@ ble_gatts_lcl_svc_foreach(ble_gatt_svc_foreach_fn cb, void *arg) } } +#define BUF_PUT_LE16(buf, buf_len, val) \ + do { \ + put_le16((buf) + (buf_len), (val)); \ + (buf_len) += 2; \ + } while (0) + +int +ble_gatts_foreach_hash_append(struct ble_att_svr_entry *entry, void *arg) +{ + uint16_t uuid; + struct os_mbuf *om; + uint8_t buf[24]; + uint16_t buf_len; + int rc; + + uuid = ble_uuid_u16(entry->ha_uuid); + buf_len = 0; + om = NULL; + + switch (uuid) { + case BLE_ATT_UUID_PRIMARY_SERVICE: + case BLE_ATT_UUID_SECONDARY_SERVICE: + case BLE_ATT_UUID_INCLUDE: + case BLE_ATT_UUID_CHARACTERISTIC: + case BLE_GATT_DSC_EXT_PROP_UUID16: + /* attr handle */ + BUF_PUT_LE16(buf, buf_len, entry->ha_handle_id); + /* attr type */ + BUF_PUT_LE16(buf, buf_len, uuid); + rc = ble_att_svr_read_local(entry->ha_handle_id, &om); + if (rc != 0) { + return rc; + } + /* attr value */ + os_mbuf_copydata(om, 0, os_mbuf_len(om), buf + buf_len); + buf_len += os_mbuf_len(om); + os_mbuf_free_chain(om); + break; + + case BLE_GATT_DSC_CLT_CFG_UUID16: + /* attr handle */ + BUF_PUT_LE16(buf, buf_len, entry->ha_handle_id); + /* attr type */ + BUF_PUT_LE16(buf, buf_len, uuid); + break; + default: + return 0; + } + + if (tc_cmac_update(arg, buf, buf_len) == TC_CRYPTO_FAIL) { + return BLE_HS_EUNKNOWN; + } + + return 0; +} + +int +ble_gatts_calculate_db_hash() +{ +#if !MYNEWT_VAL(BLE_ATT_SVR_ROBUST_CACHE) + return BLE_HS_ENOTSUP; +#endif + struct tc_aes_key_sched_struct sched; + struct tc_cmac_struct state; + + /* 128-bit key shall be all zero (7.3.1 Core spec) */ + const uint8_t key[16] = { 0 }; + + if (tc_cmac_setup(&state, key, &sched) == TC_CRYPTO_FAIL) { + return BLE_HS_EUNKNOWN; + } + + ble_att_svr_foreach(ble_gatts_foreach_hash_append, &state); + + if (tc_cmac_final(db_hash, &state) == TC_CRYPTO_FAIL) { + return BLE_HS_EUNKNOWN; + } + + /* Endianness of the hash is not indicated in Core spec; + * PTS expects hash to be in little endian */ + swap_in_place(db_hash, BLE_GATT_DB_HASH_SZ); + + return 0; +} + +void +set_change_aware(uint16_t conn_handle, enum ble_gatts_change_aware_state state) +{ + struct ble_hs_conn *conn; + + ble_hs_lock(); + conn = ble_hs_conn_find_assert(conn_handle); + ble_hs_unlock(); + + conn->bhc_gatt_svr.awareness = state; +} + +int +set_change_unaware(uint16_t conn_handle, void *arg) +{ + struct ble_hs_conn *conn; + + ble_hs_lock(); + conn = ble_hs_conn_find_assert(conn_handle); + ble_hs_unlock(); + + conn->bhc_gatt_svr.awareness = BLE_GATTS_CHANGE_UNAWARE; + + return 0; +} + +void +set_all_change_aware_action(ble_gap_conn_foreach_handle_fn *cb, void *arg) +{ + ble_gap_conn_foreach_handle(cb, arg); +} + +const uint8_t * +ble_gatts_get_db_hash() +{ + return db_hash; +} + +bool +is_out_of_sync_sent(uint16_t conn_handle) +{ + struct ble_hs_conn *conn; + + ble_hs_lock(); + conn = ble_hs_conn_find_assert(conn_handle); + ble_hs_unlock(); + + return conn->bhc_gatt_svr.awareness == BLE_GATTS_OUT_OF_SYNC_SENT; +} + +bool +is_change_aware(uint16_t conn_handle) +{ + struct ble_hs_conn *conn; + + ble_hs_lock(); + conn = ble_hs_conn_find_assert(conn_handle); + ble_hs_unlock(); + + if ((conn->bhc_gatt_svr.peer_cl_sup_feat[0] & (1 << 0)) == 0) { + return true; + } + + if (conn->bhc_gatt_svr.awareness == BLE_GATTS_CHANGE_AWARE) { + return true; + } + + if (conn->bhc_gatt_svr.awareness == BLE_GATTS_HASH_READ_PENDING) { + set_change_aware(conn_handle, BLE_GATTS_CHANGE_AWARE); + return true; + } + + if (conn->bhc_gatt_svr.awareness == BLE_GATTS_OUT_OF_SYNC_SENT) { + /* TODO: set change_aware only for specific ATT bearer. */ + set_change_aware(conn_handle, BLE_GATTS_CHANGE_AWARE); + return true; + } + + return false; +} + +void +db_hash_store(uint16_t conn_handle) +{ + struct ble_store_value_db_hash store_hash; + struct ble_hs_conn *conn; + + ble_hs_lock(); + + conn = ble_hs_conn_find(conn_handle); + BLE_HS_DBG_ASSERT(conn != NULL); + + store_hash.peer_addr = conn->bhc_peer_addr; + store_hash.peer_addr.type = + ble_hs_misc_peer_addr_type_to_id(conn->bhc_peer_addr.type); + store_hash.db_hash = ble_gatts_get_db_hash(); + + ble_hs_unlock(); + + ble_store_write_db_hash(&store_hash); +} + +bool +db_hash_cmp(const uint8_t *peer_hash) +{ + uint8_t diff; + const uint8_t *global_hash = ble_gatts_get_db_hash(); + + if (global_hash == NULL || peer_hash == NULL) { + return false; + } + + diff = 0; + for (size_t i = 0; i < BLE_GATT_DB_HASH_SZ; ++i) { + diff |= global_hash[i] ^ peer_hash[i]; + } + + return diff == 0; +} + int ble_gatts_reset(void) { diff --git a/nimble/host/src/ble_store.c b/nimble/host/src/ble_store.c index 79b2f7b95e..e85c908b82 100644 --- a/nimble/host/src/ble_store.c +++ b/nimble/host/src/ble_store.c @@ -294,6 +294,50 @@ ble_store_key_from_value_cccd(struct ble_store_key_cccd *out_key, out_key->idx = 0; } +int +ble_store_read_db_hash(const struct ble_store_key_db_hash *key, + struct ble_store_value_db_hash *out_value) +{ + union ble_store_value *store_value; + union ble_store_key *store_key; + int rc; + + store_key = (void *)key; + store_value = (void *)out_value; + rc = ble_store_read(BLE_STORE_OBJ_TYPE_DB_HASH, store_key, store_value); + return rc; +} + +int +ble_store_write_db_hash(const struct ble_store_value_db_hash *value) +{ + union ble_store_value *store_value; + int rc; + + store_value = (void *)value; + rc = ble_store_write(BLE_STORE_OBJ_TYPE_DB_HASH, store_value); + return rc; +} + +int +ble_store_delete_db_hash(const struct ble_store_value_db_hash *key) +{ + union ble_store_key *store_key; + int rc; + + store_key = (void *)key; + rc = ble_store_delete(BLE_STORE_OBJ_TYPE_DB_HASH, store_key); + return rc; +} + +void +ble_store_key_from_value_db_hash(struct ble_store_key_db_hash *out_key, + const struct ble_store_value_db_hash *value) +{ + out_key->peer_addr = value->peer_addr; + out_key->idx = 0; +} + void ble_store_key_from_value_sec(struct ble_store_key_sec *out_key, const struct ble_store_value_sec *value) @@ -317,6 +361,10 @@ ble_store_key_from_value(int obj_type, ble_store_key_from_value_cccd(&out_key->cccd, &value->cccd); break; + case BLE_STORE_OBJ_TYPE_DB_HASH: + ble_store_key_from_value_db_hash(&out_key->db_hash, &value->db_hash); + break; + default: BLE_HS_DBG_ASSERT(0); break; @@ -346,6 +394,14 @@ ble_store_iterate(int obj_type, key.cccd.peer_addr = *BLE_ADDR_ANY; pidx = &key.cccd.idx; break; + case BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT: + key.feat.peer_addr = *BLE_ADDR_ANY; + pidx = &key.feat.idx; + break; + case BLE_STORE_OBJ_TYPE_DB_HASH: + key.db_hash.peer_addr = *BLE_ADDR_ANY; + pidx = &key.db_hash.idx; + break; default: BLE_HS_DBG_ASSERT(0); return BLE_HS_EINVAL; @@ -390,6 +446,7 @@ ble_store_clear(void) BLE_STORE_OBJ_TYPE_OUR_SEC, BLE_STORE_OBJ_TYPE_PEER_SEC, BLE_STORE_OBJ_TYPE_CCCD, + BLE_STORE_OBJ_TYPE_DB_HASH, }; union ble_store_key key; int obj_type; diff --git a/nimble/host/src/ble_store_util.c b/nimble/host/src/ble_store_util.c index 6dcbca25a1..b9509a819e 100644 --- a/nimble/host/src/ble_store_util.c +++ b/nimble/host/src/ble_store_util.c @@ -109,6 +109,14 @@ ble_store_util_delete_peer(const ble_addr_t *peer_id_addr) return rc; } + memset(&key, 0, sizeof key); + key.db_hash.peer_addr = *peer_id_addr; + + rc = ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_DB_HASH, &key); + if (rc != 0) { + return rc; + } + return 0; } @@ -192,6 +200,7 @@ ble_store_util_status_rr(struct ble_store_status_event *event, void *arg) switch (event->overflow.obj_type) { case BLE_STORE_OBJ_TYPE_OUR_SEC: case BLE_STORE_OBJ_TYPE_PEER_SEC: + case BLE_STORE_OBJ_TYPE_DB_HASH: return ble_gap_unpair_oldest_peer(); case BLE_STORE_OBJ_TYPE_CCCD: /* Try unpairing oldest peer except current peer */ diff --git a/nimble/host/store/config/src/ble_store_config.c b/nimble/host/store/config/src/ble_store_config.c index 741c73a5f5..6469c20353 100644 --- a/nimble/host/store/config/src/ble_store_config.c +++ b/nimble/host/store/config/src/ble_store_config.c @@ -47,6 +47,12 @@ struct ble_store_value_cccd int ble_store_config_num_cccds; +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) +struct ble_store_value_db_hash ble_store_config_db_hashes[MYNEWT_VAL(BLE_STORE_MAX_BONDS)]; +#endif + +int ble_store_config_num_db_hashes; + /***************************************************************************** * $sec * *****************************************************************************/ @@ -447,6 +453,109 @@ ble_store_config_write_cccd(const struct ble_store_value_cccd *value_cccd) #endif } +/***************************************************************************** + * hash * + *****************************************************************************/ + +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) +static int +ble_store_config_find_hash(const struct ble_store_key_db_hash *key_hash) +{ + struct ble_store_value_db_hash *hash; + int skipped; + int i; + + skipped = 0; + for (i = 0; i < ble_store_config_num_db_hashes; i++) { + hash = ble_store_config_db_hashes + i; + + if (ble_addr_cmp(&key_hash->peer_addr, BLE_ADDR_ANY)) { + if (ble_addr_cmp(&hash->peer_addr, &key_hash->peer_addr)) { + continue; + } + } + + if (key_hash->idx > skipped) { + skipped++; + continue; + } + + return i; + } + + return -1; +} +#endif + +static int +ble_store_config_delete_hash(const struct ble_store_key_db_hash *key_hash) +{ +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) + int idx; + int rc; + + idx = ble_store_config_find_hash(key_hash); + if (idx == -1) { + return BLE_HS_ENOENT; + } + + rc = ble_store_config_delete_obj(ble_store_config_db_hashes, + sizeof *ble_store_config_db_hashes, idx, + &ble_store_config_num_db_hashes); + if (rc != 0) { + return rc; + } + return 0; +#else + return BLE_HS_ENOENT; +#endif +} + +static int +ble_store_config_read_db_hash(const struct ble_store_key_db_hash *key_hash, + struct ble_store_value_db_hash *value_hash) +{ +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) + int idx; + + idx = ble_store_config_find_hash(key_hash); + if (idx == -1) { + return BLE_HS_ENOENT; + } + + *value_hash = ble_store_config_db_hashes[idx]; + + return 0; +#else + return BLE_HS_ENOENT; +#endif +} + +static int +ble_store_config_write_db_hash(const struct ble_store_value_db_hash *value_hash) +{ +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) + struct ble_store_key_db_hash key_hash; + int idx; + + ble_store_key_from_value_db_hash(&key_hash, value_hash); + idx = ble_store_config_find_hash(&key_hash); + if (idx == -1) { + if (ble_store_config_num_db_hashes >= MYNEWT_VAL(BLE_STORE_MAX_BONDS)) { + return BLE_HS_ESTORE_CAP; + } + + idx = ble_store_config_num_db_hashes; + ble_store_config_num_db_hashes++; + } + + ble_store_config_db_hashes[idx] = *value_hash; + return 0; +#else + return BLE_HS_ENOENT; +#endif +} + /***************************************************************************** * $api * *****************************************************************************/ @@ -489,6 +598,10 @@ ble_store_config_read(int obj_type, const union ble_store_key *key, rc = ble_store_config_read_cccd(&key->cccd, &value->cccd); return rc; + case BLE_STORE_OBJ_TYPE_DB_HASH: + rc = ble_store_config_read_db_hash(&key->db_hash, &value->db_hash); + return rc; + default: return BLE_HS_ENOTSUP; } @@ -518,6 +631,10 @@ ble_store_config_write(int obj_type, const union ble_store_value *val) rc = ble_store_config_write_cccd(&val->cccd); return rc; + case BLE_STORE_OBJ_TYPE_DB_HASH: + rc = ble_store_config_write_db_hash(&val->db_hash); + return rc; + default: return BLE_HS_ENOTSUP; } @@ -541,6 +658,10 @@ ble_store_config_delete(int obj_type, const union ble_store_key *key) rc = ble_store_config_delete_cccd(&key->cccd); return rc; + case BLE_STORE_OBJ_TYPE_DB_HASH: + rc = ble_store_config_delete_hash(&key->db_hash); + return rc; + default: return BLE_HS_ENOTSUP; } @@ -560,6 +681,7 @@ ble_store_config_init(void) ble_store_config_num_our_secs = 0; ble_store_config_num_peer_secs = 0; ble_store_config_num_cccds = 0; + ble_store_config_num_db_hashes = 0; ble_store_config_conf_init(); } 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 bae90e9765..7ee751f83c 100644 --- a/nimble/host/store/config/src/ble_store_config_priv.h +++ b/nimble/host/store/config/src/ble_store_config_priv.h @@ -36,11 +36,15 @@ extern struct ble_store_value_cccd ble_store_config_cccds[MYNEWT_VAL(BLE_STORE_MAX_CCCDS)]; extern int ble_store_config_num_cccds; +extern struct ble_store_value_db_hash ble_store_config_db_hashes[MYNEWT_VAL(BLE_STORE_MAX_BONDS)]; +extern int ble_store_config_num_db_hashes; + #if MYNEWT_VAL(BLE_STORE_CONFIG_PERSIST) int ble_store_config_persist_our_secs(void); int ble_store_config_persist_peer_secs(void); int ble_store_config_persist_cccds(void); +int ble_store_config_persist_db_hash(void); void ble_store_config_conf_init(void); #else @@ -48,6 +52,11 @@ void ble_store_config_conf_init(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; } static inline int ble_store_config_persist_cccds(void) { return 0; } +static inline int +ble_store_config_persist_db_hash(void) +{ + return 0; +} static inline void ble_store_config_conf_init(void) { } #endif /* MYNEWT_VAL(BLE_STORE_CONFIG_PERSIST) */ diff --git a/nimble/host/store/ram/src/ble_store_ram.c b/nimble/host/store/ram/src/ble_store_ram.c index 9359f6d399..ace1ea69ae 100644 --- a/nimble/host/store/ram/src/ble_store_ram.c +++ b/nimble/host/store/ram/src/ble_store_ram.c @@ -57,6 +57,12 @@ static struct ble_store_value_cccd static int ble_store_ram_num_cccds; +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) +static struct ble_store_value_db_hash ble_store_ram_db_hashes[MYNEWT_VAL(BLE_STORE_MAX_BONDS)]; +#endif + +static int ble_store_ram_num_db_hashes; + /***************************************************************************** * $sec * *****************************************************************************/ @@ -426,6 +432,109 @@ ble_store_ram_write_cccd(const struct ble_store_value_cccd *value_cccd) } +/***************************************************************************** + * hash * + *****************************************************************************/ + +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) +static int +ble_store_ram_find_hash(const struct ble_store_key_db_hash *key_hash) +{ + struct ble_store_value_db_hash *hash; + int skipped; + int i; + + skipped = 0; + for (i = 0; i < ble_store_ram_num_db_hashes; i++) { + hash = ble_store_ram_db_hashes + i; + + if (ble_addr_cmp(&key_hash->peer_addr, BLE_ADDR_ANY)) { + if (ble_addr_cmp(&hash->peer_addr, &key_hash->peer_addr)) { + continue; + } + } + + if (key_hash->idx > skipped) { + skipped++; + continue; + } + + return i; + } + + return -1; +} +#endif + +static int +ble_store_ram_delete_hash(const struct ble_store_key_db_hash *key_hash) +{ +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) + int idx; + int rc; + + idx = ble_store_ram_find_hash(key_hash); + if (idx == -1) { + return BLE_HS_ENOENT; + } + + rc = ble_store_ram_delete_obj(ble_store_ram_db_hashes, + sizeof *ble_store_ram_db_hashes, idx, + &ble_store_ram_num_db_hashes); + if (rc != 0) { + return rc; + } + return 0; +#else + return BLE_HS_ENOENT; +#endif +} + +static int +ble_store_ram_read_db_hash(const struct ble_store_key_db_hash *key_hash, + struct ble_store_value_db_hash *value_hash) +{ +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) + int idx; + + idx = ble_store_ram_find_hash(key_hash); + if (idx == -1) { + return BLE_HS_ENOENT; + } + + *value_hash = ble_store_ram_db_hashes[idx]; + + return 0; +#else + return BLE_HS_ENOENT; +#endif +} + +static int +ble_store_ram_write_db_hash(const struct ble_store_value_db_hash *value_hash) +{ +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) + struct ble_store_key_db_hash key_hash; + int idx; + + ble_store_key_from_value_db_hash(&key_hash, value_hash); + idx = ble_store_ram_find_hash(&key_hash); + if (idx == -1) { + if (ble_store_ram_num_db_hashes >= MYNEWT_VAL(BLE_STORE_MAX_BONDS)) { + return BLE_HS_ESTORE_CAP; + } + + idx = ble_store_ram_num_db_hashes; + ble_store_ram_num_db_hashes++; + } + + ble_store_ram_db_hashes[idx] = *value_hash; + return 0; +#else + return BLE_HS_ENOENT; +#endif +} + /***************************************************************************** * $api * *****************************************************************************/ @@ -468,6 +577,10 @@ ble_store_ram_read(int obj_type, const union ble_store_key *key, rc = ble_store_ram_read_cccd(&key->cccd, &value->cccd); return rc; + case BLE_STORE_OBJ_TYPE_DB_HASH: + rc = ble_store_ram_read_db_hash(&key->db_hash, &value->db_hash); + return rc; + default: return BLE_HS_ENOTSUP; } @@ -497,6 +610,10 @@ ble_store_ram_write(int obj_type, const union ble_store_value *val) rc = ble_store_ram_write_cccd(&val->cccd); return rc; + case BLE_STORE_OBJ_TYPE_DB_HASH: + rc = ble_store_ram_write_db_hash(&val->db_hash); + return rc; + default: return BLE_HS_ENOTSUP; } @@ -520,6 +637,10 @@ ble_store_ram_delete(int obj_type, const union ble_store_key *key) rc = ble_store_ram_delete_cccd(&key->cccd); return rc; + case BLE_STORE_OBJ_TYPE_DB_HASH: + rc = ble_store_ram_delete_hash(&key->db_hash); + return rc; + default: return BLE_HS_ENOTSUP; } @@ -539,4 +660,5 @@ ble_store_ram_init(void) ble_store_ram_num_our_secs = 0; ble_store_ram_num_peer_secs = 0; ble_store_ram_num_cccds = 0; + ble_store_ram_num_db_hashes = 0; } diff --git a/nimble/host/syscfg.yml b/nimble/host/syscfg.yml index 873dcae4bf..d635bc691b 100644 --- a/nimble/host/syscfg.yml +++ b/nimble/host/syscfg.yml @@ -402,6 +402,10 @@ syscfg.defs: commands. (0/1) value: 1 + BLE_ATT_SVR_ROBUST_CACHE: + description: dupa + value: 1 + # ATT options. BLE_ATT_PREFERRED_MTU: description: The preferred MTU to indicate in MTU exchange commands. From af7bb4f1c4e49064c89b8c850aab9242e5f1f6b9 Mon Sep 17 00:00:00 2001 From: Piotr Narajowski Date: Tue, 26 Aug 2025 11:19:21 +0200 Subject: [PATCH 2/6] nimble/host: Add support for new type of object in the storage. Add support for BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT. This is used for storing values of Client Supported Features for clients with a trusted relationship. --- nimble/host/include/host/ble_store.h | 121 ++++++++++++++- nimble/host/src/ble_store.c | 53 +++++++ nimble/host/src/ble_store_util.c | 9 ++ .../host/store/config/src/ble_store_config.c | 139 ++++++++++++++++++ .../store/config/src/ble_store_config_conf.c | 40 +++++ .../store/config/src/ble_store_config_priv.h | 7 +- nimble/host/store/ram/src/ble_store_ram.c | 130 +++++++++++++++- 7 files changed, 492 insertions(+), 7 deletions(-) diff --git a/nimble/host/include/host/ble_store.h b/nimble/host/include/host/ble_store.h index a57bebb5e1..ea72e2f4c4 100644 --- a/nimble/host/include/host/ble_store.h +++ b/nimble/host/include/host/ble_store.h @@ -29,6 +29,7 @@ #include #include "nimble/ble.h" +#include "host/ble_gatt.h" #ifdef __cplusplus extern "C" { @@ -40,17 +41,19 @@ extern "C" { * @{ */ /** Object type: Our security material. */ -#define BLE_STORE_OBJ_TYPE_OUR_SEC 1 +#define BLE_STORE_OBJ_TYPE_OUR_SEC 1 /** Object type: Peer security material. */ -#define BLE_STORE_OBJ_TYPE_PEER_SEC 2 +#define BLE_STORE_OBJ_TYPE_PEER_SEC 2 /** Object type: Client Characteristic Configuration Descriptor. */ -#define BLE_STORE_OBJ_TYPE_CCCD 3 +#define BLE_STORE_OBJ_TYPE_CCCD 3 /** Object type: Peer last known database hash. */ -#define BLE_STORE_OBJ_TYPE_DB_HASH 4 +#define BLE_STORE_OBJ_TYPE_DB_HASH 4 +/** Object type: Peer Client Supported Features. */ +#define BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT 5 /** @} */ /** @@ -184,6 +187,33 @@ struct ble_store_value_db_hash { * is change aware/unaware. */ const uint8_t *db_hash; }; + + /** + * Used as a key for lookups of stored client supported features of specific + * peer. This struct corresponds to the BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT + * store object type. + */ +struct ble_store_key_cl_sup_feat { + /** + * Key by peer identity address; + * peer_addr=BLE_ADDR_NONE means don't key off peer. + */ + ble_addr_t peer_addr; + + /** Number of results to skip; 0 means retrieve the first match. */ + uint8_t idx; +}; + +/** + * Represents a stored client supported features of specific peer. This struct + * corresponds to the BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT store object type. + */ + struct ble_store_value_cl_sup_feat { + /** The peer address associated with the stored supported features. */ + ble_addr_t peer_addr; + /** Client supported features of a specific peer. */ + uint8_t peer_cl_sup_feat[BLE_GATT_CHR_CLI_SUP_FEAT_SZ]; + }; /** * Used as a key for store lookups. This union must be accompanied by an @@ -196,6 +226,8 @@ union ble_store_key { struct ble_store_key_cccd cccd; /** Key for database hash store lookups */ struct ble_store_key_db_hash db_hash; + /** Key for Peer Client Supported Features store lookpus. */ + struct ble_store_key_cl_sup_feat feat; }; /** @@ -209,6 +241,8 @@ union ble_store_value { struct ble_store_value_cccd cccd; /** Stored database hash. */ struct ble_store_value_db_hash db_hash; + /** Stored Client Supported Features. */ + struct ble_store_value_cl_sup_feat feat; }; /** Represents an event associated with the BLE Store. */ @@ -659,6 +693,65 @@ int ble_store_delete_db_hash(const struct ble_store_value_db_hash *key); void ble_store_key_from_value_db_hash(struct ble_store_key_db_hash *out_key, const struct ble_store_value_db_hash *value); +/** + * @brief Reads Client Supported Features value from a storage + * + * This function reads client supported features value from a storage based + * on the provied key and stores the retrieved value in the specified output + * structure. + * + * @param key A pointer to a 'ble_store_key_cl_sup_feat' + * struct representing the key to identify + * Client Supported Features value to be read. + * @param out_value A pointer to a 'ble_store_value_cl_sup_feat' + * struct to store the Client Supported + * Features value read from a storage + * + * @return 0 if the Client Supported Features values was + * successfully read and stored in the + * 'out_value' structure; + * Non-zero on error + */ +int +ble_store_read_peer_cl_sup_feat(const struct ble_store_key_cl_sup_feat *key, + struct ble_store_value_cl_sup_feat *out_value); + +/** + * @brief Writes a Client Supported Features value to a storage. + * + * This function writes a Client Supported Features value to a storage based on + * the provided value + * + * @param value A pointer to a 'ble_store_value_cl_sup_feat' + * structure representing the Client Supported + * Features value to be written to a storage. + * + * @return 0 if the value was successfully written to + * a storage; + * Non-zero on error. + */ +int +ble_store_write_peer_cl_sup_feat(const struct ble_store_value_cl_sup_feat + *value); + +/** + * @brief Deletes a Client Supported Features value from a storage. + * + * This function deletes a Client Supported Features value from a storage based + * on the provided key. + * + * @param key A pointer to a 'ble_store_key_cl_sup_feat' + * structure identifying the Client Supported + * Features value to be deleted from + * a storage. + * + * @return 0 if the Client Supported Features value was + * successfully written to a storage; + * Non-zero on error. + */ +int ble_store_delete_peer_cl_sup_feat(const struct ble_store_key_cl_sup_feat + *key); + /** * @brief Generates a storage key for a security material entry from its value. * @@ -689,6 +782,26 @@ void ble_store_key_from_value_sec(struct ble_store_key_sec *out_key, void ble_store_key_from_value_cccd(struct ble_store_key_cccd *out_key, const struct ble_store_value_cccd *value); +/** + * @brief Generates a storage key for a Client Supported Features entry from + * its value + * + * This function generates a storage key for a Client Supported Features value + * entry based on the provided value. + * + * @param out_key A pointer to a 'ble_store_key_cl_sup_feat' + * structure where the generated key will be + * stored. + * @param value A pointer to a 'ble_store_value_cl_sup_feat' + * structure containing the Client Supported + * Features value from which the key will be + * generated. + */ +void +ble_store_key_from_value_peer_cl_sup_feat(struct ble_store_key_cl_sup_feat + *out_key, + const struct ble_store_value_cl_sup_feat + *value); /** * @brief Generates a storage key from a value based on the object type. diff --git a/nimble/host/src/ble_store.c b/nimble/host/src/ble_store.c index e85c908b82..3ff423c752 100644 --- a/nimble/host/src/ble_store.c +++ b/nimble/host/src/ble_store.c @@ -249,6 +249,44 @@ ble_store_write_peer_sec(const struct ble_store_value_sec *value_sec) return 0; } +int +ble_store_read_peer_cl_sup_feat(const struct ble_store_key_cl_sup_feat *key, + struct ble_store_value_cl_sup_feat *out_value) +{ + union ble_store_value *store_value; + union ble_store_key *store_key; + int rc; + + store_key = (void *)key; + store_value = (void *)out_value; + rc = ble_store_read(BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT, store_key, + store_value); + return rc; +} + +int +ble_store_write_peer_cl_sup_feat(const struct ble_store_value_cl_sup_feat + *value) +{ + union ble_store_value *store_value; + int rc; + + store_value = (void *)value; + rc = ble_store_write(BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT, store_value); + return rc; +} + +int +ble_store_delete_peer_cl_sup_feat(const struct ble_store_key_cl_sup_feat *key) +{ + union ble_store_key *store_key; + int rc; + + store_key = (void *)key; + rc = ble_store_delete(BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT, store_key); + return rc; +} + int ble_store_read_cccd(const struct ble_store_key_cccd *key, struct ble_store_value_cccd *out_value) @@ -346,6 +384,16 @@ ble_store_key_from_value_sec(struct ble_store_key_sec *out_key, out_key->idx = 0; } +void +ble_store_key_from_value_peer_cl_sup_feat(struct ble_store_key_cl_sup_feat + *out_key, + const struct ble_store_value_cl_sup_feat + *value) +{ + out_key->peer_addr = value->peer_addr; + out_key->idx = 0; +} + void ble_store_key_from_value(int obj_type, union ble_store_key *out_key, @@ -365,6 +413,10 @@ ble_store_key_from_value(int obj_type, ble_store_key_from_value_db_hash(&out_key->db_hash, &value->db_hash); break; + case BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT: + ble_store_key_from_value_peer_cl_sup_feat(&out_key->feat, + &value->feat); + default: BLE_HS_DBG_ASSERT(0); break; @@ -447,6 +499,7 @@ ble_store_clear(void) BLE_STORE_OBJ_TYPE_PEER_SEC, BLE_STORE_OBJ_TYPE_CCCD, BLE_STORE_OBJ_TYPE_DB_HASH, + BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT, }; union ble_store_key key; int obj_type; diff --git a/nimble/host/src/ble_store_util.c b/nimble/host/src/ble_store_util.c index b9509a819e..36c28a7553 100644 --- a/nimble/host/src/ble_store_util.c +++ b/nimble/host/src/ble_store_util.c @@ -116,6 +116,14 @@ ble_store_util_delete_peer(const ble_addr_t *peer_id_addr) if (rc != 0) { return rc; } + + memset(&key, 0, sizeof key); + key.feat.peer_addr = *peer_id_addr; + + rc = ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT, &key); + if (rc != 0) { + return rc; + } return 0; } @@ -201,6 +209,7 @@ ble_store_util_status_rr(struct ble_store_status_event *event, void *arg) case BLE_STORE_OBJ_TYPE_OUR_SEC: case BLE_STORE_OBJ_TYPE_PEER_SEC: case BLE_STORE_OBJ_TYPE_DB_HASH: + case BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT: return ble_gap_unpair_oldest_peer(); case BLE_STORE_OBJ_TYPE_CCCD: /* Try unpairing oldest peer except current peer */ diff --git a/nimble/host/store/config/src/ble_store_config.c b/nimble/host/store/config/src/ble_store_config.c index 6469c20353..b3693a8a05 100644 --- a/nimble/host/store/config/src/ble_store_config.c +++ b/nimble/host/store/config/src/ble_store_config.c @@ -53,6 +53,13 @@ struct ble_store_value_db_hash ble_store_config_db_hashes[MYNEWT_VAL(BLE_STORE_M int ble_store_config_num_db_hashes; +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) +struct ble_store_value_cl_sup_feat + ble_store_config_feats[MYNEWT_VAL(BLE_STORE_MAX_BONDS)]; +#endif + +int ble_store_config_num_feats; + /***************************************************************************** * $sec * *****************************************************************************/ @@ -556,6 +563,125 @@ ble_store_config_write_db_hash(const struct ble_store_value_db_hash *value_hash) #endif } +/***************************************************************************** + * $cl_sup_feat * + *****************************************************************************/ + +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) +static int +ble_store_config_find_cl_sup_feat(const struct ble_store_key_cl_sup_feat *key) +{ + struct ble_store_value_cl_sup_feat *feat; + int skipped; + int i; + + skipped = 0; + for (i = 0; i < ble_store_config_num_feats; i++) { + feat = ble_store_config_feats + i; + + if (ble_addr_cmp(&key->peer_addr, BLE_ADDR_ANY)) { + if (ble_addr_cmp(&feat->peer_addr, &key->peer_addr)) { + continue; + } + } + + if (key->idx > skipped) { + skipped++; + continue; + } + + return i; + } + return -1; +} +#endif + +static int +ble_store_config_delete_cl_sup_feat(const struct ble_store_key_cl_sup_feat + *key_feat) +{ +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) + int idx; + int rc; + + idx = ble_store_config_find_cl_sup_feat(key_feat); + if (idx < 0) { + return BLE_HS_ENOENT; + } + + assert(ble_store_config_num_feats < ARRAY_SIZE(ble_store_config_feats)); + rc = ble_store_config_delete_obj(ble_store_config_feats, + sizeof *ble_store_config_feats, + idx, + &ble_store_config_num_feats); + if (rc != 0) { + return rc; + } + + rc = ble_store_config_persist_feats(); + if (rc != 0) { + return rc; + } + return 0; +#else + return BLE_HS_ENOENT; +#endif +} + +static int +ble_store_config_read_cl_sup_feat(const struct ble_store_key_cl_sup_feat + *key_feat, + struct ble_store_value_cl_sup_feat + *value_feat) +{ +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) + int idx; + + idx = ble_store_config_find_cl_sup_feat(key_feat); + if (idx == -1) { + return BLE_HS_ENOENT; + } + + *value_feat = ble_store_config_feats[idx]; + return 0; +#else + return BLE_HS_ENOENT; +#endif +} + +static int +ble_store_config_write_cl_sup(const struct ble_store_value_cl_sup_feat + *value_feat) +{ +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) + struct ble_store_key_cl_sup_feat key_feat; + int idx; + int rc; + + ble_store_key_from_value_peer_cl_sup_feat(&key_feat, value_feat); + idx = ble_store_config_find_cl_sup_feat(&key_feat); + if (idx == -1) { + if (ble_store_config_num_feats >= MYNEWT_VAL(BLE_STORE_MAX_BONDS)) { + return BLE_HS_ESTORE_CAP; + } + + idx = ble_store_config_num_feats; + ble_store_config_num_feats++; + } + + ble_store_config_feats[idx] = *value_feat; + + rc = ble_store_config_persist_feats(); + if (rc != 0) { + return rc; + } + + return 0; +#else + return BLE_HS_ENOENT; +#endif +} + /***************************************************************************** * $api * *****************************************************************************/ @@ -601,6 +727,10 @@ ble_store_config_read(int obj_type, const union ble_store_key *key, case BLE_STORE_OBJ_TYPE_DB_HASH: rc = ble_store_config_read_db_hash(&key->db_hash, &value->db_hash); return rc; + + case BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT: + rc = ble_store_config_read_cl_sup_feat(&key->feat, &value->feat); + return rc; default: return BLE_HS_ENOTSUP; @@ -634,6 +764,10 @@ ble_store_config_write(int obj_type, const union ble_store_value *val) case BLE_STORE_OBJ_TYPE_DB_HASH: rc = ble_store_config_write_db_hash(&val->db_hash); return rc; + + case BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT: + rc = ble_store_config_write_cl_sup(&val->feat); + return rc; default: return BLE_HS_ENOTSUP; @@ -661,6 +795,10 @@ ble_store_config_delete(int obj_type, const union ble_store_key *key) case BLE_STORE_OBJ_TYPE_DB_HASH: rc = ble_store_config_delete_hash(&key->db_hash); return rc; + + case BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT: + rc = ble_store_config_delete_cl_sup_feat(&key->feat); + return rc; default: return BLE_HS_ENOTSUP; @@ -682,6 +820,7 @@ ble_store_config_init(void) ble_store_config_num_peer_secs = 0; ble_store_config_num_cccds = 0; ble_store_config_num_db_hashes = 0; + ble_store_config_num_feats = 0; ble_store_config_conf_init(); } 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 4090b6020b..e40efaf81a 100644 --- a/nimble/host/store/config/src/ble_store_config_conf.c +++ b/nimble/host/store/config/src/ble_store_config_conf.c @@ -57,6 +57,12 @@ static struct conf_handler ble_store_config_conf_handler = { #define BLE_STORE_CONFIG_CCCD_SET_ENCODE_SZ \ (MYNEWT_VAL(BLE_STORE_MAX_CCCDS) * BLE_STORE_CONFIG_CCCD_ENCODE_SZ + 1) +#define BLE_STORE_CONFIG_CL_SUP_FEAT_ENCODE_SZ \ + BASE64_ENCODE_SIZE(sizeof (struct ble_store_value_cl_sup_feat)) + +#define BLE_STORE_CONFIG_CL_SUP_FEAT_SET_ENCODE_SZ \ + (MYNEWT_VAL(BLE_STORE_MAX_BONDS) * BLE_STORE_CONFIG_CL_SUP_FEAT_ENCODE_SZ + 1) + static void ble_store_config_serialize_arr(const void *arr, int obj_sz, int num_objs, char *out_buf, int buf_sz) @@ -118,6 +124,13 @@ ble_store_config_conf_set(int argc, char **argv, char *val) sizeof *ble_store_config_cccds, &ble_store_config_num_cccds); return rc; + } else if (strcmp(argv[0], "feat") == 0) { + rc = ble_store_config_deserialize_arr( + val, + ble_store_config_feats, + sizeof *ble_store_config_feats, + &ble_store_config_num_feats); + return rc; } } return OS_ENOENT; @@ -130,6 +143,7 @@ ble_store_config_conf_export(void (*func)(char *name, char *val), union { char sec[BLE_STORE_CONFIG_SEC_SET_ENCODE_SZ]; char cccd[BLE_STORE_CONFIG_CCCD_SET_ENCODE_SZ]; + char feat[BLE_STORE_CONFIG_CL_SUP_FEAT_SET_ENCODE_SZ]; } buf; ble_store_config_serialize_arr(ble_store_config_our_secs, @@ -153,6 +167,13 @@ ble_store_config_conf_export(void (*func)(char *name, char *val), sizeof buf.cccd); func("ble_hs/cccd", buf.cccd); + ble_store_config_serialize_arr(ble_store_config_feats, + sizeof *ble_store_config_feats, + ble_store_config_num_feats, + buf.feat, + sizeof buf.feat); + func("ble_hs/feat", buf.feat); + return 0; } @@ -223,6 +244,25 @@ ble_store_config_persist_cccds(void) return 0; } +int +ble_store_config_persist_feats(void) +{ + char buf[BLE_STORE_CONFIG_CL_SUP_FEAT_SET_ENCODE_SZ]; + int rc; + + ble_store_config_serialize_arr(ble_store_config_feats, + sizeof *ble_store_config_feats, + ble_store_config_num_feats, + buf, + sizeof buf); + rc = conf_save_one("ble_hs/feat", buf); + if (rc != 0) { + return BLE_HS_ESTORE_FAIL; + } + + return 0; +} + void ble_store_config_conf_init(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 7ee751f83c..a1f56a84e9 100644 --- a/nimble/host/store/config/src/ble_store_config_priv.h +++ b/nimble/host/store/config/src/ble_store_config_priv.h @@ -39,12 +39,17 @@ extern int ble_store_config_num_cccds; extern struct ble_store_value_db_hash ble_store_config_db_hashes[MYNEWT_VAL(BLE_STORE_MAX_BONDS)]; extern int ble_store_config_num_db_hashes; +extern struct ble_store_value_cl_sup_feat + ble_store_config_feats[MYNEWT_VAL(BLE_STORE_MAX_BONDS)]; +extern int ble_store_config_num_feats; + #if MYNEWT_VAL(BLE_STORE_CONFIG_PERSIST) int ble_store_config_persist_our_secs(void); int ble_store_config_persist_peer_secs(void); int ble_store_config_persist_cccds(void); int ble_store_config_persist_db_hash(void); +int ble_store_config_persist_feats(void); void ble_store_config_conf_init(void); #else @@ -53,7 +58,7 @@ static inline int ble_store_config_persist_our_secs(void) { return 0; } static inline int ble_store_config_persist_peer_secs(void) { return 0; } static inline int ble_store_config_persist_cccds(void) { return 0; } static inline int -ble_store_config_persist_db_hash(void) +static inline int ble_store_config_persist_feats(void) { return 0; } { return 0; } diff --git a/nimble/host/store/ram/src/ble_store_ram.c b/nimble/host/store/ram/src/ble_store_ram.c index ace1ea69ae..355d1c110d 100644 --- a/nimble/host/store/ram/src/ble_store_ram.c +++ b/nimble/host/store/ram/src/ble_store_ram.c @@ -24,8 +24,8 @@ */ /* This package has been deprecated and you should - * use the store/config package. For a RAM-only BLE store, - * use store/config and set BLE_STORE_CONFIG_PERSIST to 0. + * use the store/ram package. For a RAM-only BLE store, + * use store/ram and set BLE_STORE_ram_PERSIST to 0. */ #include @@ -63,6 +63,12 @@ static struct ble_store_value_db_hash ble_store_ram_db_hashes[MYNEWT_VAL(BLE_STO static int ble_store_ram_num_db_hashes; +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) +static struct ble_store_value_cl_sup_feat + ble_store_ram_feats[MYNEWT_VAL(BLE_STORE_MAX_BONDS)]; +#endif + +static int ble_store_ram_num_feats; /***************************************************************************** * $sec * *****************************************************************************/ @@ -535,6 +541,112 @@ ble_store_ram_write_db_hash(const struct ble_store_value_db_hash *value_hash) #endif } +/***************************************************************************** + * $cl_sup_feat * + *****************************************************************************/ + +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) +static int +ble_store_ram_find_cl_sup_feat(const struct ble_store_key_cl_sup_feat *key) +{ + struct ble_store_value_cl_sup_feat *feat; + int skipped; + int i; + + skipped = 0; + for (i = 0; i < ble_store_ram_num_feats; i++) { + feat = ble_store_ram_feats + i; + + if (ble_addr_cmp(&key->peer_addr, BLE_ADDR_ANY)) { + if (ble_addr_cmp(&feat->peer_addr, &key->peer_addr)) { + continue; + } + } + + if (key->idx > skipped) { + skipped++; + continue; + } + + return i; + } + return -1; +} +#endif + +static int +ble_store_ram_delete_cl_sup_feat(const struct ble_store_key_cl_sup_feat + *key_feat) +{ +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) + int idx; + int rc; + + idx = ble_store_ram_find_cl_sup_feat(key_feat); + if (idx < 0) { + return BLE_HS_ENOENT; + } + + rc = ble_store_ram_delete_obj(ble_store_ram_feats, + sizeof *ble_store_ram_feats, + idx, + &ble_store_ram_num_feats); + if (rc != 0) { + return rc; + } + + return 0; +#else + return BLE_HS_ENOENT; +#endif +} + +static int +ble_store_ram_read_cl_sup_feat(const struct ble_store_key_cl_sup_feat + *key_feat, + struct ble_store_value_cl_sup_feat *value_feat) +{ +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) + int idx; + + idx = ble_store_ram_find_cl_sup_feat(key_feat); + if (idx == -1) { + return BLE_HS_ENOENT; + } + + *value_feat = ble_store_ram_feats[idx]; + return 0; +#else + return BLE_HS_ENOENT; +#endif +} + +static int +ble_store_ram_write_cl_sup(const struct ble_store_value_cl_sup_feat + *value_feat) +{ +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) + struct ble_store_key_cl_sup_feat key_feat; + int idx; + + ble_store_key_from_value_peer_cl_sup_feat(&key_feat, value_feat); + idx = ble_store_ram_find_cl_sup_feat(&key_feat); + if (idx == -1) { + if (ble_store_ram_num_feats >= MYNEWT_VAL(BLE_STORE_MAX_BONDS)) { + return BLE_HS_ESTORE_CAP; + } + + idx = ble_store_ram_num_feats; + ble_store_ram_num_feats++; + } + + ble_store_ram_feats[idx] = *value_feat; + return 0; +#else + return BLE_HS_ENOENT; +#endif +} + /***************************************************************************** * $api * *****************************************************************************/ @@ -580,6 +692,11 @@ ble_store_ram_read(int obj_type, const union ble_store_key *key, case BLE_STORE_OBJ_TYPE_DB_HASH: rc = ble_store_ram_read_db_hash(&key->db_hash, &value->db_hash); return rc; + + case BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT: + rc = ble_store_ram_read_cl_sup_feat(&key->feat, &value->feat); + return rc; + default: return BLE_HS_ENOTSUP; @@ -613,6 +730,10 @@ ble_store_ram_write(int obj_type, const union ble_store_value *val) case BLE_STORE_OBJ_TYPE_DB_HASH: rc = ble_store_ram_write_db_hash(&val->db_hash); return rc; + + case BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT: + rc = ble_store_ram_write_cl_sup(&val->feat); + return rc; default: return BLE_HS_ENOTSUP; @@ -640,6 +761,10 @@ ble_store_ram_delete(int obj_type, const union ble_store_key *key) case BLE_STORE_OBJ_TYPE_DB_HASH: rc = ble_store_ram_delete_hash(&key->db_hash); return rc; + + case BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT: + rc = ble_store_ram_delete_cl_sup_feat(&key->feat); + return rc; default: return BLE_HS_ENOTSUP; @@ -661,4 +786,5 @@ ble_store_ram_init(void) ble_store_ram_num_peer_secs = 0; ble_store_ram_num_cccds = 0; ble_store_ram_num_db_hashes = 0; + ble_store_ram_num_feats = 0; } From 7912f9da524e19030e02cffc3703d2a6f9f04bea Mon Sep 17 00:00:00 2001 From: Piotr Narajowski Date: Tue, 26 Aug 2025 11:21:10 +0200 Subject: [PATCH 3/6] nimble/host: store client supported features value This adds support for storing client supported features for clients with a trusted relationship. Fixes Host qualification test case GATT/SR/GAS/BV-04-C --- nimble/host/src/ble_gatts.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/nimble/host/src/ble_gatts.c b/nimble/host/src/ble_gatts.c index d9085aba2e..d83077e954 100644 --- a/nimble/host/src/ble_gatts.c +++ b/nimble/host/src/ble_gatts.c @@ -1718,6 +1718,8 @@ int ble_gatts_peer_cl_sup_feat_get(uint16_t conn_handle, uint8_t *out_supported_feat, uint8_t len) { struct ble_hs_conn *conn; + struct ble_store_key_cl_sup_feat feat_key; + struct ble_store_value_cl_sup_feat feat_val; int rc = 0; if (out_supported_feat == NULL) { @@ -1735,8 +1737,16 @@ ble_gatts_peer_cl_sup_feat_get(uint16_t conn_handle, uint8_t *out_supported_feat len = BLE_GATT_CHR_CLI_SUP_FEAT_SZ; } - memcpy(out_supported_feat, conn->bhc_gatt_svr.peer_cl_sup_feat, - sizeof(uint8_t) * len); + if (conn->bhc_sec_state.bonded) { + feat_key.peer_addr = conn->bhc_peer_addr; + feat_key.idx = 0; + ble_store_read_peer_cl_sup_feat(&feat_key, &feat_val); + memcpy(out_supported_feat, feat_val.peer_cl_sup_feat, + sizeof(uint8_t) * len); + } else { + memcpy(out_supported_feat, conn->bhc_gatt_svr.peer_cl_sup_feat, + sizeof(uint8_t) * len); + } done: ble_hs_unlock(); @@ -1747,6 +1757,9 @@ int ble_gatts_peer_cl_sup_feat_update(uint16_t conn_handle, struct os_mbuf *om) { struct ble_hs_conn *conn; + struct ble_store_value_cl_sup_feat store_feat; + struct ble_store_key_cl_sup_feat feat_key; + struct ble_store_value_cl_sup_feat feat_val; uint8_t feat[BLE_GATT_CHR_CLI_SUP_FEAT_SZ] = {}; uint16_t len; int rc = 0; @@ -1800,7 +1813,7 @@ ble_gatts_peer_cl_sup_feat_update(uint16_t conn_handle, struct os_mbuf *om) store_feat.peer_addr = conn->bhc_peer_addr; memcpy(store_feat.peer_cl_sup_feat, feat, BLE_GATT_CHR_CLI_SUP_FEAT_SZ); if (ble_store_write_peer_cl_sup_feat(&store_feat)) { - /* XXX: How to report an error? */ + /* XXX: How should this error get reported? */ } rc = 0; goto done; From 17d7a869b1d593d2df4549daa855a7fd45fbc04e Mon Sep 17 00:00:00 2001 From: Piotr Narajowski Date: Wed, 3 Sep 2025 13:06:30 +0200 Subject: [PATCH 4/6] nimble/host: move BLE_GATT_CHR_CLI_SUP_FEAT_SZ to public header Move BLE_GATT_CHR_CLI_SUP_FEAT_SZ to public header. --- nimble/host/include/host/ble_gatt.h | 3 +++ nimble/host/src/ble_gatt_priv.h | 5 ----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/nimble/host/include/host/ble_gatt.h b/nimble/host/include/host/ble_gatt.h index 0ba3d26e09..083f09eba9 100644 --- a/nimble/host/include/host/ble_gatt.h +++ b/nimble/host/include/host/ble_gatt.h @@ -201,6 +201,9 @@ struct ble_hs_cfg; /** @} */ +/** Client supported features size. For now only 3 bits in first octet are defined */ +#define BLE_GATT_CHR_CLI_SUP_FEAT_SZ 1 + /*** @client. */ /** Represents a GATT error. */ struct ble_gatt_error { diff --git a/nimble/host/src/ble_gatt_priv.h b/nimble/host/src/ble_gatt_priv.h index 4f277c145d..b3cce83306 100644 --- a/nimble/host/src/ble_gatt_priv.h +++ b/nimble/host/src/ble_gatt_priv.h @@ -88,11 +88,6 @@ extern STATS_SECT_DECL(ble_gatts_stats) ble_gatts_stats; #define BLE_GATT_CHR_DECL_SZ_16 5 #define BLE_GATT_CHR_DECL_SZ_128 19 -#define BLE_GATT_CHR_CLI_SUP_FEAT_SZ 1 -/** - * For now only 3 bits in first octet are defined - * - */ #define BLE_GATT_CHR_CLI_SUP_FEAT_MASK 7 #define BLE_GATT_DB_HASH_SZ 16 From 50e09b7b9a7b9f2d688a6f9a4a62513c30fa5def Mon Sep 17 00:00:00 2001 From: Piotr Narajowski Date: Wed, 27 Aug 2025 09:37:26 +0200 Subject: [PATCH 5/6] nimble/host: update storage unit tests Extend storage unit tests to cover new object type PEER_CL_SUP_FEAT --- nimble/host/test/src/ble_store_test.c | 71 +++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/nimble/host/test/src/ble_store_test.c b/nimble/host/test/src/ble_store_test.c index 75d3a490f8..9939bc2e0f 100644 --- a/nimble/host/test/src/ble_store_test.c +++ b/nimble/host/test/src/ble_store_test.c @@ -45,6 +45,11 @@ ble_store_test_util_verify_peer_deleted(const ble_addr_t *addr) rc = ble_store_read(BLE_STORE_OBJ_TYPE_CCCD, &key, &value); TEST_ASSERT(rc == BLE_HS_ENOENT); + memset(&key, 0, sizeof key); + key.feat.peer_addr = *addr; + rc = ble_store_read(BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT, &key, &value); + TEST_ASSERT(rc == BLE_HS_ENOENT); + rc = ble_store_util_bonded_peers(addrs, &num_addrs, sizeof addrs / sizeof addrs[0]); TEST_ASSERT_FATAL(rc == 0); @@ -198,6 +203,16 @@ TEST_CASE_SELF(ble_store_test_delete_peer) .chr_val_handle = 5, }, }; + struct ble_store_value_cl_sup_feat feats[2] = { + { + .peer_addr = secs[0].peer_addr, + .peer_cl_sup_feat[0] = 0, + }, + { + .peer_addr = secs[1].peer_addr, + .peer_cl_sup_feat[0] = 0, + }, + }; union ble_store_value value; union ble_store_key key; int count; @@ -218,6 +233,11 @@ TEST_CASE_SELF(ble_store_test_delete_peer) TEST_ASSERT_FATAL(rc == 0); } + for (i = 0; i < sizeof feats / sizeof feats[0]; i++) { + rc = ble_store_write_peer_cl_sup_feat(feats + i); + TEST_ASSERT_FATAL(rc == 0); + } + /* Delete first peer. */ rc = ble_store_util_delete_peer(&secs[0].peer_addr); TEST_ASSERT_FATAL(rc == 0); @@ -254,6 +274,16 @@ TEST_CASE_SELF(ble_store_test_delete_peer) TEST_ASSERT_FATAL(rc == 0); TEST_ASSERT(memcmp(&value.cccd, cccds + 2, sizeof value.cccd) == 0); + ble_store_key_from_value_peer_cl_sup_feat(&key.feat, feats + 1); + + rc = ble_store_util_count(BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT, &count); + TEST_ASSERT_FATAL(rc == 0); + TEST_ASSERT(count == 1); + + rc = ble_store_read_peer_cl_sup_feat(&key.feat, &value.feat); + TEST_ASSERT_FATAL(rc == 0); + TEST_ASSERT(memcmp(&value.feat, feats + 1, sizeof value.feat) == 0); + /* Delete second peer. */ rc = ble_store_util_delete_peer(&secs[1].peer_addr); TEST_ASSERT_FATAL(rc == 0); @@ -294,6 +324,16 @@ TEST_CASE_SELF(ble_store_test_count) .chr_val_handle = 8, }, }; + struct ble_store_value_cl_sup_feat feats[2] = { + { + .peer_addr = secs[0].peer_addr, + .peer_cl_sup_feat[0] = 0, + }, + { + .peer_addr = secs[1].peer_addr, + .peer_cl_sup_feat[0] = 0, + }, + }; int count; int rc; int i; @@ -314,6 +354,10 @@ TEST_CASE_SELF(ble_store_test_count) TEST_ASSERT_FATAL(rc == 0); TEST_ASSERT(count == 0); + rc = ble_store_util_count(BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT, &count); + TEST_ASSERT_FATAL(rc == 0); + TEST_ASSERT(count == 0); + /* Write some test data. */ for (i = 0; i < 3; i++) { @@ -328,6 +372,10 @@ TEST_CASE_SELF(ble_store_test_count) rc = ble_store_write_cccd(cccds + i); TEST_ASSERT_FATAL(rc == 0); } + for (i = 0; i < 1; i++) { + rc = ble_store_write_peer_cl_sup_feat(feats + i); + TEST_ASSERT_FATAL(rc == 0); + } /*** Verify counts after populating store. */ rc = ble_store_util_count(BLE_STORE_OBJ_TYPE_OUR_SEC, &count); @@ -342,6 +390,10 @@ TEST_CASE_SELF(ble_store_test_count) TEST_ASSERT_FATAL(rc == 0); TEST_ASSERT(count == 1); + rc = ble_store_util_count(BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT, &count); + TEST_ASSERT_FATAL(rc == 0); + TEST_ASSERT(count == 1); + ble_hs_test_util_assert_mbufs_freed(NULL); } @@ -383,6 +435,16 @@ TEST_CASE_SELF(ble_store_test_clear) .chr_val_handle = 5, }, }; + struct ble_store_value_cl_sup_feat feats[2] = { + { + .peer_addr = secs[0].peer_addr, + .peer_cl_sup_feat[0] = 0, + }, + { + .peer_addr = secs[1].peer_addr, + .peer_cl_sup_feat[0] = 0, + }, + }; int rc; int i; @@ -400,6 +462,11 @@ TEST_CASE_SELF(ble_store_test_clear) TEST_ASSERT_FATAL(rc == 0); } + for (i = 0; i < sizeof feats / sizeof feats[0]; i++) { + rc = ble_store_write_peer_cl_sup_feat(feats + i); + TEST_ASSERT_FATAL(rc == 0); + } + /* Sanity check. */ TEST_ASSERT_FATAL( ble_store_test_util_count(BLE_STORE_OBJ_TYPE_OUR_SEC) == 2); @@ -407,6 +474,8 @@ TEST_CASE_SELF(ble_store_test_clear) ble_store_test_util_count(BLE_STORE_OBJ_TYPE_PEER_SEC) == 2); TEST_ASSERT_FATAL( ble_store_test_util_count(BLE_STORE_OBJ_TYPE_CCCD) == 3); + TEST_ASSERT_FATAL( + ble_store_test_util_count(BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT) == 2); /* Ensure store is empty after clear gets called. */ rc = ble_store_clear(); @@ -414,6 +483,7 @@ TEST_CASE_SELF(ble_store_test_clear) TEST_ASSERT(ble_store_test_util_count(BLE_STORE_OBJ_TYPE_OUR_SEC) == 0); TEST_ASSERT(ble_store_test_util_count(BLE_STORE_OBJ_TYPE_PEER_SEC) == 0); TEST_ASSERT(ble_store_test_util_count(BLE_STORE_OBJ_TYPE_CCCD) == 0); + TEST_ASSERT(ble_store_test_util_count(BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT) == 0); /* Ensure second clear succeeds with no effect. */ rc = ble_store_clear(); @@ -421,6 +491,7 @@ TEST_CASE_SELF(ble_store_test_clear) TEST_ASSERT(ble_store_test_util_count(BLE_STORE_OBJ_TYPE_OUR_SEC) == 0); TEST_ASSERT(ble_store_test_util_count(BLE_STORE_OBJ_TYPE_PEER_SEC) == 0); TEST_ASSERT(ble_store_test_util_count(BLE_STORE_OBJ_TYPE_CCCD) == 0); + TEST_ASSERT(ble_store_test_util_count(BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT) == 0); ble_hs_test_util_assert_mbufs_freed(NULL); } From cd2e0bb7f10c8756565a5fb1c082bea98c176f16 Mon Sep 17 00:00:00 2001 From: Piotr Narajowski Date: Thu, 4 Sep 2025 14:47:27 +0200 Subject: [PATCH 6/6] nimble/host: update storage unit tests Extend storage unit tests to test new object type DB_HASH. --- nimble/host/test/src/ble_store_test.c | 72 +++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/nimble/host/test/src/ble_store_test.c b/nimble/host/test/src/ble_store_test.c index 9939bc2e0f..60a8b42154 100644 --- a/nimble/host/test/src/ble_store_test.c +++ b/nimble/host/test/src/ble_store_test.c @@ -22,6 +22,8 @@ #include "ble_hs_test_util.h" static struct ble_store_status_event ble_store_test_status_event; +uint8_t database_hash[16] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; static void ble_store_test_util_verify_peer_deleted(const ble_addr_t *addr) @@ -50,6 +52,11 @@ ble_store_test_util_verify_peer_deleted(const ble_addr_t *addr) rc = ble_store_read(BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT, &key, &value); TEST_ASSERT(rc == BLE_HS_ENOENT); + memset(&key, 0, sizeof key); + key.feat.peer_addr = *addr; + rc = ble_store_read(BLE_STORE_OBJ_TYPE_DB_HASH, &key, &value); + TEST_ASSERT(rc == BLE_HS_ENOENT); + rc = ble_store_util_bonded_peers(addrs, &num_addrs, sizeof addrs / sizeof addrs[0]); TEST_ASSERT_FATAL(rc == 0); @@ -213,6 +220,16 @@ TEST_CASE_SELF(ble_store_test_delete_peer) .peer_cl_sup_feat[0] = 0, }, }; + struct ble_store_value_db_hash hashes[2] = { + { + .peer_addr = secs[0].peer_addr, + .db_hash = database_hash, + }, + { + .peer_addr = secs[1].peer_addr, + .db_hash = database_hash, + }, + }; union ble_store_value value; union ble_store_key key; int count; @@ -238,6 +255,11 @@ TEST_CASE_SELF(ble_store_test_delete_peer) TEST_ASSERT_FATAL(rc == 0); } + for (i = 0; i < sizeof hashes / sizeof hashes[0]; i++) { + rc = ble_store_write_db_hash(hashes + i); + TEST_ASSERT_FATAL(rc == 0); + } + /* Delete first peer. */ rc = ble_store_util_delete_peer(&secs[0].peer_addr); TEST_ASSERT_FATAL(rc == 0); @@ -284,6 +306,16 @@ TEST_CASE_SELF(ble_store_test_delete_peer) TEST_ASSERT_FATAL(rc == 0); TEST_ASSERT(memcmp(&value.feat, feats + 1, sizeof value.feat) == 0); + ble_store_key_from_value_db_hash(&key.db_hash, hashes + 1); + + rc = ble_store_util_count(BLE_STORE_OBJ_TYPE_DB_HASH, &count); + TEST_ASSERT_FATAL(rc == 0); + TEST_ASSERT(count == 1); + + rc = ble_store_read_db_hash(&key.db_hash, &value.db_hash); + TEST_ASSERT_FATAL(rc == 0); + TEST_ASSERT(memcmp(&value.db_hash, hashes + 1, sizeof value.db_hash) == 0); + /* Delete second peer. */ rc = ble_store_util_delete_peer(&secs[1].peer_addr); TEST_ASSERT_FATAL(rc == 0); @@ -334,6 +366,16 @@ TEST_CASE_SELF(ble_store_test_count) .peer_cl_sup_feat[0] = 0, }, }; + struct ble_store_value_db_hash hashes[2] = { + { + .peer_addr = secs[0].peer_addr, + .db_hash = database_hash, + }, + { + .peer_addr = secs[1].peer_addr, + .db_hash = database_hash, + }, + }; int count; int rc; int i; @@ -358,6 +400,10 @@ TEST_CASE_SELF(ble_store_test_count) TEST_ASSERT_FATAL(rc == 0); TEST_ASSERT(count == 0); + rc = ble_store_util_count(BLE_STORE_OBJ_TYPE_DB_HASH, &count); + TEST_ASSERT_FATAL(rc == 0); + TEST_ASSERT(count == 0); + /* Write some test data. */ for (i = 0; i < 3; i++) { @@ -376,6 +422,10 @@ TEST_CASE_SELF(ble_store_test_count) rc = ble_store_write_peer_cl_sup_feat(feats + i); TEST_ASSERT_FATAL(rc == 0); } + for (i = 0; i < 1; i++) { + rc = ble_store_write_db_hash(hashes + i); + TEST_ASSERT_FATAL(rc == 0); + } /*** Verify counts after populating store. */ rc = ble_store_util_count(BLE_STORE_OBJ_TYPE_OUR_SEC, &count); @@ -394,6 +444,10 @@ TEST_CASE_SELF(ble_store_test_count) TEST_ASSERT_FATAL(rc == 0); TEST_ASSERT(count == 1); + rc = ble_store_util_count(BLE_STORE_OBJ_TYPE_DB_HASH, &count); + TEST_ASSERT_FATAL(rc == 0); + TEST_ASSERT(count == 1); + ble_hs_test_util_assert_mbufs_freed(NULL); } @@ -445,6 +499,16 @@ TEST_CASE_SELF(ble_store_test_clear) .peer_cl_sup_feat[0] = 0, }, }; + struct ble_store_value_db_hash hashes[2] = { + { + .peer_addr = secs[0].peer_addr, + .db_hash = database_hash, + }, + { + .peer_addr = secs[1].peer_addr, + .db_hash = database_hash, + }, + }; int rc; int i; @@ -467,6 +531,11 @@ TEST_CASE_SELF(ble_store_test_clear) TEST_ASSERT_FATAL(rc == 0); } + for (i = 0; i < sizeof hashes / sizeof hashes[0]; i++) { + rc = ble_store_write_db_hash(hashes + i); + TEST_ASSERT_FATAL(rc == 0); + } + /* Sanity check. */ TEST_ASSERT_FATAL( ble_store_test_util_count(BLE_STORE_OBJ_TYPE_OUR_SEC) == 2); @@ -476,6 +545,7 @@ TEST_CASE_SELF(ble_store_test_clear) ble_store_test_util_count(BLE_STORE_OBJ_TYPE_CCCD) == 3); TEST_ASSERT_FATAL( ble_store_test_util_count(BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT) == 2); + TEST_ASSERT_FATAL(ble_store_test_util_count(BLE_STORE_OBJ_TYPE_DB_HASH) == 2); /* Ensure store is empty after clear gets called. */ rc = ble_store_clear(); @@ -484,6 +554,7 @@ TEST_CASE_SELF(ble_store_test_clear) TEST_ASSERT(ble_store_test_util_count(BLE_STORE_OBJ_TYPE_PEER_SEC) == 0); TEST_ASSERT(ble_store_test_util_count(BLE_STORE_OBJ_TYPE_CCCD) == 0); TEST_ASSERT(ble_store_test_util_count(BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT) == 0); + TEST_ASSERT(ble_store_test_util_count(BLE_STORE_OBJ_TYPE_DB_HASH) == 0); /* Ensure second clear succeeds with no effect. */ rc = ble_store_clear(); @@ -492,6 +563,7 @@ TEST_CASE_SELF(ble_store_test_clear) TEST_ASSERT(ble_store_test_util_count(BLE_STORE_OBJ_TYPE_PEER_SEC) == 0); TEST_ASSERT(ble_store_test_util_count(BLE_STORE_OBJ_TYPE_CCCD) == 0); TEST_ASSERT(ble_store_test_util_count(BLE_STORE_OBJ_TYPE_PEER_CL_SUP_FEAT) == 0); + TEST_ASSERT(ble_store_test_util_count(BLE_STORE_OBJ_TYPE_DB_HASH) == 0); ble_hs_test_util_assert_mbufs_freed(NULL); }