Skip to content

nimble/host: add server-side GATT caching support (Database Hash + robust caching)#2268

Open
gmarull wants to merge 6 commits into
apache:masterfrom
teslabs:gatt-hash-new
Open

nimble/host: add server-side GATT caching support (Database Hash + robust caching)#2268
gmarull wants to merge 6 commits into
apache:masterfrom
teslabs:gatt-hash-new

Conversation

@gmarull

@gmarull gmarull commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

This series adds server-side support for GATT caching as defined in Core spec v5.4, Vol 3, Part G, 2.5.2 and 7.3:

  • ble_gatts_calculate_hash() computes the database hash (AES-CMAC over the client-visible attribute database) using the Mbed TLS streaming CMAC API.
  • The GATT service exposes the Database Hash characteristic (0x2B2A).
  • New CSFC and database-hash store object types persist each bonded peer's Client Supported Features and change-aware state; the config store handles them under ble_hs/csfc and ble_hs/db_hash.
  • Robust caching state machine: change-unaware bonded clients that enabled robust caching receive the new Database Out Of Sync ATT error (0x12) when operating on stale handles after the database changed, and become change-aware per the spec (hash read or Service Changed confirmation).

Everything is gated behind the new BLE_GATT_CACHING syscfg setting, disabled by default.

Note: Briefly tested on PebbleOS, where we need this feature. We'll conduct more tests in the following days.

gmarull added 6 commits July 14, 2026 16:55
Add the ATT error code 0x12 (Core spec v5.4, Vol 3, Part F, 3.4.1.1),
sent by a server to a change-unaware client as part of the GATT robust
caching flow.

Signed-off-by: Gerard Marull-Paretas <gerard@teslabs.com>
Add ble_gatts_calculate_hash(), which computes the GATT database hash
as defined in Core spec v5.4, Vol 3, Part G, 7.3.1: an AES-CMAC (with
an all-zero key) over the concatenation of the client-visible attribute
database, in ascending handle order. Service declarations, includes,
characteristic declarations and extended properties descriptors
contribute handle, type and value; the 0x2901-0x2905 descriptors
contribute handle and type only; all other attributes are excluded.
Attributes hidden via ble_gatts_svc_set_visibility() are skipped, as
they are not part of the client-visible database.

The stream is fed incrementally into the Mbed TLS CMAC API, avoiding a
heap allocation for a flattened copy of the database. Per-attribute
values are read through ble_att_svr_read_local(), so declaration values
come from the same access callbacks that serve regular ATT reads. The
CMAC output is byte-swapped since the hash characteristic value is
transmitted in little-endian byte order (matching PTS expectations and
other stacks, e.g. Zephyr).

The feature is gated behind the new BLE_GATT_CACHING syscfg setting,
disabled by default.

Signed-off-by: Gerard Marull-Paretas <gerard@teslabs.com>
Expose the Database Hash characteristic (0x2B2A) in the GATT service
when BLE_GATT_CACHING is enabled, backed by ble_gatts_calculate_hash().
The hash is computed on each read; the database is static after
ble_gatts_start(), and reads only happen when a caching client
revalidates its cache, typically once per reconnection.

This lets clients that cache the attribute database (notably iOS and
Android) detect database changes across firmware updates
deterministically, instead of relying only on Service Changed
indications.

Signed-off-by: Gerard Marull-Paretas <gerard@teslabs.com>
Add two store object types used by server-side GATT caching:

- BLE_STORE_OBJ_TYPE_CSFC persists, per bonded peer, the Client
  Supported Features value written by the peer along with its
  change-aware state, both of which must survive reconnections for
  bonded clients (Vol 3, Part G, 2.5.2.1).
- BLE_STORE_OBJ_TYPE_DB_HASH persists the local database hash, so the
  host can detect across reboots (e.g. firmware updates) that the
  database changed and mark bonded peers change-unaware.

ble_store_util_delete_peer() now also removes the peer's CSFC record,
tolerating stores that do not handle the new object type.

Signed-off-by: Gerard Marull-Paretas <gerard@teslabs.com>
Store and load the CSFC and database hash records in the config store
backend when BLE_GATT_CACHING is enabled. They are persisted under the
ble_hs/csfc and ble_hs/db_hash settings, following the existing CCCD
pattern; CSFC records are capped at BLE_STORE_MAX_BONDS.

Signed-off-by: Gerard Marull-Paretas <gerard@teslabs.com>
Implement the server side of GATT robust caching (Vol 3, Part G,
2.5.2.1) on top of the Database Hash characteristic, enabled by the
BLE_GATT_CACHING syscfg setting. The structure mirrors Zephyr's
PTS-validated implementation.

Each connection tracks a change-aware state in ble_gatts_conn. A
change-unaware client that enabled Robust Caching in its Client
Supported Features receives an ATT Database Out Of Sync error (0x12)
once on the fixed bearer, and its commands are ignored; the error is
gated centrally in ble_att_rx_extended() before dispatch. Read By Type
requests over the full handle range, or for Include / Characteristic
declarations, are exempt so the client can read the Database Hash and
rediscover.

A client becomes change-aware again when it reads the Database Hash and
sends another request, confirms a Service Changed indication (only when
using just the fixed ATT bearer, per spec), or sends another request
after the error (fixed bearer only). Writing Client Supported Features
also marks the client change-aware. Per-bearer tracking of the error on
enhanced bearers is not maintained: change-unaware clients get the
error on every EATT request until they revalidate via the hash.

For bonded peers, the features and change-aware state persist through
the CSFC store record: stored when bonding is established and on every
state transition, restored when encryption with a bonded peer is
re-established. State is not restored before encryption; before that,
a reconnected bonded peer is served normally. On ble_gatts_start() the
database hash is compared against the persisted copy and, on mismatch
(e.g. after a firmware update), all persisted peers are marked
change-unaware. Service visibility changes mark all peers, connected
and persisted, change-unaware at runtime.

Signed-off-by: Gerard Marull-Paretas <gerard@teslabs.com>
@sjanc

sjanc commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Hi, is this from scratch or based on #2098 ?

@gmarull

gmarull commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Hi, is this from scratch or based on #2098 ?

I was not aware of that PR, this is from scratch

@gmarull

gmarull commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

@sjanc what is the plan for this feature? I'd like to know if you're expecting to have something in-tree soon (be based on this PR or the other; I don't care really). I'm happy to iterate on this PR if needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants