Discovering the GATT database of a remote device every time a connection is made takes a lot of time and energy. To avoid this, most Bluetooth devices use attribute caching, i.e. once they discovered the GATT database of a remote device, they store the discovered attribute handles for future use (in other words they store a local copy of the remote database structure, or at least parts of it). This works well as long as the remote device keeps its GATT database structure unchanged. If the database structure changes - e.g. because of a firmware update that introduces new features - the stored attribute handles become invalid.
There are two methods to notify peer devices about the change:
using Service Change Indication
using GATT Caching feature
Service Change Indication was introduced in Bluetooth 4.0. This solution is discussed in the following article: KBA_BT_0312: Service Change Indications. The main disadvantage of this method is, that it works only if the peer devices are bonded.
GATT caching feature was introduced in Bluetooth 5.1 to overcome the bonding problem. Using GATT caching, every device can make sure, if it stores the latest version of the GATT database structure. This is achieved by:
Adding two new characteristics to the Generic Attribute service (Database Hash and Client Supported Features)
Adding a new GATT error code (database out-of-sync)
Database Hash characteristic
The Database Hash characteristic stores a 128bit value, which is a AES-CMAC hash calculated from the database structure. Any little change in the database structure will result in a different hash value. After discovering the database once, the client should store this value. Next time, when the client connects to the server, it should only check if the Database Hash has changed. If it hasn't, it is safe to assume, that the database structure in unchanged, and the cached attribute handles are still valid. If it has changed, the client need to rediscover the GATT database.
Note: The Database Hash must be read using GATT Read Using Characteristic UUID sub-procedure gecko_cmd_gatt_read_characteristic_value_by_uuid()
Database out-of-sync error code
If the client enables Robust Caching, by setting the Robust Caching bit to 1 in the Client Supported Features characteristic, then the server can send a database out-of-sync error code as a response to a GATT operation whenever it assumes, that the client has an out-of-date database.
If the client and the server are bonded, then the server checks if the database has changed since the last connection and notifies the client about the change via Service Change Indication - just like before introducing GATT caching. The only difference is, that if the client initiates a GATT operation before the indication is received and confirmed, the server can send a database out-of-sync error code. This helps avoid race condition if the client would like to initiate a GATT operation too quickly after connection establishment.
If the client and the server are not bonded, then the server assumes, that the client checks the Database Hash and/or discovers the GATT database upon each connection before initiating any GATT operation. I.e. the server assumes that the client is aware of database changes after the connection was established. If there is a change in the database during connection, the client gets change-unaware. At the next GATT operation the server sends a database out-of-sync error code to the client to signal this (except if the client reads the Database Hash meanwhile). From the next GATT operation the server assumes that the client is change-aware again.
Implementation
Server
To enable GATT caching functionality on the server side you have to enable Generic Attribute Service in the GATT database. To do so
Open the GATT Configurator by opening the .isc file in you Bluetooth project
Click on the Profile (by default it is Custom BLE GATT) in the GATT database structure
In the GATT settings tick the Generic Attribute Service checkbox. You will not see the service in the GATT database, but it will be added to generated gatt_db.c
Press Generate
Client
To enable GATT caching on the client side, you have to
Read the Database Hash characteristic upon each connection and compare its value with the last stored value:
Check for database out-of-sync error after each GATT operation
if (evt->data.evt_gatt_procedure_completed.result == bg_err_att_out_of_sync)
printLog("database has changed!\r\n");
Example
In the attached example we implement a server that can change its GATT database structure runtime, and a client that can recognize the database change.
The server uses the polymorphic GATT feature, i.e. it can enable and disable some if its services / characteristics runtime. Here we define two custom services in the database, from which only one is enabled at a time.
From the client side we always want to write to the "My Custom Characteristic" characteristic, but its characteristic handle will change with the version of the database. So we have to detect the version of the database, and write to the proper handle. Also, we have to recognize if the database changes, and change the handle according to it.
To run the example:
Create 2 new soc-empty projects, one for the server, and one for the client
import gatt_server.xml into the server project. Open the GATT Configurator, click the import button on the right side, select the file, and after successful import, press Generate.
copy app.h into both projects (overwriting the existing app.h)
remove app.c from both projects
copy app_server.c into the server project
copy app_client.c into the client project
copy C:\SiliconLabs\SimplicityStudio\v4\developer\sdks\gecko_sdk_suite\v2.x\platform\emdrv\gpiointerrupt\src\gpiointerrupt.c into the server project
build and flash the two projects to two WSTKs with a radio board
open two terminals on your PC and connect to the virtual COM ports to see the logs
reset both WSTKs
press PB0 / PB1 on the server side WSTK to switch between the two GATT database versions
KBA_BT_0313: GATT Caching
Introduction
Discovering the GATT database of a remote device every time a connection is made takes a lot of time and energy. To avoid this, most Bluetooth devices use attribute caching, i.e. once they discovered the GATT database of a remote device, they store the discovered attribute handles for future use (in other words they store a local copy of the remote database structure, or at least parts of it). This works well as long as the remote device keeps its GATT database structure unchanged. If the database structure changes - e.g. because of a firmware update that introduces new features - the stored attribute handles become invalid.
There are two methods to notify peer devices about the change:
Service Change Indication was introduced in Bluetooth 4.0. This solution is discussed in the following article: KBA_BT_0312: Service Change Indications. The main disadvantage of this method is, that it works only if the peer devices are bonded.
GATT caching feature was introduced in Bluetooth 5.1 to overcome the bonding problem. Using GATT caching, every device can make sure, if it stores the latest version of the GATT database structure. This is achieved by:
Database Hash characteristic
The Database Hash characteristic stores a 128bit value, which is a AES-CMAC hash calculated from the database structure. Any little change in the database structure will result in a different hash value. After discovering the database once, the client should store this value. Next time, when the client connects to the server, it should only check if the Database Hash has changed. If it hasn't, it is safe to assume, that the database structure in unchanged, and the cached attribute handles are still valid. If it has changed, the client need to rediscover the GATT database.
Database out-of-sync error code
If the client enables Robust Caching, by setting the Robust Caching bit to 1 in the Client Supported Features characteristic, then the server can send a database out-of-sync error code as a response to a GATT operation whenever it assumes, that the client has an out-of-date database.
If the client and the server are bonded, then the server checks if the database has changed since the last connection and notifies the client about the change via Service Change Indication - just like before introducing GATT caching. The only difference is, that if the client initiates a GATT operation before the indication is received and confirmed, the server can send a database out-of-sync error code. This helps avoid race condition if the client would like to initiate a GATT operation too quickly after connection establishment.
If the client and the server are not bonded, then the server assumes, that the client checks the Database Hash and/or discovers the GATT database upon each connection before initiating any GATT operation. I.e. the server assumes that the client is aware of database changes after the connection was established. If there is a change in the database during connection, the client gets change-unaware. At the next GATT operation the server sends a database out-of-sync error code to the client to signal this (except if the client reads the Database Hash meanwhile). From the next GATT operation the server assumes that the client is change-aware again.
Implementation
Server
To enable GATT caching functionality on the server side you have to enable Generic Attribute Service in the GATT database. To do so
Client
To enable GATT caching on the client side, you have to
Example
In the attached example we implement a server that can change its GATT database structure runtime, and a client that can recognize the database change.
The server uses the polymorphic GATT feature, i.e. it can enable and disable some if its services / characteristics runtime. Here we define two custom services in the database, from which only one is enabled at a time.
From the client side we always want to write to the "My Custom Characteristic" characteristic, but its characteristic handle will change with the version of the database. So we have to detect the version of the database, and write to the proper handle. Also, we have to recognize if the database changes, and change the handle according to it.
To run the example:
Now you should observe something like this: