Skip to content

[Bugfix] Missing notification data when length > 255 bytes#403

Merged
h2zero merged 1 commit intomasterfrom
bugfix/notify-data-len
Mar 17, 2026
Merged

[Bugfix] Missing notification data when length > 255 bytes#403
h2zero merged 1 commit intomasterfrom
bugfix/notify-data-len

Conversation

@h2zero
Copy link
Owner

@h2zero h2zero commented Mar 16, 2026

When the ACL buffer is less than the MTU, the data arrives in more than one mbuf. This combines the data from the mbuf chain and stores it before calling the appliation callback, ensuring it has all the data.

Summary by CodeRabbit

  • Bug Fixes
    • More robust assembly of fragmented BLE notifications so callbacks receive complete messages.
    • Stronger validation with clear error reporting for unknown handles, length mismatches, and overflow conditions.
    • Callbacks invoked only after successful full-data assembly to avoid partial delivery.
    • Streamlined logging for clearer diagnostics when notification data or handles are invalid.

@coderabbitai
Copy link

coderabbitai bot commented Mar 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Arr: client notification flow now finds the matching remote characteristic, assembles multi-fragment mbuf payloads into one buffer, validates lengths (returns ATT errors on mismatch), logs unknown handles, and invokes the characteristic's notify callback only after full assembly.

Changes

Cohort / File(s) Summary
BLE Notification Assembly
src/NimBLEClient.cpp
Reworked notification path to verify connection handle, retrieve the matching NimBLERemoteCharacteristic via getCharacteristic, assemble fragmented mbuf data into a single value, validate final length (set BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN on mismatch/overflow), log unknown-handle warnings, and invoke per-characteristic notify callback only after successful assembly. Also trimmed newline/log formatting on disconnect handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

Arr, the fragments came rollin' from the deep,
Stitched into one chest where the whole secrets keep,
The lengths be checked, the errors shown true,
Only whole spoils be given to crew,
Yarrr — the callback fires, the message be reap'd!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main bugfix: handling notification data that exceeds 255 bytes by properly assembling fragmented mbuf data.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bugfix/notify-data-len
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/NimBLEClient.cpp (1)

1054-1077: Optional DRY-up: reuse the handle lookup helper.

Arr matey, this block re-implements characteristic lookup that NimBLEClient::getCharacteristic(uint16_t) already provides. Reusin’ it would reduce traversal duplication and maintenance risk.

♻️ Suggested simplification
-            for (const auto& svc : pClient->m_svcVec) {
-                // Dont waste cycles searching services without this handle in its range
-                if (svc->getEndHandle() < event->notify_rx.attr_handle) {
-                    continue;
-                }
-                ...
-                NimBLERemoteCharacteristic* pChr = nullptr;
-                for (const auto& chr : svc->m_vChars) {
-                    if (chr->getHandle() == event->notify_rx.attr_handle) {
-                        pChr = chr;
-                        break;
-                    }
-                }
-                if (pChr == nullptr) {
-                    continue;
-                }
+            NimBLERemoteCharacteristic* pChr = pClient->getCharacteristic(event->notify_rx.attr_handle);
+            if (pChr == nullptr) {
+                return 0;
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NimBLEClient.cpp` around lines 1054 - 1077, This block duplicates
characteristic lookup logic; replace the inner service+characteristic traversal
with a call to NimBLEClient::getCharacteristic(uint16_t) using
event->notify_rx.attr_handle to find the characteristic. Specifically, in the
loop over pClient->m_svcVec (and before logging "Got Notification..."), call
getCharacteristic(event->notify_rx.attr_handle) to obtain a
NimBLERemoteCharacteristic* (check for nullptr) and then proceed with the
existing notification handling; remove the nested for-loop that searches
svc->m_vChars and its manual assignment to pChr to avoid duplicated traversal.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/NimBLEClient.cpp`:
- Around line 1054-1077: This block duplicates characteristic lookup logic;
replace the inner service+characteristic traversal with a call to
NimBLEClient::getCharacteristic(uint16_t) using event->notify_rx.attr_handle to
find the characteristic. Specifically, in the loop over pClient->m_svcVec (and
before logging "Got Notification..."), call
getCharacteristic(event->notify_rx.attr_handle) to obtain a
NimBLERemoteCharacteristic* (check for nullptr) and then proceed with the
existing notification handling; remove the nested for-loop that searches
svc->m_vChars and its manual assignment to pChr to avoid duplicated traversal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 587ed991-f1fb-426d-b5d7-4a616b8789d4

📥 Commits

Reviewing files that changed from the base of the PR and between 42c33fc and f3329c2.

📒 Files selected for processing (1)
  • src/NimBLEClient.cpp

@h2zero h2zero force-pushed the bugfix/notify-data-len branch 2 times, most recently from 5933942 to c348a7d Compare March 16, 2026 23:34
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/NimBLEClient.cpp (1)

1057-1074: Hoist this handle lookup to one code path, matey.

This reimplements NimBLEClient::getCharacteristic(uint16_t) and can drift over time. Reusing the helper (or checking both start/end bounds) keeps lookup logic consistent and cheaper to maintain.

⚓ Suggested refactor
-            NimBLERemoteCharacteristic* pChr = nullptr;
-            for (const auto& svc : pClient->m_svcVec) {
-                // Dont waste cycles searching services without this handle in its range
-                if (svc->getEndHandle() < event->notify_rx.attr_handle) {
-                    continue;
-                }
-
-                for (const auto& chr : svc->m_vChars) {
-                    if (chr->getHandle() == event->notify_rx.attr_handle) {
-                        NIMBLE_LOGD(LOG_TAG, "Got Notification for characteristic %s", chr->toString().c_str());
-                        pChr = chr;
-                        break;
-                    }
-                }
-
-                if (pChr == nullptr) {
-                    continue;
-                }
+            NimBLERemoteCharacteristic* pChr = pClient->getCharacteristic(event->notify_rx.attr_handle);
+            if (pChr != nullptr) {
+                NIMBLE_LOGD(LOG_TAG, "Got Notification for characteristic %s", pChr->toString().c_str());
                 ...
-
-                break;
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NimBLEClient.cpp` around lines 1057 - 1074, The handle lookup loop
duplicates NimBLEClient::getCharacteristic(uint16_t); replace the manual
iteration over pClient->m_svcVec and per-service m_vChars with a single call to
pClient->getCharacteristic(event->notify_rx.attr_handle), assign the returned
NimBLERemoteCharacteristic* to pChr, and then proceed to the existing
notification handling (including the NIMBLE_LOGD call and null check). If you
prefer to keep inline logic instead, make it consistent by checking both service
start and end bounds (using service->getStartHandle() and
service->getEndHandle()) before scanning m_vChars to match getCharacteristic’s
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/NimBLEClient.cpp`:
- Around line 1057-1074: The handle lookup loop duplicates
NimBLEClient::getCharacteristic(uint16_t); replace the manual iteration over
pClient->m_svcVec and per-service m_vChars with a single call to
pClient->getCharacteristic(event->notify_rx.attr_handle), assign the returned
NimBLERemoteCharacteristic* to pChr, and then proceed to the existing
notification handling (including the NIMBLE_LOGD call and null check). If you
prefer to keep inline logic instead, make it consistent by checking both service
start and end bounds (using service->getStartHandle() and
service->getEndHandle()) before scanning m_vChars to match getCharacteristic’s
behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4afae33b-58e9-4692-bd26-f55aeaaf4803

📥 Commits

Reviewing files that changed from the base of the PR and between 5933942 and c348a7d.

📒 Files selected for processing (1)
  • src/NimBLEClient.cpp

@h2zero h2zero force-pushed the bugfix/notify-data-len branch from c348a7d to 3762eaa Compare March 16, 2026 23:49
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/NimBLEClient.cpp (1)

1063-1083: Arrr, avoid partial cache writes when fragment assembly fails.

Right now pChr->m_value is mutated as fragments arrive; on error, a partial value can remain cached. Assemble in a temporary and assign only after full validation.

🏴‍☠️ Proposed refactor
-            auto len = event->notify_rx.om->om_len;
-            if (pChr->m_value.setValue(event->notify_rx.om->om_data, len)) {
+            NimBLEAttValue assembled{};
+            auto          len = event->notify_rx.om->om_len;
+            if (assembled.setValue(event->notify_rx.om->om_data, len)) {
                 os_mbuf* next;
                 next = SLIST_NEXT(event->notify_rx.om, om_next);
                 while (next != NULL) {
-                    pChr->m_value.append(next->om_data, next->om_len);
-                    if (pChr->m_value.length() != len + next->om_len) {
+                    assembled.append(next->om_data, next->om_len);
+                    if (assembled.length() != len + next->om_len) {
                         rc = BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
                         break;
                     }
                     len  += next->om_len;
                     next  = SLIST_NEXT(next, om_next);
                 }
             } else {
                 rc = BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
             }

             if (rc != 0) { // This should never happen
                 NIMBLE_LOGE(LOG_TAG, "notification value error; exceeds limit");
                 return rc;
             }
+
+            pChr->m_value.setValue(assembled.getValue().data(), assembled.length());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NimBLEClient.cpp` around lines 1063 - 1083, The code currently writes
fragments directly into pChr->m_value during assembly and may leave a partial
value on error; instead, allocate or use a temporary container (e.g., a local
os_mbuf-like buffer or a temporary m_value copy) and perform setValue/append
operations against that temporary using event->notify_rx.om, SLIST_NEXT,
setValue, append and length checks; only if assembly and length validation
succeed, assign or swap the temporary into pChr->m_value atomically; on any
error return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN without mutating the original
pChr->m_value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/NimBLEClient.cpp`:
- Around line 1063-1083: The code currently writes fragments directly into
pChr->m_value during assembly and may leave a partial value on error; instead,
allocate or use a temporary container (e.g., a local os_mbuf-like buffer or a
temporary m_value copy) and perform setValue/append operations against that
temporary using event->notify_rx.om, SLIST_NEXT, setValue, append and length
checks; only if assembly and length validation succeed, assign or swap the
temporary into pChr->m_value atomically; on any error return
BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN without mutating the original pChr->m_value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8b25ea70-b836-42be-9790-659489939cf9

📥 Commits

Reviewing files that changed from the base of the PR and between c348a7d and 3762eaa.

📒 Files selected for processing (1)
  • src/NimBLEClient.cpp

When the ACL buffer is less than the MTU, the data arrives in more than one mbuf.
This combines the data from the mbuf chain and stores it before calling the appliation callback, ensuring it has all the data.
@h2zero h2zero force-pushed the bugfix/notify-data-len branch from 3762eaa to 52598b1 Compare March 17, 2026 00:01
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/NimBLEClient.cpp (1)

1086-1090: Arr, ye spotted treasure to plunder later, matey!

The const_cast here be a bit like patchin' yer hull with barnacles - it works but ain't pretty. The TODO comment shows ye be aware this callback interface needs modernizin' to use NimBLEAttValue directly.

Would ye like me to hoist the flag and open an issue to track this here technical debt fer a future voyage?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NimBLEClient.cpp` around lines 1086 - 1090, The code is using const_cast
to pass raw pointer/length into pChr->m_notifyCallback; instead update the
notify callback to accept a NimBLEAttValue (or const NimBLEAttValue&) instead of
raw data/length, update the callback type/registration where m_notifyCallback is
defined, and change the call site to pass pChr->m_value directly (removing
const_cast and uses of m_value.getValue().data() / m_value.length()); ensure all
places that set or implement m_notifyCallback are updated to the new signature.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/NimBLEClient.cpp`:
- Around line 1086-1090: The code is using const_cast to pass raw pointer/length
into pChr->m_notifyCallback; instead update the notify callback to accept a
NimBLEAttValue (or const NimBLEAttValue&) instead of raw data/length, update the
callback type/registration where m_notifyCallback is defined, and change the
call site to pass pChr->m_value directly (removing const_cast and uses of
m_value.getValue().data() / m_value.length()); ensure all places that set or
implement m_notifyCallback are updated to the new signature.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ff6387e9-db49-4a87-867d-63de49a86cc6

📥 Commits

Reviewing files that changed from the base of the PR and between 3762eaa and 52598b1.

📒 Files selected for processing (1)
  • src/NimBLEClient.cpp

@h2zero h2zero merged commit c731adc into master Mar 17, 2026
66 checks passed
@h2zero h2zero deleted the bugfix/notify-data-len branch March 17, 2026 00:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant