NimBLE-Arduino 2.4.0
Loading...
Searching...
No Matches
NimBLEBondMigration.h
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#include <stdarg.h>
11#include <string>
12#include <vector>
13
14#include "esp_err.h"
15#include "esp_log.h"
16#include "nvs.h"
17#include "syscfg/syscfg.h"
18
27
28namespace detail {
29
30static constexpr const char* kLogTag = "NimBLEBondMigration";
31static constexpr const char* kBondNamespace = "nimble_bond";
32static constexpr const char* kOurSecPrefix = "our_sec";
33static constexpr const char* kPeerSecPrefix = "peer_sec";
34static constexpr const char* kLocalIrkPrefix = "local_irk";
35static constexpr size_t kNvsKeyMaxLen = 16;
36
37typedef struct {
38 uint8_t type;
39 uint8_t val[6];
40} ble_addr_t;
41
42struct BleStoreValueSecV1 {
43 ble_addr_t peer_addr;
44
45 uint8_t key_size;
46 uint16_t ediv;
47 uint64_t rand_num;
48 uint8_t ltk[16];
49 uint8_t ltk_present : 1;
50
51 uint8_t irk[16];
52 uint8_t irk_present : 1;
53
54 uint8_t csrk[16];
55 uint8_t csrk_present : 1;
56
57 unsigned authenticated : 1;
58 uint8_t sc : 1;
59};
60
61struct BleStoreValueSecCurrent {
62 ble_addr_t peer_addr;
63 uint16_t bond_count;
64
65 uint8_t key_size;
66 uint16_t ediv;
67 uint64_t rand_num;
68 uint8_t ltk[16];
69 uint8_t ltk_present : 1;
70
71 uint8_t irk[16];
72 uint8_t irk_present : 1;
73
74 uint8_t csrk[16];
75 uint8_t csrk_present : 1;
76 uint32_t sign_counter;
77
78 unsigned authenticated : 1;
79 uint8_t sc : 1;
80};
81
82struct BleStoreValueLocalIrkV1 {
83 uint8_t irk[16];
84};
85
86struct BleStoreValueLocalIrkCurrent {
87 ble_addr_t addr;
88 uint8_t irk[16];
89};
90
91static constexpr uint8_t kDefaultLocalIrk[16] = {
92 0xef, 0x8d, 0xe2, 0x16, 0x4f, 0xec, 0x43, 0x0d,
93 0xbf, 0x5b, 0xdd, 0x34, 0xc0, 0x53, 0x1e, 0xb8,
94};
95
96struct MigrationStats {
97 uint16_t scanned;
98 uint16_t converted;
99 uint16_t alreadyTarget;
100 uint16_t skippedUnknownSize;
101};
102
103inline void appendLine(std::string& out, const char* fmt, ...) {
104 char buf[256];
105 va_list args;
106 va_start(args, fmt);
107 const int written = vsnprintf(buf, sizeof(buf), fmt, args);
108 va_end(args);
109 if (written > 0) {
110 out.append(buf, strlen(buf));
111 }
112}
113
114inline void appendHex(std::string& out, const uint8_t* data, size_t len) {
115 static constexpr char hex[] = "0123456789ABCDEF";
116 out.reserve(out.size() + (len * 2));
117 for (size_t i = 0; i < len; ++i) {
118 const uint8_t value = data[i];
119 out.push_back(hex[(value >> 4) & 0x0F]);
120 out.push_back(hex[value & 0x0F]);
121 }
122}
123
124inline void appendAddr(std::string& out, const ble_addr_t& addr) {
125 appendLine(out,
126 "%02X:%02X:%02X:%02X:%02X:%02X(type=%u)",
127 addr.val[5],
128 addr.val[4],
129 addr.val[3],
130 addr.val[2],
131 addr.val[1],
132 addr.val[0],
133 addr.type);
134}
135
136inline void appendV1Record(std::string& out, const BleStoreValueSecV1& sec) {
137 out += " peer_addr=";
138 appendAddr(out, sec.peer_addr);
139 out += "\n";
140 appendLine(out,
141 " key_size=%u ediv=%u rand_num=%llu auth=%u sc=%u\n",
142 sec.key_size,
143 sec.ediv,
144 static_cast<unsigned long long>(sec.rand_num),
145 sec.authenticated,
146 sec.sc);
147 appendLine(out,
148 " ltk_present=%u irk_present=%u csrk_present=%u\n",
149 sec.ltk_present,
150 sec.irk_present,
151 sec.csrk_present);
152 if (sec.ltk_present) {
153 out += " ltk=";
154 appendHex(out, sec.ltk, sizeof(sec.ltk));
155 out += "\n";
156 }
157 if (sec.irk_present) {
158 out += " irk=";
159 appendHex(out, sec.irk, sizeof(sec.irk));
160 out += "\n";
161 }
162 if (sec.csrk_present) {
163 out += " csrk=";
164 appendHex(out, sec.csrk, sizeof(sec.csrk));
165 out += "\n";
166 }
167}
168
169inline void appendCurrentRecord(std::string& out, const BleStoreValueSecCurrent& sec) {
170 out += " peer_addr=";
171 appendAddr(out, sec.peer_addr);
172 out += "\n";
173 appendLine(out,
174 " bond_count=%u sign_counter=%u key_size=%u ediv=%u rand_num=%llu auth=%u sc=%u\n",
175 sec.bond_count,
176 sec.sign_counter,
177 sec.key_size,
178 sec.ediv,
179 static_cast<unsigned long long>(sec.rand_num),
180 sec.authenticated,
181 sec.sc);
182 appendLine(out,
183 " ltk_present=%u irk_present=%u csrk_present=%u\n",
184 sec.ltk_present,
185 sec.irk_present,
186 sec.csrk_present);
187 if (sec.ltk_present) {
188 out += " ltk=";
189 appendHex(out, sec.ltk, sizeof(sec.ltk));
190 out += "\n";
191 }
192 if (sec.irk_present) {
193 out += " irk=";
194 appendHex(out, sec.irk, sizeof(sec.irk));
195 out += "\n";
196 }
197 if (sec.csrk_present) {
198 out += " csrk=";
199 appendHex(out, sec.csrk, sizeof(sec.csrk));
200 out += "\n";
201 }
202}
203
204inline void appendLocalIrkV1Record(std::string& out, const BleStoreValueLocalIrkV1& irkRecord) {
205 out += " irk=";
206 appendHex(out, irkRecord.irk, sizeof(irkRecord.irk));
207 out += "\n";
208}
209
210inline void appendLocalIrkCurrentRecord(std::string& out, const BleStoreValueLocalIrkCurrent& irkRecord) {
211 out += " addr=";
212 appendAddr(out, irkRecord.addr);
213 out += "\n";
214 out += " irk=";
215 appendHex(out, irkRecord.irk, sizeof(irkRecord.irk));
216 out += "\n";
217}
218
219inline bool makeBondKey(char* out, size_t outLen, const char* prefix, uint16_t index) {
220 const int written = snprintf(out, outLen, "%s_%u", prefix, index);
221 return written > 0 && static_cast<size_t>(written) < outLen;
222}
223
224inline bool migrateEntryToCurrent(nvs_handle_t nvsHandle,
225 const char* key,
226 uint16_t index,
227 MigrationStats* stats) {
228 size_t blobSize = 0;
229 esp_err_t err = nvs_get_blob(nvsHandle, key, nullptr, &blobSize);
230 if (err == ESP_ERR_NVS_NOT_FOUND) {
231 return true;
232 }
233 if (err != ESP_OK) {
234 ESP_LOGE(kLogTag, "nvs_get_blob size failed key=%s err=%d", key, static_cast<int>(err));
235 return false;
236 }
237
238 stats->scanned++;
239
240 if (blobSize == sizeof(BleStoreValueSecCurrent)) {
241 stats->alreadyTarget++;
242 return true;
243 }
244
245 if (blobSize != sizeof(BleStoreValueSecV1)) {
246 stats->skippedUnknownSize++;
247 ESP_LOGW(kLogTag,
248 "Skipping key=%s due to unexpected size=%u",
249 key,
250 static_cast<unsigned>(blobSize));
251 return true;
252 }
253
254 BleStoreValueSecV1 oldValue{};
255 size_t readSize = sizeof(oldValue);
256 err = nvs_get_blob(nvsHandle, key, &oldValue, &readSize);
257 if (err != ESP_OK) {
258 ESP_LOGE(kLogTag, "nvs_get_blob value failed key=%s err=%d", key, static_cast<int>(err));
259 return false;
260 }
261
262 BleStoreValueSecCurrent newValue{};
263 newValue.peer_addr = oldValue.peer_addr;
264 newValue.bond_count = index;
265 newValue.key_size = oldValue.key_size;
266 newValue.ediv = oldValue.ediv;
267 newValue.rand_num = oldValue.rand_num;
268 memcpy(newValue.ltk, oldValue.ltk, sizeof(newValue.ltk));
269 newValue.ltk_present = oldValue.ltk_present;
270 memcpy(newValue.irk, oldValue.irk, sizeof(newValue.irk));
271 newValue.irk_present = oldValue.irk_present;
272 memcpy(newValue.csrk, oldValue.csrk, sizeof(newValue.csrk));
273 newValue.csrk_present = oldValue.csrk_present;
274 newValue.sign_counter = 0;
275 newValue.authenticated = oldValue.authenticated;
276 newValue.sc = oldValue.sc;
277
278 err = nvs_set_blob(nvsHandle, key, &newValue, sizeof(newValue));
279 if (err != ESP_OK) {
280 ESP_LOGE(kLogTag, "nvs_set_blob failed key=%s err=%d", key, static_cast<int>(err));
281 return false;
282 }
283
284 stats->converted++;
285 return true;
286}
287
288inline bool migrateEntryToV1(nvs_handle_t nvsHandle,
289 const char* key,
290 MigrationStats* stats) {
291 size_t blobSize = 0;
292 esp_err_t err = nvs_get_blob(nvsHandle, key, nullptr, &blobSize);
293 if (err == ESP_ERR_NVS_NOT_FOUND) {
294 return true;
295 }
296 if (err != ESP_OK) {
297 ESP_LOGE(kLogTag, "nvs_get_blob size failed key=%s err=%d", key, static_cast<int>(err));
298 return false;
299 }
300
301 stats->scanned++;
302
303 if (blobSize == sizeof(BleStoreValueSecV1)) {
304 stats->alreadyTarget++;
305 return true;
306 }
307
308 if (blobSize != sizeof(BleStoreValueSecCurrent)) {
309 stats->skippedUnknownSize++;
310 ESP_LOGW(kLogTag,
311 "Skipping key=%s due to unexpected size=%u",
312 key,
313 static_cast<unsigned>(blobSize));
314 return true;
315 }
316
317 BleStoreValueSecCurrent curValue{};
318 size_t readSize = sizeof(curValue);
319 err = nvs_get_blob(nvsHandle, key, &curValue, &readSize);
320 if (err != ESP_OK) {
321 ESP_LOGE(kLogTag, "nvs_get_blob value failed key=%s err=%d", key, static_cast<int>(err));
322 return false;
323 }
324
325 BleStoreValueSecV1 oldValue{};
326 oldValue.peer_addr = curValue.peer_addr;
327 oldValue.key_size = curValue.key_size;
328 oldValue.ediv = curValue.ediv;
329 oldValue.rand_num = curValue.rand_num;
330 memcpy(oldValue.ltk, curValue.ltk, sizeof(oldValue.ltk));
331 oldValue.ltk_present = curValue.ltk_present;
332 memcpy(oldValue.irk, curValue.irk, sizeof(oldValue.irk));
333 oldValue.irk_present = curValue.irk_present;
334 memcpy(oldValue.csrk, curValue.csrk, sizeof(oldValue.csrk));
335 oldValue.csrk_present = curValue.csrk_present;
336 oldValue.authenticated = curValue.authenticated;
337 oldValue.sc = curValue.sc;
338
339 err = nvs_set_blob(nvsHandle, key, &oldValue, sizeof(oldValue));
340 if (err != ESP_OK) {
341 ESP_LOGE(kLogTag, "nvs_set_blob failed key=%s err=%d", key, static_cast<int>(err));
342 return false;
343 }
344
345 stats->converted++;
346 return true;
347}
348
349inline bool migrateLocalIrkEntryToCurrent(nvs_handle_t nvsHandle,
350 const char* key,
351 MigrationStats* stats) {
352 size_t blobSize = 0;
353 esp_err_t err = nvs_get_blob(nvsHandle, key, nullptr, &blobSize);
354 if (err == ESP_ERR_NVS_NOT_FOUND) {
355 blobSize = 0;
356 } else if (err != ESP_OK) {
357 ESP_LOGE(kLogTag, "nvs_get_blob size failed key=%s err=%d", key, static_cast<int>(err));
358 return false;
359 } else {
360 stats->scanned++;
361 }
362
363 if (blobSize != 0 && blobSize != sizeof(BleStoreValueLocalIrkCurrent) &&
364 blobSize != sizeof(BleStoreValueLocalIrkV1)) {
365 stats->skippedUnknownSize++;
366 ESP_LOGW(kLogTag,
367 "Overwriting key=%s with default local_irk despite unexpected size=%u",
368 key,
369 static_cast<unsigned>(blobSize));
370 }
371
372 BleStoreValueLocalIrkCurrent newValue{};
373 memcpy(newValue.irk, kDefaultLocalIrk, sizeof(newValue.irk));
374
375 err = nvs_set_blob(nvsHandle, key, &newValue, sizeof(newValue));
376 if (err != ESP_OK) {
377 ESP_LOGE(kLogTag, "nvs_set_blob failed key=%s err=%d", key, static_cast<int>(err));
378 return false;
379 }
380
381 stats->converted++;
382 return true;
383}
384
385inline bool migrateLocalIrkEntryToV1(nvs_handle_t nvsHandle,
386 const char* key,
387 MigrationStats* stats) {
388 size_t blobSize = 0;
389 esp_err_t err = nvs_get_blob(nvsHandle, key, nullptr, &blobSize);
390 if (err == ESP_ERR_NVS_NOT_FOUND) {
391 return true;
392 }
393 if (err != ESP_OK) {
394 ESP_LOGE(kLogTag, "nvs_get_blob size failed key=%s err=%d", key, static_cast<int>(err));
395 return false;
396 }
397
398 stats->scanned++;
399
400 // Rollback to v1 should not persist local IRK entries; erase if present.
401 err = nvs_erase_key(nvsHandle, key);
402 if (err != ESP_OK) {
403 ESP_LOGE(kLogTag, "nvs_erase_key failed key=%s err=%d", key, static_cast<int>(err));
404 return false;
405 }
406
407 stats->converted++;
408 return true;
409}
410
411inline bool migrateBondStore(bool toCurrent, uint16_t maxEntries) {
412 nvs_handle_t nvsHandle;
413 esp_err_t err = nvs_open(kBondNamespace, NVS_READWRITE, &nvsHandle);
414 if (err != ESP_OK) {
415 ESP_LOGE(kLogTag,
416 "Failed to open NVS namespace '%s', err=%d",
417 kBondNamespace,
418 static_cast<int>(err));
419 return false;
420 }
421
422 MigrationStats stats{};
423 char key[kNvsKeyMaxLen]{};
424
425 for (uint16_t i = 1; i <= maxEntries; ++i) {
426 if (!makeBondKey(key, sizeof(key), kOurSecPrefix, i)) {
427 nvs_close(nvsHandle);
428 return false;
429 }
430
431 if (!(toCurrent ? migrateEntryToCurrent(nvsHandle, key, i, &stats)
432 : migrateEntryToV1(nvsHandle, key, &stats))) {
433 nvs_close(nvsHandle);
434 return false;
435 }
436
437 if (!makeBondKey(key, sizeof(key), kPeerSecPrefix, i)) {
438 nvs_close(nvsHandle);
439 return false;
440 }
441
442 if (!(toCurrent ? migrateEntryToCurrent(nvsHandle, key, i, &stats)
443 : migrateEntryToV1(nvsHandle, key, &stats))) {
444 nvs_close(nvsHandle);
445 return false;
446 }
447 }
448
449 if (toCurrent) {
450 if (!makeBondKey(key, sizeof(key), kLocalIrkPrefix, 1)) {
451 nvs_close(nvsHandle);
452 return false;
453 }
454
455 if (!migrateLocalIrkEntryToCurrent(nvsHandle, key, &stats)) {
456 nvs_close(nvsHandle);
457 return false;
458 }
459 } else {
460 for (uint16_t i = 1; i <= maxEntries; ++i) {
461 if (!makeBondKey(key, sizeof(key), kLocalIrkPrefix, i)) {
462 nvs_close(nvsHandle);
463 return false;
464 }
465
466 if (!migrateLocalIrkEntryToV1(nvsHandle, key, &stats)) {
467 nvs_close(nvsHandle);
468 return false;
469 }
470 }
471 }
472
473 if (stats.converted > 0) {
474 err = nvs_commit(nvsHandle);
475 if (err != ESP_OK) {
476 ESP_LOGE(kLogTag, "nvs_commit failed err=%d", static_cast<int>(err));
477 nvs_close(nvsHandle);
478 return false;
479 }
480 }
481
482 nvs_close(nvsHandle);
483
484 ESP_LOGI(kLogTag,
485 "Bond migration %s: scanned=%u converted=%u already=%u skipped=%u",
486 toCurrent ? "to-current" : "to-v1",
487 stats.scanned,
488 stats.converted,
489 stats.alreadyTarget,
490 stats.skippedUnknownSize);
491
492 return true;
493}
494
495} // namespace detail
496
508inline std::string dumpBondData(uint16_t maxEntries = MYNEWT_VAL(BLE_STORE_MAX_BONDS)) {
509 std::string out;
510 out.reserve(2048);
511
512 nvs_handle_t nvsHandle;
513 esp_err_t err = nvs_open(detail::kBondNamespace, NVS_READONLY, &nvsHandle);
514 if (err != ESP_OK) {
515 detail::appendLine(out,
516 "Failed to open NVS namespace '%s', err=%d\n",
517 detail::kBondNamespace,
518 static_cast<int>(err));
519 return out;
520 }
521
522 out += "NimBLE bond dump\n";
523 detail::appendLine(out,
524 "v1_size=%u current_size=%u max_entries=%u\n",
525 static_cast<unsigned>(sizeof(detail::BleStoreValueSecV1)),
526 static_cast<unsigned>(sizeof(detail::BleStoreValueSecCurrent)),
527 maxEntries);
528
529 uint16_t foundCount = 0;
530 char key[detail::kNvsKeyMaxLen]{};
531
532 for (uint16_t i = 1; i <= maxEntries; ++i) {
533 const char* prefixes[] = {detail::kOurSecPrefix, detail::kPeerSecPrefix};
534 const char* types[] = {"our", "peer"};
535
536 for (size_t j = 0; j < 2; ++j) {
537 if (!detail::makeBondKey(key, sizeof(key), prefixes[j], i)) {
538 out += "Key format error\n";
539 continue;
540 }
541
542 size_t blobSize = 0;
543 err = nvs_get_blob(nvsHandle, key, nullptr, &blobSize);
544 if (err == ESP_ERR_NVS_NOT_FOUND) {
545 continue;
546 }
547 if (err != ESP_OK) {
548 detail::appendLine(out,
549 "[%s:%u] key=%s read-size error err=%d\n",
550 types[j],
551 i,
552 key,
553 static_cast<int>(err));
554 continue;
555 }
556
557 std::vector<uint8_t> blob(blobSize);
558 size_t readSize = blobSize;
559 err = nvs_get_blob(nvsHandle, key, blob.data(), &readSize);
560 if (err != ESP_OK) {
561 detail::appendLine(out,
562 "[%s:%u] key=%s read error err=%d\n",
563 types[j],
564 i,
565 key,
566 static_cast<int>(err));
567 continue;
568 }
569
570 foundCount++;
571 detail::appendLine(out,
572 "[%s:%u] key=%s size=%u ",
573 types[j],
574 i,
575 key,
576 static_cast<unsigned>(blobSize));
577
578 if (blobSize == sizeof(detail::BleStoreValueSecCurrent)) {
579 out += "format=current\n";
580 detail::BleStoreValueSecCurrent sec{};
581 memcpy(&sec, blob.data(), sizeof(sec));
582 detail::appendCurrentRecord(out, sec);
583 } else if (blobSize == sizeof(detail::BleStoreValueSecV1)) {
584 out += "format=v1\n";
585 detail::BleStoreValueSecV1 sec{};
586 memcpy(&sec, blob.data(), sizeof(sec));
587 detail::appendV1Record(out, sec);
588 } else {
589 out += "format=unknown\n";
590 out += " raw=";
591 const size_t dumpLen = blobSize > 64 ? 64 : blobSize;
592 detail::appendHex(out, blob.data(), dumpLen);
593 if (blobSize > dumpLen) {
594 out += "...";
595 }
596 out += "\n";
597 }
598 }
599
600 if (!detail::makeBondKey(key, sizeof(key), detail::kLocalIrkPrefix, i)) {
601 out += "Key format error\n";
602 continue;
603 }
604
605 size_t blobSize = 0;
606 err = nvs_get_blob(nvsHandle, key, nullptr, &blobSize);
607 if (err == ESP_ERR_NVS_NOT_FOUND) {
608 continue;
609 }
610 if (err != ESP_OK) {
611 detail::appendLine(out,
612 "[local_irk:%u] key=%s read-size error err=%d\n",
613 i,
614 key,
615 static_cast<int>(err));
616 continue;
617 }
618
619 std::vector<uint8_t> blob(blobSize);
620 size_t readSize = blobSize;
621 err = nvs_get_blob(nvsHandle, key, blob.data(), &readSize);
622 if (err != ESP_OK) {
623 detail::appendLine(out,
624 "[local_irk:%u] key=%s read error err=%d\n",
625 i,
626 key,
627 static_cast<int>(err));
628 continue;
629 }
630
631 foundCount++;
632 detail::appendLine(out,
633 "[local_irk:%u] key=%s size=%u ",
634 i,
635 key,
636 static_cast<unsigned>(blobSize));
637
638 if (blobSize == sizeof(detail::BleStoreValueLocalIrkCurrent)) {
639 out += "format=local_irk_current\n";
640 detail::BleStoreValueLocalIrkCurrent irkRecord{};
641 memcpy(&irkRecord, blob.data(), sizeof(irkRecord));
642 detail::appendLocalIrkCurrentRecord(out, irkRecord);
643 } else if (blobSize == sizeof(detail::BleStoreValueLocalIrkV1)) {
644 out += "format=local_irk_v1\n";
645 detail::BleStoreValueLocalIrkV1 irkRecord{};
646 memcpy(&irkRecord, blob.data(), sizeof(irkRecord));
647 detail::appendLocalIrkV1Record(out, irkRecord);
648 } else {
649 out += "format=unknown\n";
650 out += " raw=";
651 const size_t dumpLen = blobSize > 64 ? 64 : blobSize;
652 detail::appendHex(out, blob.data(), dumpLen);
653 if (blobSize > dumpLen) {
654 out += "...";
655 }
656 out += "\n";
657 }
658 }
659
660 if (foundCount == 0) {
661 out += "No bond entries found\n";
662 } else {
663 detail::appendLine(out, "Total entries found: %u\n", foundCount);
664 }
665
666 nvs_close(nvsHandle);
667 return out;
668}
669
681inline bool migrateBondStoreToCurrent(uint16_t maxEntries = MYNEWT_VAL(BLE_STORE_MAX_BONDS)) {
682 return detail::migrateBondStore(true, maxEntries);
683}
684
695inline bool migrateBondStoreToV1(uint16_t maxEntries = MYNEWT_VAL(BLE_STORE_MAX_BONDS)) {
696 return detail::migrateBondStore(false, maxEntries);
697}
698
699} // namespace NimBLEBondMigration
Helpers for converting NimBLE bond data stored in ESP NVS between the legacy 1.x layout and the curre...
Definition NimBLEBondMigration.h:26
std::string dumpBondData(uint16_t maxEntries=MYNEWT_VAL(BLE_STORE_MAX_BONDS))
Read and format the contents of the NimBLE bond store.
Definition NimBLEBondMigration.h:508
bool migrateBondStoreToCurrent(uint16_t maxEntries=MYNEWT_VAL(BLE_STORE_MAX_BONDS))
Convert legacy 1.x bond data in NVS to the current 2.x format.
Definition NimBLEBondMigration.h:681
bool migrateBondStoreToV1(uint16_t maxEntries=MYNEWT_VAL(BLE_STORE_MAX_BONDS))
Convert current 2.x bond data in NVS back to the legacy 1.x format.
Definition NimBLEBondMigration.h:695