Skip to content

Commit d8b2ea8

Browse files
obraclaude
andcommitted
Disable CCCD persistence to prevent flash corruption
This commit intentionally disables the saving of CCCD (Client Characteristic Configuration Descriptor) values to flash storage while maintaining API compatibility. This change addresses a critical flash corruption issue that has been affecting BLE keyboard users. Problem: -------- Users have reported that their BLE keyboards fail to reconnect after sleep, requiring re-pairing. Investigation revealed that bonding data files were becoming corrupted, particularly during the wake-from-sleep sequence. Root Cause: ----------- 1. When a device wakes from sleep, the BLE connection is re-established 2. The host automatically re-writes CCCD values to enable notifications 3. These writes are queued via ada_callback() to execute asynchronously 4. The wake period is unstable - connections often drop with errors 0x08 (BLE_HCI_CONNECTION_TIMEOUT) or 0x22 (BLE_HCI_STATUS_CODE_LMP_RESPONSE_TIMEOUT) 5. When disconnection occurs during flash write, the operation is interrupted 6. The flash_cache layer silently ignores write errors, resulting in corrupted data Why Disable CCCD Saving: ------------------------ 1. UNNECESSARY: For HID devices, CCCD values are constant - notifications must always be enabled for keyboard functionality. Every host writes the same value. 2. DANGEROUS: The post-wake period creates a perfect storm for corruption: unstable connections + automatic CCCD writes + poor error handling 3. MINIMAL IMPACT: Hosts automatically re-enable notifications on connection. The only theoretical downside is a 1-2 second delay for non-HID services like Battery Service, which is negligible. Implementation: --------------- - bond_save_cccd() now returns true immediately without writing to flash - bond_load_cccd() remains functional for compatibility and migration - BLEGatt.cpp call site preserved to maintain API compatibility - Extensive comments document the rationale and trade-offs This eliminates a major corruption vector while having no functional impact on keyboard operation. The change is backward compatible - existing devices will load stored CCCDs one final time, then stop saving new values. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 56a379b commit d8b2ea8

File tree

3 files changed

+69
-2
lines changed

3 files changed

+69
-2
lines changed

libraries/Bluefruit52Lib/src/BLEGatt.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ void BLEGatt::_eventHandler(ble_evt_t* evt)
142142
// Save CCCD if paired
143143
if ( conn->secured() && (evt_id == BLE_GATTS_EVT_WRITE) && (req_handle == chr->handles().cccd_handle) )
144144
{
145+
/* NOTE: This call to saveCccd() is intentionally left in place even though the underlying
146+
* implementation has been disabled. This preserves API compatibility and avoids the need
147+
* to modify this widely-used code path. The actual bond_save_cccd() function now returns
148+
* immediately without performing any flash operations. See bond_save_cccd() in bonding.cpp
149+
* for detailed explanation of why CCCD saving has been disabled.
150+
*/
145151
conn->saveCccd();
146152
}
147153
}

libraries/Bluefruit52Lib/src/utility/bonding.cpp

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,12 +258,71 @@ static void bond_save_cccd_dfr (uint8_t role, uint16_t conn_hdl, ble_gap_addr_t
258258

259259
bool bond_save_cccd (uint8_t role, uint16_t conn_hdl, ble_gap_addr_t const* id_addr)
260260
{
261-
// queue to execute in Ada Callback thread
262-
return ada_callback(id_addr, sizeof(ble_gap_addr_t), bond_save_cccd_dfr, role, conn_hdl, id_addr);
261+
/* DISABLED: CCCD (Client Characteristic Configuration Descriptor) saving has been intentionally disabled
262+
* to prevent flash storage corruption issues.
263+
*
264+
* BACKGROUND:
265+
* CCCDs control whether notifications/indications are enabled for BLE characteristics. The original
266+
* implementation saved these values to flash storage to persist them across connections. However,
267+
* this approach has several critical issues:
268+
*
269+
* 1. CORRUPTION RISK: The flash write operations occur via deferred callbacks (ada_callback) which
270+
* can be interrupted by BLE disconnections. When disconnections happen during flash writes
271+
* (especially common during the unstable period after device wake), the flash storage can
272+
* become corrupted. The underlying flash_cache layer does not properly handle write errors,
273+
* leading to partial writes being silently accepted as successful.
274+
*
275+
* 2. UNNECESSARY FOR HID: For HID devices like keyboards, CCCD values are effectively constant.
276+
* The HID Input Report characteristics MUST have notifications enabled for the device to
277+
* function. Every host will write the same value (0x0001) during connection setup. Saving
278+
* and restoring these values provides no functional benefit.
279+
*
280+
* 3. WAKE-UP VULNERABILITY: The highest risk period is immediately after device wake from sleep.
281+
* During this time:
282+
* - The BLE connection is re-establishing and unstable
283+
* - The host automatically re-writes CCCD values to restore notifications
284+
* - These writes trigger flash operations during the most vulnerable period
285+
* - Disconnections are common as connection parameters are renegotiated
286+
*
287+
* 4. MINIMAL IMPACT: Other services that might use CCCDs (Battery Service, DFU, etc.) will
288+
* simply have their notifications re-enabled by the host on each connection. This happens
289+
* automatically and quickly, with no user-visible impact.
290+
*
291+
* TRADE-OFFS:
292+
* - Benefit: Eliminates a major source of flash corruption
293+
* - Benefit: Reduces unnecessary flash wear
294+
* - Benefit: Simpler, more reliable operation
295+
* - Cost: Theoretical ~1-2 second delay for non-HID notification enable (negligible in practice)
296+
*
297+
* FUTURE CONSIDERATIONS:
298+
* If CCCD persistence becomes necessary for specific use cases, implement:
299+
* 1. Proper error handling in the flash_cache layer
300+
* 2. Connection stability checks before allowing writes
301+
* 3. Write verification and retry logic
302+
* 4. Selective saving only for non-HID services
303+
*/
304+
305+
// Return true to indicate "success" - prevents error propagation while doing nothing
306+
return true;
307+
308+
// Original implementation disabled:
309+
// return ada_callback(id_addr, sizeof(ble_gap_addr_t), bond_save_cccd_dfr, role, conn_hdl, id_addr);
263310
}
264311

265312
bool bond_load_cccd(uint8_t role, uint16_t conn_hdl, ble_gap_addr_t const* id_addr)
266313
{
314+
/* NOTE: While bond_save_cccd() has been disabled to prevent corruption, we keep bond_load_cccd()
315+
* functional to maintain compatibility with existing stored data and to avoid breaking the
316+
* API for applications that might call this function.
317+
*
318+
* Since we no longer save CCCD data, this function will typically return false (not loaded),
319+
* which causes the BLE stack to use default values. This is the desired behavior - the host
320+
* will simply re-enable any required notifications during connection setup.
321+
*
322+
* Keeping this function operational also provides a migration path: devices with existing
323+
* stored CCCD data will load it one final time, after which no new data will be saved.
324+
*/
325+
267326
bool loaded = false;
268327

269328
char filename[BOND_FNAME_LEN];

libraries/Bluefruit52Lib/src/utility/bonding.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ void bond_remove_key(uint8_t role, ble_gap_addr_t const* id_addr);
6262
bool bond_save_keys (uint8_t role, uint16_t conn_hdl, bond_keys_t const* bkeys);
6363
bool bond_load_keys(uint8_t role, ble_gap_addr_t* peer_addr, bond_keys_t* bkeys);
6464

65+
// CCCD persistence functions - save is disabled to prevent flash corruption
66+
// See implementation in bonding.cpp for detailed explanation
6567
bool bond_save_cccd (uint8_t role, uint16_t conn_hdl, ble_gap_addr_t const* id_addr);
6668
bool bond_load_cccd (uint8_t role, uint16_t conn_hdl, ble_gap_addr_t const* id_addr);
6769

0 commit comments

Comments
 (0)