att_server: cache GATT Database Hash in TLV and discard stored CCCs upon change

This commit is contained in:
Matthias Ringwald 2025-03-14 13:27:10 +01:00
parent 9a0cde5baa
commit 03b919e9ae
2 changed files with 60 additions and 4 deletions

View File

@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Added
- GAP: simulate HCI_EVENT_REMOTE_NAME_REQUEST_COMPLETE if HCI Remote Name Request fails
- AVRCP Controller: Added send generic PASS THROUGH command
- GATT Server: store Databse Hash in TLV and discard stored CCCs if database changes
- HID Host: storage for HID Descriptors is now optional
- HCI: support newer AIROC Controller that require Download Mode with ENABLE_AIROC_DOWNLOAD_MODE
- Zephyr: provide hal_flash_bank implementation for native flash driver

View File

@ -1030,6 +1030,10 @@ static uint32_t att_server_persistent_ccc_tag_for_index(uint8_t index){
return (((uint8_t)'B') << 24u) | (((uint8_t)'T') << 16u) | (((uint8_t)'C') << 8u) | index;
}
static uint32_t att_server_tag_for_hash(void){
return (((uint8_t)'B') << 24u) | (((uint8_t)'T') << 16u) | (((uint8_t)'D') << 8u) | ((uint8_t)'B');
}
static void att_server_persistent_ccc_write(hci_con_handle_t con_handle, uint16_t att_handle, uint16_t value){
// lookup att_server instance
hci_connection_t * hci_connection = hci_connection_for_handle(con_handle);
@ -1149,16 +1153,67 @@ static void att_server_persistent_ccc_clear(att_server_t * att_server){
}
}
// @returns true if cccs are valid
static bool att_server_persistent_ccc_validate_database(const btstack_tlv_t * tlv_impl, void * tlv_context) {
// we skip validation if there's no GATT Database Hash Characteristic
uint8_t hash_from_db[16];
bool have_db_hash = gatt_server_get_database_hash(hash_from_db);
if (!have_db_hash) return true;
log_info("Database Hash:");
log_info_hexdump(hash_from_db, 16);
// otherwise, compare to stored hash if available
uint8_t hash_from_tlv[16];
uint32_t hash_tag = att_server_tag_for_hash();
bool store_hash = false;
bool stored_cccs_valid = true;
int len = tlv_impl->get_tag(tlv_context, hash_tag, hash_from_tlv, sizeof(hash_from_tlv));
if (len == (int) sizeof(hash_from_tlv)) {
log_info("TLV Hash:");
log_info_hexdump(hash_from_db, 16);
if (memcmp(hash_from_db, hash_from_tlv, 16) != 0) {
log_info("Database Hash changed -> clear persistent CCCs");
stored_cccs_valid = false;
store_hash = true;
}
} else {
// optimistic update: store hash without clearing CCCs
store_hash = true;
}
// clear stored CCCs
if (stored_cccs_valid == false) {
for (int index=0;index<NVN_NUM_GATT_SERVER_CCC;index++) {
uint32_t ccc_tag = att_server_persistent_ccc_tag_for_index(index);
tlv_impl->delete_tag(tlv_context, ccc_tag);
}
}
// store updated database hash
if (store_hash) {
tlv_impl->store_tag(tlv_context, hash_tag, hash_from_db, sizeof(hash_from_db));
}
return stored_cccs_valid;
}
static void att_server_persistent_ccc_restore(att_server_t * att_server, att_connection_t * att_connection){
int le_device_index = att_server->ir_le_device_db_index;
log_info("Restore CCC values of remote %s, le device id %d", bd_addr_to_str(att_server->peer_address), le_device_index);
// check if bonded
if (le_device_index < 0) return;
// get btstack_tlv
const btstack_tlv_t * tlv_impl = NULL;
void * tlv_context;
btstack_tlv_get_instance(&tlv_impl, &tlv_context);
if (!tlv_impl) return;
// validate database hash
if ((att_server_flags & ATT_SERVER_FLAGS_VALIDATE_DATABASE_HASH) != 0) {
att_server_flags &= ~ATT_SERVER_FLAGS_VALIDATE_DATABASE_HASH;
if (att_server_persistent_ccc_validate_database(tlv_impl, tlv_context) == false) {
return;
}
}
// check if bonded
int le_device_index = att_server->ir_le_device_db_index;
if (le_device_index < 0) return;
log_info("Restore CCC values of remote %s, le device id %d", bd_addr_to_str(att_server->peer_address), le_device_index);
// get all ccc tag
int index;
persistent_ccc_entry_t entry;