diff --git a/nimble/host/include/host/ble_att.h b/nimble/host/include/host/ble_att.h index 8323c9d764..1c7ccc4ef8 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_DATABASE_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 e74ba9e1a1..a534346b61 100644 --- a/nimble/host/include/host/ble_gatt.h +++ b/nimble/host/include/host/ble_gatt.h @@ -62,12 +62,27 @@ struct ble_hs_cfg; /** GATT service 16-bit UUID. */ #define BLE_GATT_SVC_UUID16 0x1801 +/** GATT Service Changed characteristic 16-bit UUID. */ +#define BLE_GATT_CHR_SVC_CHANGED_UUID16 0x2a05 + /** GATT Client Characteristic Configuration descriptor 16-bit UUID. */ #define BLE_GATT_DSC_CLT_CFG_UUID16 0x2902 /** GATT Characteristic Extended Porperties descriptor 16-bit UUID. */ #define BLE_GATT_DSC_EXT_PROP_UUID16 0x2900 +/** GATT Characteristic User Description descriptor 16-bit UUID. */ +#define BLE_GATT_DSC_USER_DESC_UUID16 0x2901 + +/** GATT Server Characteristic Configuration descriptor 16-bit UUID. */ +#define BLE_GATT_DSC_SRV_CFG_UUID16 0x2903 + +/** GATT Characteristic Presentation Format descriptor 16-bit UUID. */ +#define BLE_GATT_DSC_CHR_FMT_UUID16 0x2904 + +/** GATT Characteristic Aggregate Format descriptor 16-bit UUID. */ +#define BLE_GATT_DSC_AGG_FMT_UUID16 0x2905 + /** @} */ /** @@ -1139,6 +1154,18 @@ typedef void (*ble_gatt_svc_foreach_fn)(const struct ble_gatt_svc_def *svc, */ void ble_gatts_show_local(void); +/** + * Calculates the database hash of the local GATT database (AES-CMAC over + * the attribute database; Core spec Vol 3, Part G, 7.3.1). Only available + * if the BLE_GATT_CACHING syscfg setting is enabled. + * + * @param out_hash_key Buffer to fill with the 16-byte database + * hash, in little-endian byte order. + * + * @return 0 on success; nonzero on failure. + */ +int ble_gatts_calculate_hash(uint8_t *out_hash_key); + /** * Resets the GATT server to its initial state. On success, this function * removes all supported services, characteristics, and descriptors. This diff --git a/nimble/host/include/host/ble_store.h b/nimble/host/include/host/ble_store.h index 83b1c4f15f..f396cedde3 100644 --- a/nimble/host/include/host/ble_store.h +++ b/nimble/host/include/host/ble_store.h @@ -48,6 +48,12 @@ extern "C" { /** Object type: Client Characteristic Configuration Descriptor. */ #define BLE_STORE_OBJ_TYPE_CCCD 3 +/** Object type: Client Supported Features characteristic value. */ +#define BLE_STORE_OBJ_TYPE_CSFC 4 + +/** Object type: local GATT database hash. */ +#define BLE_STORE_OBJ_TYPE_DB_HASH 5 + /** @} */ /** @@ -154,6 +160,57 @@ struct ble_store_value_cccd { unsigned value_changed:1; }; +/** Size of the stored Client Supported Features value, in bytes. */ +#define BLE_STORE_CSFC_SZ 1 + +/** + * Used as a key for lookups of stored Client Supported Features characteristic + * (CSFC) values. This struct corresponds to the BLE_STORE_OBJ_TYPE_CSFC store + * object type. + */ +struct ble_store_key_csfc { + /** + * 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 characteristic (CSFC) value + * along with the associated GATT caching state. This struct corresponds to + * the BLE_STORE_OBJ_TYPE_CSFC store object type. + */ +struct ble_store_value_csfc { + /** The peer address associated with the stored value. */ + ble_addr_t peer_addr; + /** The Client Supported Features bits written by the peer. */ + uint8_t csfc[BLE_STORE_CSFC_SZ]; + /** Flag indicating whether the peer is change-aware. */ + unsigned change_aware:1; +}; + +/** + * Used as a key for lookups of the stored local GATT database hash. This + * struct corresponds to the BLE_STORE_OBJ_TYPE_DB_HASH store object type. + */ +struct ble_store_key_db_hash { + /** Number of results to skip; 0 means retrieve the first match. */ + uint8_t idx; +}; + +/** + * Represents the stored local GATT database hash. This struct corresponds to + * the BLE_STORE_OBJ_TYPE_DB_HASH store object type. + */ +struct ble_store_value_db_hash { + /** The database hash, in little-endian byte order. */ + uint8_t hash[16]; +}; + /** * 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 +220,10 @@ 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 Client Supported Features store lookups. */ + struct ble_store_key_csfc csfc; + /** Key for local GATT database hash store lookups. */ + struct ble_store_key_db_hash db_hash; }; /** @@ -174,6 +235,10 @@ union ble_store_value { struct ble_store_value_sec sec; /** Stored Client Characteristic Configuration Descriptor. */ struct ble_store_value_cccd cccd; + /** Stored Client Supported Features value. */ + struct ble_store_value_csfc csfc; + /** Stored local GATT database hash. */ + struct ble_store_value_db_hash db_hash; }; /** Represents an event associated with the BLE Store. */ @@ -556,6 +621,53 @@ int ble_store_write_cccd(const struct ble_store_value_cccd *value); */ int ble_store_delete_cccd(const struct ble_store_key_cccd *key); +/** + * Reads a Client Supported Features characteristic (CSFC) value from storage. + * + * @param key The key identifying the CSFC value to read. + * @param out_value On success, filled with the stored value. + * + * @return 0 on success; nonzero on error. + */ +int ble_store_read_csfc(const struct ble_store_key_csfc *key, + struct ble_store_value_csfc *out_value); + +/** + * Writes a Client Supported Features characteristic (CSFC) value to storage. + * + * @param value The value to persist. + * + * @return 0 on success; nonzero on error. + */ +int ble_store_write_csfc(const struct ble_store_value_csfc *value); + +/** + * Deletes a Client Supported Features characteristic (CSFC) value from + * storage. + * + * @param key The key identifying the CSFC value to delete. + * + * @return 0 on success; nonzero on error. + */ +int ble_store_delete_csfc(const struct ble_store_key_csfc *key); + +/** + * Reads the stored local GATT database hash. + * + * @param out_value On success, filled with the stored hash. + * + * @return 0 on success; nonzero on error. + */ +int ble_store_read_db_hash(struct ble_store_value_db_hash *out_value); + +/** + * Writes the local GATT database hash to storage. + * + * @param value The hash to persist. + * + * @return 0 on success; nonzero on error. + */ +int ble_store_write_db_hash(const struct ble_store_value_db_hash *value); /** * @brief Generates a storage key for a security material entry from its value. @@ -587,6 +699,15 @@ 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); +/** + * Generates a storage key for a Client Supported Features entry from its + * value. + * + * @param out_key On return, the generated key. + * @param value The value to generate a key from. + */ +void ble_store_key_from_value_csfc(struct ble_store_key_csfc *out_key, + const struct ble_store_value_csfc *value); /** * @brief Generates a storage key from a value based on the object type. diff --git a/nimble/host/pkg.yml b/nimble/host/pkg.yml index 2bb5707914..f231b79dce 100644 --- a/nimble/host/pkg.yml +++ b/nimble/host/pkg.yml @@ -41,6 +41,9 @@ pkg.deps.BLE_SM_LEGACY: pkg.deps.BLE_SM_SC: - "@apache-mynewt-core/crypto/mbedtls" +pkg.deps.BLE_GATT_CACHING: + - "@apache-mynewt-core/crypto/mbedtls" + pkg.deps.BLE_MESH: - nimble/host/mesh 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..82cdb8b741 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 @@ -29,6 +29,7 @@ extern "C" { struct ble_hs_cfg; #define BLE_SVC_GATT_CHR_SERVICE_CHANGED_UUID16 0x2a05 +#define BLE_SVC_GATT_CHR_DATABASE_HASH_UUID16 0x2b2a #define BLE_SVC_GATT_CHR_SERVER_SUPPORTED_FEAT_UUID16 0x2b3a #define BLE_SVC_GATT_CHR_CLIENT_SUPPORTED_FEAT_UUID16 0x2b29 diff --git a/nimble/host/services/gatt/src/ble_svc_gatt.c b/nimble/host/services/gatt/src/ble_svc_gatt.c index 331d6bedd8..713c94761f 100644 --- a/nimble/host/services/gatt/src/ble_svc_gatt.c +++ b/nimble/host/services/gatt/src/ble_svc_gatt.c @@ -51,6 +51,12 @@ 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); +#if MYNEWT_VAL(BLE_GATT_CACHING) +static int +ble_svc_gatt_db_hash_access(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg); +#endif + static const struct ble_gatt_svc_def ble_svc_gatt_defs[] = { { /*** Service: GATT */ @@ -73,6 +79,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_GATT_CACHING) + { + .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 +135,34 @@ ble_svc_gatt_cl_sup_feat_access(uint16_t conn_handle, uint16_t attr_handle, return 0; } +#if MYNEWT_VAL(BLE_GATT_CACHING) +static int +ble_svc_gatt_db_hash_access(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + uint8_t db_hash[16]; + int rc; + + if (ctxt->op != BLE_GATT_ACCESS_OP_READ_CHR) { + return BLE_ATT_ERR_WRITE_NOT_PERMITTED; + } + + rc = ble_gatts_calculate_hash(db_hash); + if (rc != 0) { + return BLE_ATT_ERR_UNLIKELY; + } + + rc = os_mbuf_append(ctxt->om, db_hash, sizeof(db_hash)); + if (rc != 0) { + return BLE_ATT_ERR_INSUFFICIENT_RES; + } + + ble_gatts_caching_hash_read(conn_handle); + + return 0; +} +#endif + static int ble_svc_gatt_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) diff --git a/nimble/host/src/ble_att.c b/nimble/host/src/ble_att.c index 8bde29b2ac..050ed9e6bd 100644 --- a/nimble/host/src/ble_att.c +++ b/nimble/host/src/ble_att.c @@ -552,6 +552,24 @@ ble_att_rx_extended(uint16_t conn_handle, uint16_t cid, struct os_mbuf **om) /* Strip L2CAP ATT header from the front of the mbuf. */ os_mbuf_adj(*om, 1); +#if MYNEWT_VAL(BLE_GATT_CACHING) + switch (ble_gatts_caching_rx_gate(conn_handle, cid, op, *om)) { + case BLE_GATTS_CACHING_GATE_PASS: + break; + + case BLE_GATTS_CACHING_GATE_ERROR: + /* Reuse the request buffer for the error response. */ + os_mbuf_adj(*om, OS_MBUF_PKTLEN(*om)); + ble_att_svr_tx_error_rsp(conn_handle, cid, *om, op, 0, + BLE_ATT_ERR_DATABASE_OUT_OF_SYNC); + *om = NULL; + return 0; + + case BLE_GATTS_CACHING_GATE_DROP: + return 0; + } +#endif + rc = entry->bde_fn(conn_handle, cid, om); if (rc != 0) { if (rc == BLE_HS_ENOTSUP) { diff --git a/nimble/host/src/ble_eatt.c b/nimble/host/src/ble_eatt.c index 047a087641..cf138dfb68 100644 --- a/nimble/host/src/ble_eatt.c +++ b/nimble/host/src/ble_eatt.c @@ -105,6 +105,12 @@ ble_eatt_find_by_conn_handle(uint16_t conn_handle) return NULL; } +bool +ble_eatt_has_chan(uint16_t conn_handle) +{ + return ble_eatt_find_by_conn_handle(conn_handle) != NULL; +} + static struct ble_eatt * ble_eatt_find_by_conn_handle_and_busy_op(uint16_t conn_handle, uint8_t op) { diff --git a/nimble/host/src/ble_eatt_priv.h b/nimble/host/src/ble_eatt_priv.h index d3c9877672..0ea745b65d 100644 --- a/nimble/host/src/ble_eatt_priv.h +++ b/nimble/host/src/ble_eatt_priv.h @@ -17,6 +17,8 @@ * under the License. */ +#include + #include "syscfg/syscfg.h" #include "os/os_mbuf.h" #include "host/ble_l2cap.h" @@ -36,6 +38,7 @@ void ble_eatt_init(ble_eatt_att_rx_fn att_rx_fn); uint16_t ble_eatt_get_available_chan_cid(uint16_t conn_handle, uint8_t op); void ble_eatt_release_chan(uint16_t conn_handle, uint8_t op); int ble_eatt_tx(uint16_t conn_handle, uint16_t cid, struct os_mbuf *txom); +bool ble_eatt_has_chan(uint16_t conn_handle); #else static inline void ble_eatt_init(ble_eatt_att_rx_fn att_rx_fn) diff --git a/nimble/host/src/ble_gatt_priv.h b/nimble/host/src/ble_gatt_priv.h index 9a622788b8..e871d1ffc3 100644 --- a/nimble/host/src/ble_gatt_priv.h +++ b/nimble/host/src/ble_gatt_priv.h @@ -95,8 +95,16 @@ extern STATS_SECT_DECL(ble_gatts_stats) ble_gatts_stats; */ #define BLE_GATT_CHR_CLI_SUP_FEAT_MASK 7 +/* Robust Caching bit in the first Client Supported Features octet. */ +#define BLE_GATT_CHR_CLI_SUP_FEAT_ROBUST_CACHING 0x01 + typedef uint8_t ble_gatts_conn_flags; +/* GATT caching state flags (Vol 3, Part G, 2.5.2.1 Robust Caching). */ +#define BLE_GATTS_CONN_F_CHANGE_AWARE 0x01 +#define BLE_GATTS_CONN_F_DB_HASH_READ 0x02 +#define BLE_GATTS_CONN_F_OUT_OF_SYNC_SENT 0x04 + struct ble_gatts_conn { struct ble_gatts_clt_cfg *clt_cfgs; int num_clt_cfgs; @@ -110,8 +118,31 @@ struct ble_gatts_conn { * (Vol. 3, Part G, 7.2) */ uint8_t peer_cl_sup_feat[BLE_GATT_CHR_CLI_SUP_FEAT_SZ]; + +#if MYNEWT_VAL(BLE_GATT_CACHING) + /** GATT caching state; BLE_GATTS_CONN_F_[...] flags. */ + uint8_t chg_aware_flags; +#endif }; +#if MYNEWT_VAL(BLE_GATT_CACHING) +/* Results of ble_gatts_caching_rx_gate(). */ +#define BLE_GATTS_CACHING_GATE_PASS 0 +#define BLE_GATTS_CACHING_GATE_ERROR 1 +#define BLE_GATTS_CACHING_GATE_DROP 2 + +int ble_gatts_caching_rx_gate(uint16_t conn_handle, uint16_t cid, uint8_t op, + struct os_mbuf *om); +void ble_gatts_caching_hash_read(uint16_t conn_handle); +void ble_gatts_caching_cl_sup_feat_updated(uint16_t conn_handle); +void ble_gatts_caching_indicate_ack(uint16_t conn_handle, + uint16_t chr_val_handle); +void ble_gatts_caching_bonding_established(uint16_t conn_handle); +void ble_gatts_caching_bonding_restored(uint16_t conn_handle); +void ble_gatts_caching_start(void); +void ble_gatts_caching_db_changed(void); +#endif + /*** @client. */ int ble_gattc_locked_by_cur_task(void); diff --git a/nimble/host/src/ble_gatts.c b/nimble/host/src/ble_gatts.c index ac5576f6dc..f97e49213e 100644 --- a/nimble/host/src/ble_gatts.c +++ b/nimble/host/src/ble_gatts.c @@ -1395,6 +1395,13 @@ ble_gatts_start(void) } ble_hs_unlock(); + +#if MYNEWT_VAL(BLE_GATT_CACHING) + if (rc == 0) { + ble_gatts_caching_start(); + } +#endif + return rc; } @@ -1423,6 +1430,10 @@ ble_gatts_conn_init(struct ble_gatts_conn *gatts_conn) gatts_conn->num_clt_cfgs = 0; } +#if MYNEWT_VAL(BLE_GATT_CACHING) + gatts_conn->chg_aware_flags = 0; +#endif + return 0; } @@ -1600,6 +1611,10 @@ ble_gatts_rx_indicate_ack(uint16_t conn_handle, uint16_t chr_val_handle) } } +#if MYNEWT_VAL(BLE_GATT_CACHING) + ble_gatts_caching_indicate_ack(conn_handle, chr_val_handle); +#endif + return 0; } @@ -1782,6 +1797,13 @@ ble_gatts_peer_cl_sup_feat_update(uint16_t conn_handle, struct os_mbuf *om) done: ble_hs_unlock(); + +#if MYNEWT_VAL(BLE_GATT_CACHING) + if (rc == 0) { + ble_gatts_caching_cl_sup_feat_updated(conn_handle); + } +#endif + return rc; } @@ -1910,6 +1932,10 @@ ble_gatts_bonding_established(uint16_t conn_handle) } ble_hs_unlock(); + +#if MYNEWT_VAL(BLE_GATT_CACHING) + ble_gatts_caching_bonding_established(conn_handle); +#endif } /** @@ -1930,6 +1956,10 @@ ble_gatts_bonding_restored(uint16_t conn_handle) uint8_t att_op; int rc; +#if MYNEWT_VAL(BLE_GATT_CACHING) + ble_gatts_caching_bonding_restored(conn_handle); +#endif + ble_hs_lock(); conn = ble_hs_conn_find(conn_handle); @@ -2199,6 +2229,9 @@ ble_gatts_svc_set_visibility(uint16_t handle, int visible) } else { ble_att_svr_hide_range(entry->handle, entry->end_group_handle); } +#if MYNEWT_VAL(BLE_GATT_CACHING) + ble_gatts_caching_db_changed(); +#endif return 0; } } diff --git a/nimble/host/src/ble_gatts_caching.c b/nimble/host/src/ble_gatts_caching.c new file mode 100644 index 0000000000..dad9b02809 --- /dev/null +++ b/nimble/host/src/ble_gatts_caching.c @@ -0,0 +1,468 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Server-side GATT robust caching (Vol 3, Part G, 2.5.2.1). + * + * Tracks a change-aware / change-unaware state for each client. A + * change-unaware client that enabled Robust Caching in its Client Supported + * Features receives a Database Out Of Sync error (0x12) on its first ATT + * request after the database changed, and becomes change-aware again by + * reading the Database Hash characteristic, confirming a Service Changed + * indication, or sending another request after the error. For bonded peers + * the Client Supported Features value and the change-aware state persist via + * the BLE_STORE_OBJ_TYPE_CSFC store record. + */ + +#include "syscfg/syscfg.h" + +#if MYNEWT_VAL(BLE_GATT_CACHING) + +#include +#include +#include "os/endian.h" +#include "host/ble_store.h" +#include "ble_hs_priv.h" +#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0 +#include "ble_eatt_priv.h" +#endif + +/* The stored CSFC record must be able to hold the full RAM value. */ +_Static_assert(BLE_STORE_CSFC_SZ >= BLE_GATT_CHR_CLI_SUP_FEAT_SZ, + "CSFC store record too small"); + +static bool +ble_gatts_caching_fixed_chan_only(uint16_t conn_handle) +{ +#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0 + return !ble_eatt_has_chan(conn_handle); +#else + (void)conn_handle; + return true; +#endif +} + +static bool +ble_gatts_caching_robust_unaware(const struct ble_gatts_conn *gatt_srv) +{ + return (gatt_srv->peer_cl_sup_feat[0] & + BLE_GATT_CHR_CLI_SUP_FEAT_ROBUST_CACHING) && + !(gatt_srv->chg_aware_flags & BLE_GATTS_CONN_F_CHANGE_AWARE); +} + +/** + * Persists the peer's Client Supported Features and change-aware state. + * Only bonded peers are persisted. + */ +static void +ble_gatts_caching_persist(uint16_t conn_handle) +{ + struct ble_store_value_csfc value; + struct ble_hs_conn *conn; + + memset(&value, 0, sizeof value); + + ble_hs_lock(); + conn = ble_hs_conn_find(conn_handle); + if (conn == NULL || !conn->bhc_sec_state.bonded) { + ble_hs_unlock(); + return; + } + + value.peer_addr = conn->bhc_peer_addr; + value.peer_addr.type = + ble_hs_misc_peer_addr_type_to_id(conn->bhc_peer_addr.type); + memcpy(value.csfc, conn->bhc_gatt_svr.peer_cl_sup_feat, + BLE_GATT_CHR_CLI_SUP_FEAT_SZ); + value.change_aware = !!(conn->bhc_gatt_svr.chg_aware_flags & + BLE_GATTS_CONN_F_CHANGE_AWARE); + ble_hs_unlock(); + + ble_store_write_csfc(&value); +} + +/** + * Checks the Read By Type Request exceptions: a change-unaware client is + * still served when reading over the full handle range (e.g. the Database + * Hash characteristic) or when reading Include / Characteristic declarations. + * + * @param om The request payload, with the opcode stripped. + */ +static bool +ble_gatts_caching_read_type_exception(struct os_mbuf *om) +{ + uint8_t buf[4]; + uint16_t pktlen; + uint16_t uuid16; + + pktlen = OS_MBUF_PKTLEN(om); + if (pktlen != BLE_ATT_READ_TYPE_REQ_SZ_16 - 1 && + pktlen != BLE_ATT_READ_TYPE_REQ_SZ_128 - 1) { + /* Malformed; let the request handler reject it. */ + return true; + } + + if (os_mbuf_copydata(om, 0, 4, buf) != 0) { + return true; + } + + if (get_le16(buf) == 0x0001 && get_le16(buf + 2) == 0xffff) { + return true; + } + + if (pktlen == BLE_ATT_READ_TYPE_REQ_SZ_16 - 1) { + if (os_mbuf_copydata(om, 4, 2, buf) != 0) { + return true; + } + + uuid16 = get_le16(buf); + if (uuid16 == BLE_ATT_UUID_INCLUDE || + uuid16 == BLE_ATT_UUID_CHARACTERISTIC) { + return true; + } + } + + return false; +} + +/** + * Gates an incoming ATT PDU on the peer's change-aware state. Called before + * the PDU is dispatched to its handler; the opcode has been stripped from om. + */ +int +ble_gatts_caching_rx_gate(uint16_t conn_handle, uint16_t cid, uint8_t op, + struct os_mbuf *om) +{ + struct ble_gatts_conn *gatt_srv; + struct ble_hs_conn *conn; + bool persist; + bool is_req; + int res; + + switch (op) { + case BLE_ATT_OP_READ_TYPE_REQ: + case BLE_ATT_OP_READ_REQ: + case BLE_ATT_OP_READ_BLOB_REQ: + case BLE_ATT_OP_READ_MULT_REQ: + case BLE_ATT_OP_READ_MULT_VAR_REQ: + case BLE_ATT_OP_WRITE_REQ: + case BLE_ATT_OP_PREP_WRITE_REQ: + is_req = true; + break; + + case BLE_ATT_OP_WRITE_CMD: + is_req = false; + break; + + default: + return BLE_GATTS_CACHING_GATE_PASS; + } + + res = BLE_GATTS_CACHING_GATE_PASS; + persist = false; + + ble_hs_lock(); + + conn = ble_hs_conn_find(conn_handle); + if (conn == NULL) { + goto done; + } + gatt_srv = &conn->bhc_gatt_svr; + + if (!ble_gatts_caching_robust_unaware(gatt_srv)) { + goto done; + } + + /* A change-unaware client's commands are ignored. */ + if (!is_req) { + res = BLE_GATTS_CACHING_GATE_DROP; + goto done; + } + + /* The client becomes change-aware on the request following a Database + * Hash read or, when using only the fixed ATT bearer, following a + * Database Out Of Sync error. + */ + if ((gatt_srv->chg_aware_flags & BLE_GATTS_CONN_F_DB_HASH_READ) || + ((gatt_srv->chg_aware_flags & BLE_GATTS_CONN_F_OUT_OF_SYNC_SENT) && + ble_gatts_caching_fixed_chan_only(conn_handle))) { + gatt_srv->chg_aware_flags = BLE_GATTS_CONN_F_CHANGE_AWARE; + persist = conn->bhc_sec_state.bonded; + goto done; + } + + if (op == BLE_ATT_OP_READ_TYPE_REQ && + ble_gatts_caching_read_type_exception(om)) { + goto done; + } + + if (cid == BLE_L2CAP_CID_ATT) { + if (!(gatt_srv->chg_aware_flags & BLE_GATTS_CONN_F_OUT_OF_SYNC_SENT)) { + gatt_srv->chg_aware_flags |= BLE_GATTS_CONN_F_OUT_OF_SYNC_SENT; + res = BLE_GATTS_CACHING_GATE_ERROR; + } else { + res = BLE_GATTS_CACHING_GATE_DROP; + } + } else { + /* Enhanced bearer; the error-then-request transition does not apply + * (Vol 3, Part G, 2.5.2.1), so respond with the error every time. + */ + res = BLE_GATTS_CACHING_GATE_ERROR; + } + +done: + ble_hs_unlock(); + + if (persist) { + ble_gatts_caching_persist(conn_handle); + } + + return res; +} + +/** + * Called when a peer reads the Database Hash characteristic. The + * change-aware transition completes on the peer's next ATT request. + */ +void +ble_gatts_caching_hash_read(uint16_t conn_handle) +{ + struct ble_hs_conn *conn; + + if (conn_handle == BLE_HS_CONN_HANDLE_NONE) { + return; + } + + ble_hs_lock(); + conn = ble_hs_conn_find(conn_handle); + if (conn != NULL && + ble_gatts_caching_robust_unaware(&conn->bhc_gatt_svr)) { + conn->bhc_gatt_svr.chg_aware_flags |= BLE_GATTS_CONN_F_DB_HASH_READ; + } + ble_hs_unlock(); +} + +/** + * Called after a peer successfully wrote its Client Supported Features. A + * client writing its supported features is considered change-aware. + */ +void +ble_gatts_caching_cl_sup_feat_updated(uint16_t conn_handle) +{ + struct ble_hs_conn *conn; + bool persist; + + persist = false; + + ble_hs_lock(); + conn = ble_hs_conn_find(conn_handle); + if (conn != NULL) { + conn->bhc_gatt_svr.chg_aware_flags = BLE_GATTS_CONN_F_CHANGE_AWARE; + persist = conn->bhc_sec_state.bonded; + } + ble_hs_unlock(); + + if (persist) { + ble_gatts_caching_persist(conn_handle); + } +} + +/** + * Called when a peer confirms an indication. Confirming a Service Changed + * indication makes a client using only the fixed ATT bearer change-aware. + */ +void +ble_gatts_caching_indicate_ack(uint16_t conn_handle, uint16_t chr_val_handle) +{ + struct ble_att_svr_entry *entry; + struct ble_hs_conn *conn; + bool persist; + + entry = ble_att_svr_find_by_handle(chr_val_handle); + if (entry == NULL || entry->ha_uuid->type != BLE_UUID_TYPE_16 || + BLE_UUID16(entry->ha_uuid)->value != + BLE_GATT_CHR_SVC_CHANGED_UUID16) { + return; + } + + if (!ble_gatts_caching_fixed_chan_only(conn_handle)) { + return; + } + + persist = false; + + ble_hs_lock(); + conn = ble_hs_conn_find(conn_handle); + if (conn != NULL && + ble_gatts_caching_robust_unaware(&conn->bhc_gatt_svr)) { + conn->bhc_gatt_svr.chg_aware_flags = BLE_GATTS_CONN_F_CHANGE_AWARE; + persist = conn->bhc_sec_state.bonded; + } + ble_hs_unlock(); + + if (persist) { + ble_gatts_caching_persist(conn_handle); + } +} + +void +ble_gatts_caching_bonding_established(uint16_t conn_handle) +{ + struct ble_hs_conn *conn; + bool persist; + + ble_hs_lock(); + conn = ble_hs_conn_find(conn_handle); + persist = conn != NULL && conn->bhc_gatt_svr.peer_cl_sup_feat[0] != 0; + ble_hs_unlock(); + + if (persist) { + ble_gatts_caching_persist(conn_handle); + } +} + +void +ble_gatts_caching_bonding_restored(uint16_t conn_handle) +{ + struct ble_store_value_csfc value; + struct ble_store_key_csfc key; + struct ble_hs_conn *conn; + int rc; + + memset(&key, 0, sizeof key); + + ble_hs_lock(); + conn = ble_hs_conn_find(conn_handle); + if (conn == NULL) { + ble_hs_unlock(); + return; + } + key.peer_addr = conn->bhc_peer_addr; + key.peer_addr.type = + ble_hs_misc_peer_addr_type_to_id(conn->bhc_peer_addr.type); + ble_hs_unlock(); + + rc = ble_store_read_csfc(&key, &value); + if (rc != 0) { + return; + } + + ble_hs_lock(); + conn = ble_hs_conn_find(conn_handle); + if (conn != NULL) { + if (conn->bhc_gatt_svr.peer_cl_sup_feat[0] == 0) { + /* The peer has not written its features on this connection; + * adopt the persisted change-aware state. + */ + if (value.change_aware) { + conn->bhc_gatt_svr.chg_aware_flags = + BLE_GATTS_CONN_F_CHANGE_AWARE; + } else { + conn->bhc_gatt_svr.chg_aware_flags = 0; + } + } + conn->bhc_gatt_svr.peer_cl_sup_feat[0] |= value.csfc[0]; + } + ble_hs_unlock(); +} + +/** + * Marks every persisted peer as change-unaware. + */ +static void +ble_gatts_caching_set_unaware_persisted(void) +{ + struct ble_store_value_csfc value; + struct ble_store_key_csfc key; + int rc; + + memset(&key, 0, sizeof key); + key.peer_addr = *BLE_ADDR_ANY; + + for (key.idx = 0; ; key.idx++) { + rc = ble_store_read_csfc(&key, &value); + if (rc != 0) { + break; + } + + if (value.change_aware) { + value.change_aware = 0; + ble_store_write_csfc(&value); + } + } +} + +/** + * Marks every peer, connected or persisted, as change-unaware. Called when + * the database changes at runtime (e.g. service visibility change). + */ +void +ble_gatts_caching_db_changed(void) +{ + struct ble_hs_conn *conn; + int i; + + for (i = 0; ; i++) { + ble_hs_lock(); + conn = ble_hs_conn_find_by_idx(i); + if (conn != NULL) { + conn->bhc_gatt_svr.chg_aware_flags = 0; + } + ble_hs_unlock(); + + if (conn == NULL) { + break; + } + } + + ble_gatts_caching_set_unaware_persisted(); +} + +/** + * Compares the database hash against the persisted one. If the database + * changed (e.g. across a firmware update), every persisted peer becomes + * change-unaware and the new hash is persisted. Called when the GATT server + * starts. + */ +void +ble_gatts_caching_start(void) +{ + struct ble_store_value_db_hash stored; + struct ble_store_value_db_hash cur; + int rc; + + rc = ble_gatts_calculate_hash(cur.hash); + if (rc != 0) { + return; + } + + rc = ble_store_read_db_hash(&stored); + if (rc == 0 && memcmp(stored.hash, cur.hash, sizeof cur.hash) == 0) { + return; + } + + if (rc == 0) { + /* The database changed since the hash was last stored. */ + ble_gatts_caching_set_unaware_persisted(); + } + + ble_store_write_db_hash(&cur); +} + +#endif /* MYNEWT_VAL(BLE_GATT_CACHING) */ diff --git a/nimble/host/src/ble_gatts_db_hash.c b/nimble/host/src/ble_gatts_db_hash.c new file mode 100644 index 0000000000..f06a22d0a5 --- /dev/null +++ b/nimble/host/src/ble_gatts_db_hash.c @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "syscfg/syscfg.h" + +#if MYNEWT_VAL(BLE_GATT_CACHING) + +#include +#include "os/endian.h" +#include "host/ble_gatt.h" +#include "ble_hs_priv.h" +#include +#include + +/* The longest attribute value contributing to the hash is a characteristic + * declaration: properties (1) + value handle (2) + 128-bit UUID (16). + */ +#define BLE_GATTS_DB_HASH_MAX_VAL_SZ 19 + +static int +ble_gatts_db_hash_update_val(mbedtls_cipher_context_t *ctx, uint16_t handle) +{ + struct os_mbuf *om; + uint8_t buf[BLE_GATTS_DB_HASH_MAX_VAL_SZ]; + uint16_t len; + int rc; + + rc = ble_att_svr_read_local(handle, &om); + if (rc != 0) { + return rc; + } + + len = OS_MBUF_PKTLEN(om); + if (len > sizeof buf) { + rc = BLE_HS_EINVAL; + goto done; + } + + rc = os_mbuf_copydata(om, 0, len, buf); + BLE_HS_DBG_ASSERT(rc == 0); + + if (mbedtls_cipher_cmac_update(ctx, buf, len) != 0) { + rc = BLE_HS_EUNKNOWN; + goto done; + } + + rc = 0; + +done: + os_mbuf_free_chain(om); + return rc; +} + +/** + * Calculates the hash of the local attribute database, as defined in + * Core spec v5.4, Vol 3, Part G, 7.3.1. + */ +int +ble_gatts_calculate_hash(uint8_t *out_hash_key) +{ + mbedtls_cipher_context_t ctx; + struct ble_att_svr_entry *entry; + uint8_t key[16]; + uint8_t buf[4]; + uint16_t uuid16; + uint16_t handle; + int with_val; + int rc; + + mbedtls_cipher_init(&ctx); + + memset(key, 0, sizeof key); + if (mbedtls_cipher_setup(&ctx, mbedtls_cipher_info_from_type( + MBEDTLS_CIPHER_AES_128_ECB)) != 0 || + mbedtls_cipher_cmac_starts(&ctx, key, 128) != 0) { + rc = BLE_HS_EUNKNOWN; + goto done; + } + + for (handle = 1; handle != 0 && handle <= ble_att_svr_prev_handle(); + handle++) { + entry = ble_att_svr_find_by_handle(handle); + if (entry == NULL) { + /* Hidden attribute; not part of the client-visible database. */ + continue; + } + + if (entry->ha_uuid->type != BLE_UUID_TYPE_16) { + continue; + } + uuid16 = BLE_UUID16(entry->ha_uuid)->value; + + switch (uuid16) { + 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: + with_val = 1; + break; + + case BLE_GATT_DSC_USER_DESC_UUID16: + case BLE_GATT_DSC_CLT_CFG_UUID16: + case BLE_GATT_DSC_SRV_CFG_UUID16: + case BLE_GATT_DSC_CHR_FMT_UUID16: + case BLE_GATT_DSC_AGG_FMT_UUID16: + with_val = 0; + break; + + default: + continue; + } + + put_le16(buf, handle); + put_le16(buf + 2, uuid16); + if (mbedtls_cipher_cmac_update(&ctx, buf, sizeof buf) != 0) { + rc = BLE_HS_EUNKNOWN; + goto done; + } + + if (with_val) { + rc = ble_gatts_db_hash_update_val(&ctx, handle); + if (rc != 0) { + goto done; + } + } + } + + if (mbedtls_cipher_cmac_finish(&ctx, out_hash_key) != 0) { + rc = BLE_HS_EUNKNOWN; + goto done; + } + + /* CMAC output is big-endian; the hash is transmitted little-endian. */ + swap_in_place(out_hash_key, 16); + + rc = 0; + +done: + mbedtls_cipher_free(&ctx); + return rc; +} + +#endif /* MYNEWT_VAL(BLE_GATT_CACHING) */ diff --git a/nimble/host/src/ble_store.c b/nimble/host/src/ble_store.c index 79b2f7b95e..72e202bb9e 100644 --- a/nimble/host/src/ble_store.c +++ b/nimble/host/src/ble_store.c @@ -294,6 +294,74 @@ ble_store_key_from_value_cccd(struct ble_store_key_cccd *out_key, out_key->idx = 0; } +int +ble_store_read_csfc(const struct ble_store_key_csfc *key, + struct ble_store_value_csfc *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_CSFC, store_key, store_value); + return rc; +} + +int +ble_store_write_csfc(const struct ble_store_value_csfc *value) +{ + union ble_store_value *store_value; + int rc; + + store_value = (void *)value; + rc = ble_store_write(BLE_STORE_OBJ_TYPE_CSFC, store_value); + return rc; +} + +int +ble_store_delete_csfc(const struct ble_store_key_csfc *key) +{ + union ble_store_key *store_key; + int rc; + + store_key = (void *)key; + rc = ble_store_delete(BLE_STORE_OBJ_TYPE_CSFC, store_key); + return rc; +} + +void +ble_store_key_from_value_csfc(struct ble_store_key_csfc *out_key, + const struct ble_store_value_csfc *value) +{ + out_key->peer_addr = value->peer_addr; + out_key->idx = 0; +} + +int +ble_store_read_db_hash(struct ble_store_value_db_hash *out_value) +{ + union ble_store_value *store_value; + union ble_store_key store_key; + int rc; + + store_key.db_hash.idx = 0; + 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; +} + void ble_store_key_from_value_sec(struct ble_store_key_sec *out_key, const struct ble_store_value_sec *value) @@ -317,6 +385,14 @@ 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_CSFC: + ble_store_key_from_value_csfc(&out_key->csfc, &value->csfc); + break; + + case BLE_STORE_OBJ_TYPE_DB_HASH: + out_key->db_hash.idx = 0; + break; + default: BLE_HS_DBG_ASSERT(0); break; @@ -346,6 +422,13 @@ ble_store_iterate(int obj_type, key.cccd.peer_addr = *BLE_ADDR_ANY; pidx = &key.cccd.idx; break; + case BLE_STORE_OBJ_TYPE_CSFC: + key.csfc.peer_addr = *BLE_ADDR_ANY; + pidx = &key.csfc.idx; + break; + case BLE_STORE_OBJ_TYPE_DB_HASH: + pidx = &key.db_hash.idx; + break; default: BLE_HS_DBG_ASSERT(0); return BLE_HS_EINVAL; diff --git a/nimble/host/src/ble_store_util.c b/nimble/host/src/ble_store_util.c index 6dcbca25a1..e460784100 100644 --- a/nimble/host/src/ble_store_util.c +++ b/nimble/host/src/ble_store_util.c @@ -109,6 +109,15 @@ ble_store_util_delete_peer(const ble_addr_t *peer_id_addr) return rc; } + memset(&key, 0, sizeof key); + key.csfc.peer_addr = *peer_id_addr; + + /* Tolerate stores that do not handle the CSFC object type. */ + rc = ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_CSFC, &key); + if (rc != 0 && rc != BLE_HS_ENOTSUP) { + return rc; + } + return 0; } diff --git a/nimble/host/store/config/src/ble_store_config.c b/nimble/host/store/config/src/ble_store_config.c index 741c73a5f5..d851e0e1d2 100644 --- a/nimble/host/store/config/src/ble_store_config.c +++ b/nimble/host/store/config/src/ble_store_config.c @@ -47,6 +47,15 @@ struct ble_store_value_cccd int ble_store_config_num_cccds; +#if MYNEWT_VAL(BLE_GATT_CACHING) +struct ble_store_value_csfc + ble_store_config_csfcs[MYNEWT_VAL(BLE_STORE_MAX_BONDS)]; +int ble_store_config_num_csfcs; + +struct ble_store_value_db_hash ble_store_config_db_hash; +int ble_store_config_db_hash_present; +#endif + /***************************************************************************** * $sec * *****************************************************************************/ @@ -447,6 +456,160 @@ ble_store_config_write_cccd(const struct ble_store_value_cccd *value_cccd) #endif } +/***************************************************************************** + * $csfc * + *****************************************************************************/ + +#if MYNEWT_VAL(BLE_GATT_CACHING) +static int +ble_store_config_find_csfc(const struct ble_store_key_csfc *key) +{ + struct ble_store_value_csfc *csfc; + int skipped; + int i; + + skipped = 0; + for (i = 0; i < ble_store_config_num_csfcs; i++) { + csfc = ble_store_config_csfcs + i; + + if (ble_addr_cmp(&key->peer_addr, BLE_ADDR_ANY)) { + if (ble_addr_cmp(&csfc->peer_addr, &key->peer_addr)) { + continue; + } + } + + if (key->idx > skipped) { + skipped++; + continue; + } + + return i; + } + return -1; +} + +static int +ble_store_config_delete_csfc(const struct ble_store_key_csfc *key_csfc) +{ + int idx; + int rc; + + idx = ble_store_config_find_csfc(key_csfc); + if (idx < 0) { + return BLE_HS_ENOENT; + } + + rc = ble_store_config_delete_obj(ble_store_config_csfcs, + sizeof *ble_store_config_csfcs, + idx, + &ble_store_config_num_csfcs); + if (rc != 0) { + return rc; + } + + rc = ble_store_config_persist_csfcs(); + if (rc != 0) { + return rc; + } + return 0; +} + +static int +ble_store_config_read_csfc(const struct ble_store_key_csfc *key_csfc, + struct ble_store_value_csfc *value_csfc) +{ + int idx; + + idx = ble_store_config_find_csfc(key_csfc); + if (idx == -1) { + return BLE_HS_ENOENT; + } + + *value_csfc = ble_store_config_csfcs[idx]; + return 0; +} + +static int +ble_store_config_write_csfc(const struct ble_store_value_csfc *value_csfc) +{ + struct ble_store_key_csfc key_csfc; + int idx; + int rc; + + ble_store_key_from_value_csfc(&key_csfc, value_csfc); + idx = ble_store_config_find_csfc(&key_csfc); + if (idx == -1) { + if (ble_store_config_num_csfcs >= MYNEWT_VAL(BLE_STORE_MAX_BONDS)) { + BLE_HS_LOG(DEBUG, "error persisting csfc; too many entries (%d)\n", + ble_store_config_num_csfcs); + return BLE_HS_ESTORE_CAP; + } + + idx = ble_store_config_num_csfcs; + ble_store_config_num_csfcs++; + } + + ble_store_config_csfcs[idx] = *value_csfc; + + rc = ble_store_config_persist_csfcs(); + if (rc != 0) { + return rc; + } + + return 0; +} + +/***************************************************************************** + * $db hash * + *****************************************************************************/ + +static int +ble_store_config_read_db_hash(struct ble_store_value_db_hash *value_db_hash) +{ + if (!ble_store_config_db_hash_present) { + return BLE_HS_ENOENT; + } + + *value_db_hash = ble_store_config_db_hash; + return 0; +} + +static int +ble_store_config_write_db_hash(const struct ble_store_value_db_hash *value_db_hash) +{ + int rc; + + ble_store_config_db_hash = *value_db_hash; + ble_store_config_db_hash_present = 1; + + rc = ble_store_config_persist_db_hash(); + if (rc != 0) { + return rc; + } + + return 0; +} + +static int +ble_store_config_delete_db_hash(void) +{ + int rc; + + if (!ble_store_config_db_hash_present) { + return BLE_HS_ENOENT; + } + + ble_store_config_db_hash_present = 0; + + rc = ble_store_config_persist_db_hash(); + if (rc != 0) { + return rc; + } + + return 0; +} +#endif /* MYNEWT_VAL(BLE_GATT_CACHING) */ + /***************************************************************************** * $api * *****************************************************************************/ @@ -489,6 +652,16 @@ 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; +#if MYNEWT_VAL(BLE_GATT_CACHING) + case BLE_STORE_OBJ_TYPE_CSFC: + rc = ble_store_config_read_csfc(&key->csfc, &value->csfc); + return rc; + + case BLE_STORE_OBJ_TYPE_DB_HASH: + rc = ble_store_config_read_db_hash(&value->db_hash); + return rc; +#endif + default: return BLE_HS_ENOTSUP; } @@ -518,6 +691,16 @@ ble_store_config_write(int obj_type, const union ble_store_value *val) rc = ble_store_config_write_cccd(&val->cccd); return rc; +#if MYNEWT_VAL(BLE_GATT_CACHING) + case BLE_STORE_OBJ_TYPE_CSFC: + rc = ble_store_config_write_csfc(&val->csfc); + return rc; + + case BLE_STORE_OBJ_TYPE_DB_HASH: + rc = ble_store_config_write_db_hash(&val->db_hash); + return rc; +#endif + default: return BLE_HS_ENOTSUP; } @@ -541,6 +724,16 @@ ble_store_config_delete(int obj_type, const union ble_store_key *key) rc = ble_store_config_delete_cccd(&key->cccd); return rc; +#if MYNEWT_VAL(BLE_GATT_CACHING) + case BLE_STORE_OBJ_TYPE_CSFC: + rc = ble_store_config_delete_csfc(&key->csfc); + return rc; + + case BLE_STORE_OBJ_TYPE_DB_HASH: + rc = ble_store_config_delete_db_hash(); + return rc; +#endif + default: return BLE_HS_ENOTSUP; } @@ -560,6 +753,10 @@ 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; +#if MYNEWT_VAL(BLE_GATT_CACHING) + ble_store_config_num_csfcs = 0; + ble_store_config_db_hash_present = 0; +#endif 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..33f18e5c62 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,17 @@ 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) +#if MYNEWT_VAL(BLE_GATT_CACHING) +#define BLE_STORE_CONFIG_CSFC_ENCODE_SZ \ + BASE64_ENCODE_SIZE(sizeof (struct ble_store_value_csfc)) + +#define BLE_STORE_CONFIG_CSFC_SET_ENCODE_SZ \ + (MYNEWT_VAL(BLE_STORE_MAX_BONDS) * BLE_STORE_CONFIG_CSFC_ENCODE_SZ + 1) + +#define BLE_STORE_CONFIG_DB_HASH_ENCODE_SZ \ + (BASE64_ENCODE_SIZE(sizeof (struct ble_store_value_db_hash)) + 1) +#endif + static void ble_store_config_serialize_arr(const void *arr, int obj_sz, int num_objs, char *out_buf, int buf_sz) @@ -118,6 +129,22 @@ ble_store_config_conf_set(int argc, char **argv, char *val) sizeof *ble_store_config_cccds, &ble_store_config_num_cccds); return rc; +#if MYNEWT_VAL(BLE_GATT_CACHING) + } else if (strcmp(argv[0], "csfc") == 0) { + rc = ble_store_config_deserialize_arr( + val, + ble_store_config_csfcs, + sizeof *ble_store_config_csfcs, + &ble_store_config_num_csfcs); + return rc; + } else if (strcmp(argv[0], "db_hash") == 0) { + rc = ble_store_config_deserialize_arr( + val, + &ble_store_config_db_hash, + sizeof ble_store_config_db_hash, + &ble_store_config_db_hash_present); + return rc; +#endif } } return OS_ENOENT; @@ -130,6 +157,10 @@ 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]; +#if MYNEWT_VAL(BLE_GATT_CACHING) + char csfc[BLE_STORE_CONFIG_CSFC_SET_ENCODE_SZ]; + char db_hash[BLE_STORE_CONFIG_DB_HASH_ENCODE_SZ]; +#endif } buf; ble_store_config_serialize_arr(ble_store_config_our_secs, @@ -153,6 +184,22 @@ ble_store_config_conf_export(void (*func)(char *name, char *val), sizeof buf.cccd); func("ble_hs/cccd", buf.cccd); +#if MYNEWT_VAL(BLE_GATT_CACHING) + ble_store_config_serialize_arr(ble_store_config_csfcs, + sizeof *ble_store_config_csfcs, + ble_store_config_num_csfcs, + buf.csfc, + sizeof buf.csfc); + func("ble_hs/csfc", buf.csfc); + + ble_store_config_serialize_arr(&ble_store_config_db_hash, + sizeof ble_store_config_db_hash, + ble_store_config_db_hash_present, + buf.db_hash, + sizeof buf.db_hash); + func("ble_hs/db_hash", buf.db_hash); +#endif + return 0; } @@ -223,6 +270,46 @@ ble_store_config_persist_cccds(void) return 0; } +#if MYNEWT_VAL(BLE_GATT_CACHING) +int +ble_store_config_persist_csfcs(void) +{ + char buf[BLE_STORE_CONFIG_CSFC_SET_ENCODE_SZ]; + int rc; + + ble_store_config_serialize_arr(ble_store_config_csfcs, + sizeof *ble_store_config_csfcs, + ble_store_config_num_csfcs, + buf, + sizeof buf); + rc = conf_save_one("ble_hs/csfc", buf); + if (rc != 0) { + return BLE_HS_ESTORE_FAIL; + } + + return 0; +} + +int +ble_store_config_persist_db_hash(void) +{ + char buf[BLE_STORE_CONFIG_DB_HASH_ENCODE_SZ]; + int rc; + + ble_store_config_serialize_arr(&ble_store_config_db_hash, + sizeof ble_store_config_db_hash, + ble_store_config_db_hash_present, + buf, + sizeof buf); + rc = conf_save_one("ble_hs/db_hash", buf); + if (rc != 0) { + return BLE_HS_ESTORE_FAIL; + } + + return 0; +} +#endif /* MYNEWT_VAL(BLE_GATT_CACHING) */ + 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 bae90e9765..c7876fb8ab 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,24 @@ extern struct ble_store_value_cccd ble_store_config_cccds[MYNEWT_VAL(BLE_STORE_MAX_CCCDS)]; extern int ble_store_config_num_cccds; +#if MYNEWT_VAL(BLE_GATT_CACHING) +extern struct ble_store_value_csfc + ble_store_config_csfcs[MYNEWT_VAL(BLE_STORE_MAX_BONDS)]; +extern int ble_store_config_num_csfcs; + +extern struct ble_store_value_db_hash ble_store_config_db_hash; +extern int ble_store_config_db_hash_present; +#endif + #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); +#if MYNEWT_VAL(BLE_GATT_CACHING) +int ble_store_config_persist_csfcs(void); +int ble_store_config_persist_db_hash(void); +#endif void ble_store_config_conf_init(void); #else @@ -48,6 +61,10 @@ 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; } +#if MYNEWT_VAL(BLE_GATT_CACHING) +static inline int ble_store_config_persist_csfcs(void) { return 0; } +static inline int ble_store_config_persist_db_hash(void) { return 0; } +#endif static inline void ble_store_config_conf_init(void) { } #endif /* MYNEWT_VAL(BLE_STORE_CONFIG_PERSIST) */ diff --git a/nimble/host/syscfg.yml b/nimble/host/syscfg.yml index 36bfb63896..936531ca81 100644 --- a/nimble/host/syscfg.yml +++ b/nimble/host/syscfg.yml @@ -317,6 +317,14 @@ syscfg.defs: due to memory exhaustion. (0/1) Units are milliseconds. (0/1) value: 1000 + # GATT caching options + BLE_GATT_CACHING: + description: > + Enables server support for GATT caching: exposes the Database + Hash characteristic in the GATT service so clients can detect + attribute database changes. + value: 0 + # Enhanced ATT bearer options BLE_EATT_CHAN_NUM: description: > diff --git a/porting/nimble/include/syscfg/syscfg.h b/porting/nimble/include/syscfg/syscfg.h index cc614f8376..09bd81d72c 100644 --- a/porting/nimble/include/syscfg/syscfg.h +++ b/porting/nimble/include/syscfg/syscfg.h @@ -863,6 +863,10 @@ #define MYNEWT_VAL_BLE_GAP_MAX_PENDING_CONN_PARAM_UPDATE (1) #endif +#ifndef MYNEWT_VAL_BLE_GATT_CACHING +#define MYNEWT_VAL_BLE_GATT_CACHING (0) +#endif + #ifndef MYNEWT_VAL_BLE_GATT_DISC_ALL_CHRS #define MYNEWT_VAL_BLE_GATT_DISC_ALL_CHRS (MYNEWT_VAL_BLE_ROLE_CENTRAL) #endif