Skip to content

Commit a7e6a09

Browse files
committed
Add bond conversion API between v1 and v2 bond data preserving old bonds
1 parent 6854785 commit a7e6a09

2 files changed

Lines changed: 339 additions & 0 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* NimBLE_Bond_Migration Demo:
3+
*
4+
* This example demonstrates running one-time bond-store migration before
5+
* NimBLE initialization. Set NIMBLE_BOND_MIGRATION_MODE to select direction:
6+
* 0 = off, 1 = migrate to current, 2 = migrate to v1.
7+
*/
8+
9+
#include <Arduino.h>
10+
#include <NimBLEDevice.h>
11+
#include <NimBLEBondMigration.h>
12+
13+
namespace NimBLE_Bond_Migration = NimBLEBondMigration;
14+
15+
#ifndef NIMBLE_BOND_MIGRATION_MODE
16+
#define NIMBLE_BOND_MIGRATION_MODE 0
17+
#endif
18+
19+
#if (NIMBLE_BOND_MIGRATION_MODE < 0) || (NIMBLE_BOND_MIGRATION_MODE > 2)
20+
#error "NIMBLE_BOND_MIGRATION_MODE must be 0 (off), 1 (to current), or 2 (to v1)"
21+
#endif
22+
23+
void setup() {
24+
Serial.begin(115200);
25+
Serial.println("Starting NimBLE_Bond_Migration Demo");
26+
27+
#if NIMBLE_BOND_MIGRATION_MODE == 1
28+
{
29+
esp_err_t rc = NimBLE_Bond_Migration::migrateBondStoreToCurrent();
30+
Serial.printf("NimBLE_Bond_Migration to current rc=%d\n", static_cast<int>(rc));
31+
}
32+
#elif NIMBLE_BOND_MIGRATION_MODE == 2
33+
{
34+
esp_err_t rc = NimBLE_Bond_Migration::migrateBondStoreToV1();
35+
Serial.printf("NimBLE_Bond_Migration to v1 rc=%d\n", static_cast<int>(rc));
36+
if (rc == ESP_OK) {
37+
Serial.println("NimBLE_Bond_Migration completed to v1, upload v1 firmware to continue");
38+
while (true) {
39+
delay(1000);
40+
}
41+
}
42+
}
43+
#endif
44+
45+
NimBLEDevice::init("NimBLE");
46+
NimBLEDevice::setPower(3); /** +3db */
47+
48+
NimBLEDevice::setSecurityAuth(true, true, false); /** bonding, MITM, don't need BLE secure connections as we are using passkey pairing */
49+
NimBLEDevice::setSecurityPasskey(123456);
50+
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY); /** Display only passkey */
51+
NimBLEServer* pServer = NimBLEDevice::createServer();
52+
NimBLEService* pService = pServer->createService("ABCD");
53+
NimBLECharacteristic* pNonSecureCharacteristic = pService->createCharacteristic("1234", NIMBLE_PROPERTY::READ);
54+
NimBLECharacteristic* pSecureCharacteristic =
55+
pService->createCharacteristic("1235",
56+
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::READ_AUTHEN);
57+
58+
pNonSecureCharacteristic->setValue("Hello Non Secure BLE");
59+
pSecureCharacteristic->setValue("Hello Secure BLE");
60+
61+
NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
62+
pAdvertising->addServiceUUID("ABCD");
63+
pAdvertising->start();
64+
}
65+
66+
void loop() {}

src/NimBLEBondMigration.h

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
#pragma once
2+
3+
#if !defined(ESP_PLATFORM)
4+
# error "NimBLEBondMigration.h currently requires ESP_PLATFORM (NVS backend)."
5+
#endif
6+
7+
#include <stdint.h>
8+
#include <stdio.h>
9+
#include <string.h>
10+
11+
#include "esp_err.h"
12+
#include "esp_log.h"
13+
#include "nvs.h"
14+
#include "syscfg/syscfg.h"
15+
16+
namespace NimBLEBondMigration {
17+
18+
namespace detail {
19+
20+
static constexpr const char* kLogTag = "NimBLEBondMigration";
21+
static constexpr const char* kBondNamespace = "nimble_bond";
22+
static constexpr const char* kOurSecPrefix = "our_sec";
23+
static constexpr const char* kPeerSecPrefix = "peer_sec";
24+
static constexpr size_t kNvsKeyMaxLen = 16;
25+
26+
typedef struct {
27+
uint8_t type;
28+
uint8_t val[6];
29+
} ble_addr_t;
30+
31+
struct BleStoreValueSecV1 {
32+
ble_addr_t peer_addr;
33+
34+
uint8_t key_size;
35+
uint16_t ediv;
36+
uint64_t rand_num;
37+
uint8_t ltk[16];
38+
uint8_t ltk_present : 1;
39+
40+
uint8_t irk[16];
41+
uint8_t irk_present : 1;
42+
43+
uint8_t csrk[16];
44+
uint8_t csrk_present : 1;
45+
46+
unsigned authenticated : 1;
47+
uint8_t sc : 1;
48+
};
49+
50+
struct BleStoreValueSecCurrent {
51+
ble_addr_t peer_addr;
52+
uint16_t bond_count;
53+
54+
uint8_t key_size;
55+
uint16_t ediv;
56+
uint64_t rand_num;
57+
uint8_t ltk[16];
58+
uint8_t ltk_present : 1;
59+
60+
uint8_t irk[16];
61+
uint8_t irk_present : 1;
62+
63+
uint8_t csrk[16];
64+
uint8_t csrk_present : 1;
65+
uint32_t sign_counter;
66+
67+
unsigned authenticated : 1;
68+
uint8_t sc : 1;
69+
};
70+
71+
struct MigrationStats {
72+
uint16_t scanned;
73+
uint16_t converted;
74+
uint16_t alreadyTarget;
75+
uint16_t skippedUnknownSize;
76+
};
77+
78+
inline bool makeBondKey(char* out, size_t outLen, const char* prefix, uint16_t index) {
79+
const int written = snprintf(out, outLen, "%s_%u", prefix, index);
80+
return written > 0 && static_cast<size_t>(written) < outLen;
81+
}
82+
83+
inline esp_err_t migrateEntryToCurrent(nvs_handle_t nvsHandle,
84+
const char* key,
85+
uint16_t index,
86+
MigrationStats* stats) {
87+
size_t blobSize = 0;
88+
esp_err_t err = nvs_get_blob(nvsHandle, key, nullptr, &blobSize);
89+
if (err == ESP_ERR_NVS_NOT_FOUND) {
90+
return ESP_OK;
91+
}
92+
if (err != ESP_OK) {
93+
return err;
94+
}
95+
96+
stats->scanned++;
97+
98+
if (blobSize == sizeof(BleStoreValueSecCurrent)) {
99+
stats->alreadyTarget++;
100+
return ESP_OK;
101+
}
102+
103+
if (blobSize != sizeof(BleStoreValueSecV1)) {
104+
stats->skippedUnknownSize++;
105+
ESP_LOGW(kLogTag,
106+
"Skipping key=%s due to unexpected size=%u",
107+
key,
108+
static_cast<unsigned>(blobSize));
109+
return ESP_OK;
110+
}
111+
112+
BleStoreValueSecV1 oldValue{};
113+
size_t readSize = sizeof(oldValue);
114+
err = nvs_get_blob(nvsHandle, key, &oldValue, &readSize);
115+
if (err != ESP_OK) {
116+
return err;
117+
}
118+
119+
BleStoreValueSecCurrent newValue{};
120+
newValue.peer_addr = oldValue.peer_addr;
121+
newValue.bond_count = index;
122+
newValue.key_size = oldValue.key_size;
123+
newValue.ediv = oldValue.ediv;
124+
newValue.rand_num = oldValue.rand_num;
125+
memcpy(newValue.ltk, oldValue.ltk, sizeof(newValue.ltk));
126+
newValue.ltk_present = oldValue.ltk_present;
127+
memcpy(newValue.irk, oldValue.irk, sizeof(newValue.irk));
128+
newValue.irk_present = oldValue.irk_present;
129+
memcpy(newValue.csrk, oldValue.csrk, sizeof(newValue.csrk));
130+
newValue.csrk_present = oldValue.csrk_present;
131+
newValue.sign_counter = 0;
132+
newValue.authenticated = oldValue.authenticated;
133+
newValue.sc = oldValue.sc;
134+
135+
err = nvs_set_blob(nvsHandle, key, &newValue, sizeof(newValue));
136+
if (err != ESP_OK) {
137+
return err;
138+
}
139+
140+
stats->converted++;
141+
return ESP_OK;
142+
}
143+
144+
inline esp_err_t migrateEntryToV1(nvs_handle_t nvsHandle,
145+
const char* key,
146+
MigrationStats* stats) {
147+
size_t blobSize = 0;
148+
esp_err_t err = nvs_get_blob(nvsHandle, key, nullptr, &blobSize);
149+
if (err == ESP_ERR_NVS_NOT_FOUND) {
150+
return ESP_OK;
151+
}
152+
if (err != ESP_OK) {
153+
return err;
154+
}
155+
156+
stats->scanned++;
157+
158+
if (blobSize == sizeof(BleStoreValueSecV1)) {
159+
stats->alreadyTarget++;
160+
return ESP_OK;
161+
}
162+
163+
if (blobSize != sizeof(BleStoreValueSecCurrent)) {
164+
stats->skippedUnknownSize++;
165+
ESP_LOGW(kLogTag,
166+
"Skipping key=%s due to unexpected size=%u",
167+
key,
168+
static_cast<unsigned>(blobSize));
169+
return ESP_OK;
170+
}
171+
172+
BleStoreValueSecCurrent curValue{};
173+
size_t readSize = sizeof(curValue);
174+
err = nvs_get_blob(nvsHandle, key, &curValue, &readSize);
175+
if (err != ESP_OK) {
176+
return err;
177+
}
178+
179+
BleStoreValueSecV1 oldValue{};
180+
oldValue.peer_addr = curValue.peer_addr;
181+
oldValue.key_size = curValue.key_size;
182+
oldValue.ediv = curValue.ediv;
183+
oldValue.rand_num = curValue.rand_num;
184+
memcpy(oldValue.ltk, curValue.ltk, sizeof(oldValue.ltk));
185+
oldValue.ltk_present = curValue.ltk_present;
186+
memcpy(oldValue.irk, curValue.irk, sizeof(oldValue.irk));
187+
oldValue.irk_present = curValue.irk_present;
188+
memcpy(oldValue.csrk, curValue.csrk, sizeof(oldValue.csrk));
189+
oldValue.csrk_present = curValue.csrk_present;
190+
oldValue.authenticated = curValue.authenticated;
191+
oldValue.sc = curValue.sc;
192+
193+
err = nvs_set_blob(nvsHandle, key, &oldValue, sizeof(oldValue));
194+
if (err != ESP_OK) {
195+
return err;
196+
}
197+
198+
stats->converted++;
199+
return ESP_OK;
200+
}
201+
202+
inline esp_err_t migrateBondStore(bool toCurrent, uint16_t maxEntries) {
203+
nvs_handle_t nvsHandle;
204+
esp_err_t err = nvs_open(kBondNamespace, NVS_READWRITE, &nvsHandle);
205+
if (err != ESP_OK) {
206+
ESP_LOGE(kLogTag,
207+
"Failed to open NVS namespace '%s', err=%d",
208+
kBondNamespace,
209+
static_cast<int>(err));
210+
return err;
211+
}
212+
213+
MigrationStats stats{};
214+
char key[kNvsKeyMaxLen]{};
215+
216+
for (uint16_t i = 1; i <= maxEntries; ++i) {
217+
if (!makeBondKey(key, sizeof(key), kOurSecPrefix, i)) {
218+
nvs_close(nvsHandle);
219+
return ESP_FAIL;
220+
}
221+
222+
err = toCurrent ? migrateEntryToCurrent(nvsHandle, key, i, &stats)
223+
: migrateEntryToV1(nvsHandle, key, &stats);
224+
if (err != ESP_OK) {
225+
nvs_close(nvsHandle);
226+
return err;
227+
}
228+
229+
if (!makeBondKey(key, sizeof(key), kPeerSecPrefix, i)) {
230+
nvs_close(nvsHandle);
231+
return ESP_FAIL;
232+
}
233+
234+
err = toCurrent ? migrateEntryToCurrent(nvsHandle, key, i, &stats)
235+
: migrateEntryToV1(nvsHandle, key, &stats);
236+
if (err != ESP_OK) {
237+
nvs_close(nvsHandle);
238+
return err;
239+
}
240+
}
241+
242+
if (stats.converted > 0) {
243+
err = nvs_commit(nvsHandle);
244+
if (err != ESP_OK) {
245+
nvs_close(nvsHandle);
246+
return err;
247+
}
248+
}
249+
250+
nvs_close(nvsHandle);
251+
252+
ESP_LOGI(kLogTag,
253+
"Bond migration %s: scanned=%u converted=%u already=%u skipped=%u",
254+
toCurrent ? "to-current" : "to-v1",
255+
stats.scanned,
256+
stats.converted,
257+
stats.alreadyTarget,
258+
stats.skippedUnknownSize);
259+
260+
return ESP_OK;
261+
}
262+
263+
} // namespace detail
264+
265+
inline esp_err_t migrateBondStoreToCurrent(uint16_t maxEntries = MYNEWT_VAL(BLE_STORE_MAX_BONDS)) {
266+
return detail::migrateBondStore(true, maxEntries);
267+
}
268+
269+
inline esp_err_t migrateBondStoreToV1(uint16_t maxEntries = MYNEWT_VAL(BLE_STORE_MAX_BONDS)) {
270+
return detail::migrateBondStore(false, maxEntries);
271+
}
272+
273+
} // namespace NimBLEBondMigration

0 commit comments

Comments
 (0)