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;
42struct BleStoreValueSecV1 {
49 uint8_t ltk_present : 1;
52 uint8_t irk_present : 1;
55 uint8_t csrk_present : 1;
57 unsigned authenticated : 1;
61struct BleStoreValueSecCurrent {
69 uint8_t ltk_present : 1;
72 uint8_t irk_present : 1;
75 uint8_t csrk_present : 1;
76 uint32_t sign_counter;
78 unsigned authenticated : 1;
82struct BleStoreValueLocalIrkV1 {
86struct BleStoreValueLocalIrkCurrent {
91static constexpr uint8_t kDefaultLocalIrk[16] = {
92 0xef, 0x8d, 0xe2, 0x16, 0x4f, 0xec, 0x43, 0x0d,
93 0xbf, 0x5b, 0xdd, 0x34, 0xc0, 0x53, 0x1e, 0xb8,
96struct MigrationStats {
99 uint16_t alreadyTarget;
100 uint16_t skippedUnknownSize;
103inline void appendLine(std::string& out,
const char* fmt, ...) {
107 const int written = vsnprintf(buf,
sizeof(buf), fmt, args);
110 out.append(buf, strlen(buf));
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]);
124inline void appendAddr(std::string& out,
const ble_addr_t& addr) {
126 "%02X:%02X:%02X:%02X:%02X:%02X(type=%u)",
136inline void appendV1Record(std::string& out,
const BleStoreValueSecV1& sec) {
137 out +=
" peer_addr=";
138 appendAddr(out, sec.peer_addr);
141 " key_size=%u ediv=%u rand_num=%llu auth=%u sc=%u\n",
144 static_cast<unsigned long long>(sec.rand_num),
148 " ltk_present=%u irk_present=%u csrk_present=%u\n",
152 if (sec.ltk_present) {
154 appendHex(out, sec.ltk,
sizeof(sec.ltk));
157 if (sec.irk_present) {
159 appendHex(out, sec.irk,
sizeof(sec.irk));
162 if (sec.csrk_present) {
164 appendHex(out, sec.csrk,
sizeof(sec.csrk));
169inline void appendCurrentRecord(std::string& out,
const BleStoreValueSecCurrent& sec) {
170 out +=
" peer_addr=";
171 appendAddr(out, sec.peer_addr);
174 " bond_count=%u sign_counter=%u key_size=%u ediv=%u rand_num=%llu auth=%u sc=%u\n",
179 static_cast<unsigned long long>(sec.rand_num),
183 " ltk_present=%u irk_present=%u csrk_present=%u\n",
187 if (sec.ltk_present) {
189 appendHex(out, sec.ltk,
sizeof(sec.ltk));
192 if (sec.irk_present) {
194 appendHex(out, sec.irk,
sizeof(sec.irk));
197 if (sec.csrk_present) {
199 appendHex(out, sec.csrk,
sizeof(sec.csrk));
204inline void appendLocalIrkV1Record(std::string& out,
const BleStoreValueLocalIrkV1& irkRecord) {
206 appendHex(out, irkRecord.irk,
sizeof(irkRecord.irk));
210inline void appendLocalIrkCurrentRecord(std::string& out,
const BleStoreValueLocalIrkCurrent& irkRecord) {
212 appendAddr(out, irkRecord.addr);
215 appendHex(out, irkRecord.irk,
sizeof(irkRecord.irk));
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;
224inline bool migrateEntryToCurrent(nvs_handle_t nvsHandle,
227 MigrationStats* stats) {
229 esp_err_t err = nvs_get_blob(nvsHandle, key,
nullptr, &blobSize);
230 if (err == ESP_ERR_NVS_NOT_FOUND) {
234 ESP_LOGE(kLogTag,
"nvs_get_blob size failed key=%s err=%d", key,
static_cast<int>(err));
240 if (blobSize ==
sizeof(BleStoreValueSecCurrent)) {
241 stats->alreadyTarget++;
245 if (blobSize !=
sizeof(BleStoreValueSecV1)) {
246 stats->skippedUnknownSize++;
248 "Skipping key=%s due to unexpected size=%u",
250 static_cast<unsigned>(blobSize));
254 BleStoreValueSecV1 oldValue{};
255 size_t readSize =
sizeof(oldValue);
256 err = nvs_get_blob(nvsHandle, key, &oldValue, &readSize);
258 ESP_LOGE(kLogTag,
"nvs_get_blob value failed key=%s err=%d", key,
static_cast<int>(err));
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;
278 err = nvs_set_blob(nvsHandle, key, &newValue,
sizeof(newValue));
280 ESP_LOGE(kLogTag,
"nvs_set_blob failed key=%s err=%d", key,
static_cast<int>(err));
288inline bool migrateEntryToV1(nvs_handle_t nvsHandle,
290 MigrationStats* stats) {
292 esp_err_t err = nvs_get_blob(nvsHandle, key,
nullptr, &blobSize);
293 if (err == ESP_ERR_NVS_NOT_FOUND) {
297 ESP_LOGE(kLogTag,
"nvs_get_blob size failed key=%s err=%d", key,
static_cast<int>(err));
303 if (blobSize ==
sizeof(BleStoreValueSecV1)) {
304 stats->alreadyTarget++;
308 if (blobSize !=
sizeof(BleStoreValueSecCurrent)) {
309 stats->skippedUnknownSize++;
311 "Skipping key=%s due to unexpected size=%u",
313 static_cast<unsigned>(blobSize));
317 BleStoreValueSecCurrent curValue{};
318 size_t readSize =
sizeof(curValue);
319 err = nvs_get_blob(nvsHandle, key, &curValue, &readSize);
321 ESP_LOGE(kLogTag,
"nvs_get_blob value failed key=%s err=%d", key,
static_cast<int>(err));
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;
339 err = nvs_set_blob(nvsHandle, key, &oldValue,
sizeof(oldValue));
341 ESP_LOGE(kLogTag,
"nvs_set_blob failed key=%s err=%d", key,
static_cast<int>(err));
349inline bool migrateLocalIrkEntryToCurrent(nvs_handle_t nvsHandle,
351 MigrationStats* stats) {
353 esp_err_t err = nvs_get_blob(nvsHandle, key,
nullptr, &blobSize);
354 if (err == ESP_ERR_NVS_NOT_FOUND) {
356 }
else if (err != ESP_OK) {
357 ESP_LOGE(kLogTag,
"nvs_get_blob size failed key=%s err=%d", key,
static_cast<int>(err));
363 if (blobSize != 0 && blobSize !=
sizeof(BleStoreValueLocalIrkCurrent) &&
364 blobSize !=
sizeof(BleStoreValueLocalIrkV1)) {
365 stats->skippedUnknownSize++;
367 "Overwriting key=%s with default local_irk despite unexpected size=%u",
369 static_cast<unsigned>(blobSize));
372 BleStoreValueLocalIrkCurrent newValue{};
373 memcpy(newValue.irk, kDefaultLocalIrk,
sizeof(newValue.irk));
375 err = nvs_set_blob(nvsHandle, key, &newValue,
sizeof(newValue));
377 ESP_LOGE(kLogTag,
"nvs_set_blob failed key=%s err=%d", key,
static_cast<int>(err));
385inline bool migrateLocalIrkEntryToV1(nvs_handle_t nvsHandle,
387 MigrationStats* stats) {
389 esp_err_t err = nvs_get_blob(nvsHandle, key,
nullptr, &blobSize);
390 if (err == ESP_ERR_NVS_NOT_FOUND) {
394 ESP_LOGE(kLogTag,
"nvs_get_blob size failed key=%s err=%d", key,
static_cast<int>(err));
401 err = nvs_erase_key(nvsHandle, key);
403 ESP_LOGE(kLogTag,
"nvs_erase_key failed key=%s err=%d", key,
static_cast<int>(err));
411inline bool migrateBondStore(
bool toCurrent, uint16_t maxEntries) {
412 nvs_handle_t nvsHandle;
413 esp_err_t err = nvs_open(kBondNamespace, NVS_READWRITE, &nvsHandle);
416 "Failed to open NVS namespace '%s', err=%d",
418 static_cast<int>(err));
422 MigrationStats stats{};
423 char key[kNvsKeyMaxLen]{};
425 for (uint16_t i = 1; i <= maxEntries; ++i) {
426 if (!makeBondKey(key,
sizeof(key), kOurSecPrefix, i)) {
427 nvs_close(nvsHandle);
431 if (!(toCurrent ? migrateEntryToCurrent(nvsHandle, key, i, &stats)
432 : migrateEntryToV1(nvsHandle, key, &stats))) {
433 nvs_close(nvsHandle);
437 if (!makeBondKey(key,
sizeof(key), kPeerSecPrefix, i)) {
438 nvs_close(nvsHandle);
442 if (!(toCurrent ? migrateEntryToCurrent(nvsHandle, key, i, &stats)
443 : migrateEntryToV1(nvsHandle, key, &stats))) {
444 nvs_close(nvsHandle);
450 if (!makeBondKey(key,
sizeof(key), kLocalIrkPrefix, 1)) {
451 nvs_close(nvsHandle);
455 if (!migrateLocalIrkEntryToCurrent(nvsHandle, key, &stats)) {
456 nvs_close(nvsHandle);
460 for (uint16_t i = 1; i <= maxEntries; ++i) {
461 if (!makeBondKey(key,
sizeof(key), kLocalIrkPrefix, i)) {
462 nvs_close(nvsHandle);
466 if (!migrateLocalIrkEntryToV1(nvsHandle, key, &stats)) {
467 nvs_close(nvsHandle);
473 if (stats.converted > 0) {
474 err = nvs_commit(nvsHandle);
476 ESP_LOGE(kLogTag,
"nvs_commit failed err=%d",
static_cast<int>(err));
477 nvs_close(nvsHandle);
482 nvs_close(nvsHandle);
485 "Bond migration %s: scanned=%u converted=%u already=%u skipped=%u",
486 toCurrent ?
"to-current" :
"to-v1",
490 stats.skippedUnknownSize);
508inline std::string
dumpBondData(uint16_t maxEntries = MYNEWT_VAL(BLE_STORE_MAX_BONDS)) {
512 nvs_handle_t nvsHandle;
513 esp_err_t err = nvs_open(detail::kBondNamespace, NVS_READONLY, &nvsHandle);
515 detail::appendLine(out,
516 "Failed to open NVS namespace '%s', err=%d\n",
517 detail::kBondNamespace,
518 static_cast<int>(err));
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)),
529 uint16_t foundCount = 0;
530 char key[detail::kNvsKeyMaxLen]{};
532 for (uint16_t i = 1; i <= maxEntries; ++i) {
533 const char* prefixes[] = {detail::kOurSecPrefix, detail::kPeerSecPrefix};
534 const char* types[] = {
"our",
"peer"};
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";
543 err = nvs_get_blob(nvsHandle, key,
nullptr, &blobSize);
544 if (err == ESP_ERR_NVS_NOT_FOUND) {
548 detail::appendLine(out,
549 "[%s:%u] key=%s read-size error err=%d\n",
553 static_cast<int>(err));
557 std::vector<uint8_t> blob(blobSize);
558 size_t readSize = blobSize;
559 err = nvs_get_blob(nvsHandle, key, blob.data(), &readSize);
561 detail::appendLine(out,
562 "[%s:%u] key=%s read error err=%d\n",
566 static_cast<int>(err));
571 detail::appendLine(out,
572 "[%s:%u] key=%s size=%u ",
576 static_cast<unsigned>(blobSize));
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);
589 out +=
"format=unknown\n";
591 const size_t dumpLen = blobSize > 64 ? 64 : blobSize;
592 detail::appendHex(out, blob.data(), dumpLen);
593 if (blobSize > dumpLen) {
600 if (!detail::makeBondKey(key,
sizeof(key), detail::kLocalIrkPrefix, i)) {
601 out +=
"Key format error\n";
606 err = nvs_get_blob(nvsHandle, key,
nullptr, &blobSize);
607 if (err == ESP_ERR_NVS_NOT_FOUND) {
611 detail::appendLine(out,
612 "[local_irk:%u] key=%s read-size error err=%d\n",
615 static_cast<int>(err));
619 std::vector<uint8_t> blob(blobSize);
620 size_t readSize = blobSize;
621 err = nvs_get_blob(nvsHandle, key, blob.data(), &readSize);
623 detail::appendLine(out,
624 "[local_irk:%u] key=%s read error err=%d\n",
627 static_cast<int>(err));
632 detail::appendLine(out,
633 "[local_irk:%u] key=%s size=%u ",
636 static_cast<unsigned>(blobSize));
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);
649 out +=
"format=unknown\n";
651 const size_t dumpLen = blobSize > 64 ? 64 : blobSize;
652 detail::appendHex(out, blob.data(), dumpLen);
653 if (blobSize > dumpLen) {
660 if (foundCount == 0) {
661 out +=
"No bond entries found\n";
663 detail::appendLine(out,
"Total entries found: %u\n", foundCount);
666 nvs_close(nvsHandle);
682 return detail::migrateBondStore(
true, maxEntries);
696 return detail::migrateBondStore(
false, maxEntries);