Skip to content

Add bond conversion API between v1 and v2 bond data preserving old bonds#1121

Merged
h2zero merged 3 commits into
masterfrom
bond-migration-api
Mar 19, 2026
Merged

Add bond conversion API between v1 and v2 bond data preserving old bonds#1121
h2zero merged 3 commits into
masterfrom
bond-migration-api

Conversation

@h2zero
Copy link
Copy Markdown
Owner

@h2zero h2zero commented Mar 18, 2026

Fixes #740

Summary by CodeRabbit

  • New Features
    • Example sketch to perform a one-time bond-store migration before BLE initialization, with selectable modes (off / migrate-to-current / migrate-to-legacy) and optional halt-after-success behavior.
    • Bond migration utilities providing bidirectional conversion between legacy and current bond store layouts, plus a diagnostic dump and summary reporting for safe, visible migration feedback.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

Arr, this PR adds a bidirectional NimBLE bond-store migration utility (V1 ⇄ current) and a new Arduino example that can run a one‑time migration before NimBLE initialization, then configures BLE security, creates a service/characteristics, and starts advertising.

Changes

Cohort / File(s) Summary
Bond Migration Core
src/NimBLEBondMigration.h
Adds a self-contained bond migration utility: V1 and current bond structures, NVS iteration/commit logic for our_sec and peer_sec, local IRK handling, diagnostics/dump helpers, logging, and public APIs migrateBondStoreToCurrent(), migrateBondStoreToV1(), and dumpBondData().
Migration Example
examples/NimBLE_Bond_Migration/NimBLE_Bond_Migration.ino
New Arduino sketch demonstrating configurable one-time bond migration modes (0=off,1=migrate→current,2=migrate→V1), conditional migration invocation with logs and halt-on-success, NimBLE init, bonding/MITM/passkey security setup, service/characteristic creation (secure + non-secure), and advertising start.

Sequence Diagram(s)

sequenceDiagram
    participant Arduino as Arduino Setup
    participant Migration as NimBLEBondMigration
    participant NVS as NVS Storage
    participant NimBLE as NimBLE Core

    Arduino->>Migration: call migrateBondStoreToCurrent()/migrateBondStoreToV1()
    Migration->>NVS: nvs_open(namespace)
    loop for each index 0..maxEntries
        NVS->>Migration: nvs_get_blob(key_our_sec / key_peer_sec)
        alt entry found & convertible
            Migration->>Migration: parse & convert blob (V1 ⇄ current)
            Migration->>NVS: nvs_set_blob(new_key, converted_blob)
        else skip or invalid
            Migration->>Migration: record skip/unknown
        end
    end
    Migration->>NVS: nvs_commit() (if changes)
    Migration->>NVS: nvs_close()
    Migration->>Arduino: return success/failure
    alt migration not performed or failed
        Arduino->>NimBLE: NimBLEDevice::init() and normal startup
    else migration succeeded
        Arduino-->>Arduino: log + halt for user action
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

Yarrr, old bonds beneath the foam we pry,
We map their souls, convert 'em by and by.
One-time rite afore NimBLE wakes its call,
Then services and adverts set to brawl.
Avast — the fleet of pairs now stands tall!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: adding a bond conversion API between v1 and v2 formats with preservation of existing bonds.
Linked Issues check ✅ Passed The PR implements bidirectional migration functions (migrateBondStoreToCurrent and migrateBondStoreToV1) with IRK handling and diagnostic tools addressing issue #740's bond compatibility problems.
Out of Scope Changes check ✅ Passed All changes are within scope: the migration header with bidirectional conversion logic and the example sketch demonstrating conditional migration execution align with the stated objectives.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bond-migration-api
📝 Coding Plan
  • Generate coding plan for human review comments

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

Copy link
Copy Markdown

@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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/NimBLEBondMigration.h (1)

202-261: The resource cleanup be correct but could use some streamlinin', matey!

Arr, ye be callin' nvs_close(nvsHandle) on every error path like a responsible sailor. However, the repeated pattern be makin' the code a bit barnacle-encrusted. Consider a simple RAII wrapper or scope guard to reduce duplication.

🏴‍☠️ Optional: RAII cleanup fer cleaner waters
// Simple scope guard approach:
struct NvsHandleGuard {
    nvs_handle_t handle;
    ~NvsHandleGuard() { nvs_close(handle); }
};

inline esp_err_t migrateBondStore(bool toCurrent, uint16_t maxEntries) {
    nvs_handle_t nvsHandle;
    esp_err_t err = nvs_open(kBondNamespace, NVS_READWRITE, &nvsHandle);
    if (err != ESP_OK) {
        ESP_LOGE(kLogTag, "Failed to open NVS namespace '%s', err=%d",
                 kBondNamespace, static_cast<int>(err));
        return err;
    }
    NvsHandleGuard guard{nvsHandle};
    
    // ... rest of the function with simple `return err;` statements ...
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NimBLEBondMigration.h` around lines 202 - 261, The function
migrateBondStore repeatedly calls nvs_close(nvsHandle) on every error path;
introduce a simple RAII/scope-guard (e.g., struct NvsHandleGuard { nvs_handle_t
handle; ~NvsHandleGuard() { nvs_close(handle); } }) right after successful
nvs_open and initialize it with nvsHandle, then remove all explicit nvs_close
calls throughout migrateBondStore so that the guard always closes the handle on
return; keep the existing nvs_commit usage and return behavior unchanged, and
place the NvsHandleGuard definition in the same translation unit (or a shared
header) so migrateBondStore can use it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/NimBLE_Bond_Migration/NimBLE_Bond_Migration.ino`:
- Around line 32-43: When NIMBLE_BOND_MIGRATION_MODE == 2 the code currently
only halts on success; if migrateBondStoreToV1() returns rc != ESP_OK you must
prevent continuing with NimBLE initialization. Modify the block around
NimBLE_Bond_Migration::migrateBondStoreToV1() to detect rc != ESP_OK, log a
clear error (using Serial.printf/println with the rc value and a descriptive
message), and stop further execution (e.g., enter an infinite delay loop) so the
system does not proceed to initialize NimBLE with a failed/partial bond store;
ensure you reference migrateBondStoreToV1(), the rc variable, and ESP_OK in your
change.

In `@src/NimBLEBondMigration.h`:
- Around line 31-69: The BleStoreValueSecV1 and BleStoreValueSecCurrent structs
are vulnerable to compiler padding changes; make their on-disk layout explicit
by applying packing (e.g., wrap definitions with `#pragma` pack(push,1)/#pragma
pack(pop) or add __attribute__((packed)) to each struct) and update any
sizeof()-based version detection to use the packed sizes; alternatively (or
additionally) implement a versioned blob header (magic, version, payload_size)
prepended to stored bond blobs and validate the header before casting bytes to
BleStoreValueSecV1/BleStoreValueSecCurrent to avoid silent corruption across
builds.

---

Nitpick comments:
In `@src/NimBLEBondMigration.h`:
- Around line 202-261: The function migrateBondStore repeatedly calls
nvs_close(nvsHandle) on every error path; introduce a simple RAII/scope-guard
(e.g., struct NvsHandleGuard { nvs_handle_t handle; ~NvsHandleGuard() {
nvs_close(handle); } }) right after successful nvs_open and initialize it with
nvsHandle, then remove all explicit nvs_close calls throughout migrateBondStore
so that the guard always closes the handle on return; keep the existing
nvs_commit usage and return behavior unchanged, and place the NvsHandleGuard
definition in the same translation unit (or a shared header) so migrateBondStore
can use it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 00d8e269-3224-4f18-b19e-f3e0a11d836b

📥 Commits

Reviewing files that changed from the base of the PR and between 6854785 and a7e6a09.

📒 Files selected for processing (2)
  • examples/NimBLE_Bond_Migration/NimBLE_Bond_Migration.ino
  • src/NimBLEBondMigration.h

Comment thread examples/NimBLE_Bond_Migration/NimBLE_Bond_Migration.ino Outdated
Comment thread src/NimBLEBondMigration.h
@h2zero h2zero force-pushed the bond-migration-api branch from a7e6a09 to 086a8d9 Compare March 18, 2026 20:43
Copy link
Copy Markdown

@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/NimBLEBondMigration.h (1)

81-90: Arr, a minor treasure to polish here, cap'n!

In appendLine, ye be callin' strlen(buf) when written already tells ye the length (if not truncated). A small optimization to avoid the extra voyage through the string:

🏴‍☠️ Optional polish for efficiency
 inline void appendLine(std::string& out, const char* fmt, ...) {
     char    buf[256];
     va_list args;
     va_start(args, fmt);
     const int written = vsnprintf(buf, sizeof(buf), fmt, args);
     va_end(args);
     if (written > 0) {
-        out.append(buf, strlen(buf));
+        out.append(buf, static_cast<size_t>(written) < sizeof(buf) ? written : sizeof(buf) - 1);
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NimBLEBondMigration.h` around lines 81 - 90, The appendLine function
currently appends using strlen(buf) which redundantly scans the buffer; change
it to use the vsnprintf return value `written` to determine how many bytes to
append: append `written` when written is non-negative and less than the buffer
size, otherwise append the truncated length (buffer size minus one). Update the
append in appendLine to calculate the length via written vs. sizeof(buf) to
avoid the extra strlen call and to correctly handle truncation cases.
🤖 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/NimBLEBondMigration.h`:
- Around line 81-90: The appendLine function currently appends using strlen(buf)
which redundantly scans the buffer; change it to use the vsnprintf return value
`written` to determine how many bytes to append: append `written` when written
is non-negative and less than the buffer size, otherwise append the truncated
length (buffer size minus one). Update the append in appendLine to calculate the
length via written vs. sizeof(buf) to avoid the extra strlen call and to
correctly handle truncation cases.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9355dbf7-0379-4cf3-be55-f435f0ce95c0

📥 Commits

Reviewing files that changed from the base of the PR and between a7e6a09 and 1a97425.

📒 Files selected for processing (2)
  • examples/NimBLE_Bond_Migration/NimBLE_Bond_Migration.ino
  • src/NimBLEBondMigration.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/NimBLE_Bond_Migration/NimBLE_Bond_Migration.ino

Copy link
Copy Markdown

@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 (2)
src/NimBLEBondMigration.h (2)

404-486: Arr, the resource cleanup be scattered across the seven seas!

The nvs_close(nvsHandle) call appears at every early-return point (lines 420, 426, 431, 437, 444, 449, 455, 460, 470). While functionally correct, this pattern be prone to forgettin' a cleanup on future modifications. A simple RAII wrapper or scope guard would consolidate the cleanup, ye savvy?

🧹 Optional RAII cleanup pattern
// At the top of the function, after nvs_open succeeds:
struct NvsGuard {
    nvs_handle_t h;
    ~NvsGuard() { nvs_close(h); }
} guard{nvsHandle};

// Then all returns can simply `return false;` or `return true;`
// without explicit nvs_close calls.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NimBLEBondMigration.h` around lines 404 - 486, The function
migrateBondStore currently calls nvs_close(nvsHandle) at every early return;
introduce a RAII scope guard immediately after successful nvs_open to ensure
nvs_close always runs (e.g., a small local struct NvsGuard with nvs_handle_t h
and a destructor that calls nvs_close(h)), remove all explicit nvs_close calls
in migrateBondStore so early returns simply return true/false, and keep the
final commit/return logic unchanged; reference migrateBondStore, nvs_open,
nvs_close and the optional NvsGuard wrapper when making the change.

96-105: Consider usin' written directly, ye scallywag!

On line 103, strlen(buf) be redundant when written already tells ye the length. However, I spy the subtlety: when truncation occurs (written >= sizeof(buf)), strlen gives the correct truncated length. If ye want both efficiency and truncation safety, cap the length explicitly:

🔧 Optional efficiency tweak
 inline void appendLine(std::string& out, const char* fmt, ...) {
     char    buf[256];
     va_list args;
     va_start(args, fmt);
     const int written = vsnprintf(buf, sizeof(buf), fmt, args);
     va_end(args);
     if (written > 0) {
-        out.append(buf, strlen(buf));
+        const size_t len = (static_cast<size_t>(written) < sizeof(buf))
+                           ? static_cast<size_t>(written)
+                           : sizeof(buf) - 1;
+        out.append(buf, len);
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NimBLEBondMigration.h` around lines 96 - 105, The appendLine function
currently uses strlen(buf) to determine how many bytes to append, which is
redundant and unsafe; instead use the vsnprintf return value `written` to
compute the append length but cap it to the buffer size to handle truncation and
ignore negative errors: compute a length = (written < 0 ? 0 : (written >=
(int)sizeof(buf) ? (int)sizeof(buf) - 1 : written)) and call out.append(buf,
length) in appendLine (referencing appendLine, buf, and written).
🤖 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/NimBLEBondMigration.h`:
- Around line 404-486: The function migrateBondStore currently calls
nvs_close(nvsHandle) at every early return; introduce a RAII scope guard
immediately after successful nvs_open to ensure nvs_close always runs (e.g., a
small local struct NvsGuard with nvs_handle_t h and a destructor that calls
nvs_close(h)), remove all explicit nvs_close calls in migrateBondStore so early
returns simply return true/false, and keep the final commit/return logic
unchanged; reference migrateBondStore, nvs_open, nvs_close and the optional
NvsGuard wrapper when making the change.
- Around line 96-105: The appendLine function currently uses strlen(buf) to
determine how many bytes to append, which is redundant and unsafe; instead use
the vsnprintf return value `written` to compute the append length but cap it to
the buffer size to handle truncation and ignore negative errors: compute a
length = (written < 0 ? 0 : (written >= (int)sizeof(buf) ? (int)sizeof(buf) - 1
: written)) and call out.append(buf, length) in appendLine (referencing
appendLine, buf, and written).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b9d9ee94-012d-499a-b711-93b8792a3e92

📥 Commits

Reviewing files that changed from the base of the PR and between 1a97425 and c4a23a8.

📒 Files selected for processing (2)
  • examples/NimBLE_Bond_Migration/NimBLE_Bond_Migration.ino
  • src/NimBLEBondMigration.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/NimBLE_Bond_Migration/NimBLE_Bond_Migration.ino

@h2zero
Copy link
Copy Markdown
Owner Author

h2zero commented Mar 19, 2026

@thebentern It looks like this may be of interest to you.

@h2zero h2zero merged commit 592ddb8 into master Mar 19, 2026
42 checks passed
@h2zero h2zero deleted the bond-migration-api branch March 19, 2026 12:46
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.

Bonded clients do not work when switching from 1.4.2 to 2.0.0 or vice versa

1 participant