adafruit / adafruit/Adafruit_nRF52_Arduino
BLECharacteristic::notify() sends stale buffer data with fixed-length characteristics
- Dominant language
- C
- Stars
- 699
- Forks
- 570
- PR merge metrics
- No merged PRs in 30d
Description
### Operating System
Others
### IDE version
PlatformIO Core, version 6.1.18
### Board
ProMicro NRF52840
### BSP version
nicenano (platform: nordicnrf52; board: adafruit_feather_nrf52840_sense; framework: arduino)
### Sketch
#include
// UUIDs for a simple service and characteristic.
// You can generate your own, but these will work for the demo.
#define BUG_SVC_UUID "adaf-0001-0000-1000-8000-00805f9b34fb"
#define BUG_CHAR_UUID "adaf-0002-0000-1000-8000-00805f9b34fb"
// BLE Service and Characteristic objects
BLEService bugSvc(BUG_SVC_UUID);
BLECharacteristic bugChar(BUG_CHAR_UUID);
void setup() {
Serial.begin(115200);
while (!Serial && millis() < 2000) {
// Wait for serial monitor to open
}
Serial.println("Adafruit Bluefruit Fixed-Length notify() Bug Demo");
Serial.println("-------------------------------------------------");
// Initialize Bluefruit library
Bluefruit.begin();
Bluefruit.setName("NotifyBugDemo");
// Setup the Service
bugSvc.begin();
// Setup the Characteristic - THIS IS THE KEY PART
bugChar.setProperties(CHR_PROPS_NOTIFY);
bugChar.setPermission(SECMODE_OPEN, SECMODE_OPEN);
bugChar.setFixedLen(40); // Configure with a fixed length to trigger the bug
bugChar.begin();
// Setup Advertising
Bluefruit.Advertising.addService(bugSvc);
Bluefruit.Advertising.restartOnDisconnect(true);
Bluefruit.Advertising.setInterval(320, 1600); // 200ms, 1000ms
Bluefruit.Advertising.start(0); // Advertise indefinitely
Serial.println("Ready. Connect with a BLE scanner app (e.g., nRF Connect).");
Serial.println("Enable notifications on the characteristic, then send any character over this serial monitor to trigger the test.");
}
void loop() {
// Wait for a BLE connection
if (!Bluefruit.connected()) {
return;
}
// Wait for a character from the Serial monitor to trigger the test
if (Serial.available() <= 0) {
return;
}
// Read all available characters to clear the buffer
while(Serial.available() > 0) Serial.read();
Serial.println("\n--- TRIGGERING BUG ---");
// 1. Send a long string to fill the BLE stack's internal buffer
const char* long_str = "This_is_a_long_string_to_fill_buffer"; // 38 characters
Serial.print("Sending long string: '");
Serial.print(long_str);
Serial.println("'");
bugChar.notify((uint8_t*) long_str, strlen(long_str));
delay(100); // Small delay between notifications
// 2. Send a short string
const char* short_str = "short"; // 5 characters
Serial.print("Sending short string: '");
Serial.print(short_str);
Serial.println("'");
bugChar.notify((uint8_t*) short_str, strlen(short_str));
Serial.println("--- TEST COMPLETE ---");
Serial.println("Check your BLE scanner. The notification for 'short' will likely be corrupted.");
Serial.println("\nSend another character to repeat the test...");
}
### What happened ?
Summary:
When using BLECharacteristic::notify(data, len) on a characteristic configured with a fixed length (e.g., setFixedLen(40)), the stack appears to transmit the full fixed length of the characteristic, including stale data from its internal buffer from previous, longer transmissions.
Analysis:
The notify() implementation correctly copies the provided len bytes into the stack's internal transmit buffer, but then transmits the full fixed length of the characteristic without clearing or respecting the new, shorter length. This causes stale data from previous calls to leak into the current transmission.
The Adafruit library appears to be a wrapper around the Nordic nRF5 SDK's "SoftDevice," which is the low-level driver for the BLE radio. The function that notify() ultimately calls is sd_ble_gatts_hvx().
This low-level function takes a pointer to the data and a pointer to the length of the data to be sent. The Nordic documentation does not specify that it will send more bytes than requested. This strongly implies that the bug is in the Adafruit library's wrapper code—it's likely ignoring the len parameter provided and instead telling the SoftDevice to send the full, fixed length of the characteristic from its internal, un-sanitized buffer.
Workaround:
The workaround is to manually create a 40-byte buffer in user code, memset it to zero, copy the desired string into it, and then call notify() with a hardcoded length of 40.
Environment:
Board: (ProMicro nRF52840, nice!nano)
IDE: PlatformIO
### How to reproduce ?
Create a characteristic with CHR_PROPS_NOTIFY and setFixedLen(40).
In the loop(), trigger two notify() calls in succession with a short delay.
The first call should send a long string (e.g., 30 bytes).
The second call should send a short string (e.g., 10 bytes).
Use a BLE scanner app (like nRF Connect) to observe the notifications.
Expected Behavior:
The second notification packet should contain only the 10 bytes of the short string.
Actual Behavior:
The second notification packet contains the 10 bytes of the short string, immediately followed by the trailing 20 bytes from the first, longer string.
### Debug Log
_No response_
### Screenshots
_No response_
Contributor guide
No contributing guide indexed for this repository
Research direction
Start at BLECharacteristic::notify(data, len) and trace the length passed to the Nordic sd_ble_gatts_hvx() call. Reproduce the issue with a fixed-length characteristic, send the long then short payloads, and confirm that the second notification contains only the requested bytes without stale trailing data.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- arduino, cpp
- Domain
- embedded-iot
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100