1 /*
2 * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6 #include "nvs_storage.hpp"
7
8 #ifndef ESP_PLATFORM
9 // We need NO_DEBUG_STORAGE here since the integration tests on the host add some debug code.
10 // The unit tests, however, don't want debug code since they check the behavior via data in/output and disturb
11 // the order of calling mocked functions.
12 #ifndef NO_DEBUG_STORAGE
13 #include <map>
14 #include <sstream>
15 #define DEBUG_STORAGE
16 #endif
17 #endif // !ESP_PLATFORM
18
19 namespace nvs
20 {
21
~Storage()22 Storage::~Storage()
23 {
24 clearNamespaces();
25 }
26
clearNamespaces()27 void Storage::clearNamespaces()
28 {
29 mNamespaces.clearAndFreeNodes();
30 }
31
populateBlobIndices(TBlobIndexList & blobIdxList)32 esp_err_t Storage::populateBlobIndices(TBlobIndexList& blobIdxList)
33 {
34 for (auto it = mPageManager.begin(); it != mPageManager.end(); ++it) {
35 Page& p = *it;
36 size_t itemIndex = 0;
37 Item item;
38
39 /* If the power went off just after writing a blob index, the duplicate detection
40 * logic in pagemanager will remove the earlier index. So we should never find a
41 * duplicate index at this point */
42
43 while (p.findItem(Page::NS_ANY, ItemType::BLOB_IDX, nullptr, itemIndex, item) == ESP_OK) {
44 BlobIndexNode* entry = new (std::nothrow) BlobIndexNode;
45
46 if (!entry) return ESP_ERR_NO_MEM;
47
48 item.getKey(entry->key, sizeof(entry->key));
49 entry->nsIndex = item.nsIndex;
50 entry->chunkStart = item.blobIndex.chunkStart;
51 entry->chunkCount = item.blobIndex.chunkCount;
52
53 blobIdxList.push_back(entry);
54 itemIndex += item.span;
55 }
56 }
57
58 return ESP_OK;
59 }
60
eraseOrphanDataBlobs(TBlobIndexList & blobIdxList)61 void Storage::eraseOrphanDataBlobs(TBlobIndexList& blobIdxList)
62 {
63 for (auto it = mPageManager.begin(); it != mPageManager.end(); ++it) {
64 Page& p = *it;
65 size_t itemIndex = 0;
66 Item item;
67 /* Chunks with same <ns,key> and with chunkIndex in the following ranges
68 * belong to same family.
69 * 1) VER_0_OFFSET <= chunkIndex < VER_1_OFFSET-1 => Version0 chunks
70 * 2) VER_1_OFFSET <= chunkIndex < VER_ANY => Version1 chunks
71 */
72 while (p.findItem(Page::NS_ANY, ItemType::BLOB_DATA, nullptr, itemIndex, item) == ESP_OK) {
73
74 auto iter = std::find_if(blobIdxList.begin(),
75 blobIdxList.end(),
76 [=] (const BlobIndexNode& e) -> bool
77 {return (strncmp(item.key, e.key, sizeof(e.key) - 1) == 0)
78 && (item.nsIndex == e.nsIndex)
79 && (item.chunkIndex >= static_cast<uint8_t> (e.chunkStart))
80 && (item.chunkIndex < static_cast<uint8_t> (e.chunkStart) + e.chunkCount);});
81 if (iter == std::end(blobIdxList)) {
82 p.eraseItem(item.nsIndex, item.datatype, item.key, item.chunkIndex);
83 }
84 itemIndex += item.span;
85 }
86 }
87 }
88
init(uint32_t baseSector,uint32_t sectorCount)89 esp_err_t Storage::init(uint32_t baseSector, uint32_t sectorCount)
90 {
91 auto err = mPageManager.load(mPartition, baseSector, sectorCount);
92 if (err != ESP_OK) {
93 mState = StorageState::INVALID;
94 return err;
95 }
96
97 // load namespaces list
98 clearNamespaces();
99 std::fill_n(mNamespaceUsage.data(), mNamespaceUsage.byteSize() / 4, 0);
100 for (auto it = mPageManager.begin(); it != mPageManager.end(); ++it) {
101 Page& p = *it;
102 size_t itemIndex = 0;
103 Item item;
104 while (p.findItem(Page::NS_INDEX, ItemType::U8, nullptr, itemIndex, item) == ESP_OK) {
105 NamespaceEntry* entry = new (std::nothrow) NamespaceEntry;
106
107 if (!entry) {
108 mState = StorageState::INVALID;
109 return ESP_ERR_NO_MEM;
110 }
111
112 item.getKey(entry->mName, sizeof(entry->mName));
113 item.getValue(entry->mIndex);
114 mNamespaces.push_back(entry);
115 mNamespaceUsage.set(entry->mIndex, true);
116 itemIndex += item.span;
117 }
118 }
119 mNamespaceUsage.set(0, true);
120 mNamespaceUsage.set(255, true);
121 mState = StorageState::ACTIVE;
122
123 // Populate list of multi-page index entries.
124 TBlobIndexList blobIdxList;
125 err = populateBlobIndices(blobIdxList);
126 if (err != ESP_OK) {
127 mState = StorageState::INVALID;
128 return ESP_ERR_NO_MEM;
129 }
130
131 // Remove the entries for which there is no parent multi-page index.
132 eraseOrphanDataBlobs(blobIdxList);
133
134 // Purge the blob index list
135 blobIdxList.clearAndFreeNodes();
136
137 #ifdef DEBUG_STORAGE
138 debugCheck();
139 #endif
140 return ESP_OK;
141 }
142
isValid() const143 bool Storage::isValid() const
144 {
145 return mState == StorageState::ACTIVE;
146 }
147
findItem(uint8_t nsIndex,ItemType datatype,const char * key,Page * & page,Item & item,uint8_t chunkIdx,VerOffset chunkStart)148 esp_err_t Storage::findItem(uint8_t nsIndex, ItemType datatype, const char* key, Page* &page, Item& item, uint8_t chunkIdx, VerOffset chunkStart)
149 {
150 for (auto it = std::begin(mPageManager); it != std::end(mPageManager); ++it) {
151 size_t itemIndex = 0;
152 auto err = it->findItem(nsIndex, datatype, key, itemIndex, item, chunkIdx, chunkStart);
153 if (err == ESP_OK) {
154 page = it;
155 return ESP_OK;
156 }
157 }
158 return ESP_ERR_NVS_NOT_FOUND;
159 }
160
writeMultiPageBlob(uint8_t nsIndex,const char * key,const void * data,size_t dataSize,VerOffset chunkStart)161 esp_err_t Storage::writeMultiPageBlob(uint8_t nsIndex, const char* key, const void* data, size_t dataSize, VerOffset chunkStart)
162 {
163 uint8_t chunkCount = 0;
164 TUsedPageList usedPages;
165 size_t remainingSize = dataSize;
166 size_t offset = 0;
167 esp_err_t err = ESP_OK;
168
169 /* Check how much maximum data can be accommodated**/
170 uint32_t max_pages = mPageManager.getPageCount() - 1;
171
172 if(max_pages > (Page::CHUNK_ANY-1)/2) {
173 max_pages = (Page::CHUNK_ANY-1)/2;
174 }
175
176 if (dataSize > max_pages * Page::CHUNK_MAX_SIZE) {
177 return ESP_ERR_NVS_VALUE_TOO_LONG;
178 }
179
180 do {
181 Page& page = getCurrentPage();
182 size_t tailroom = page.getVarDataTailroom();
183 size_t chunkSize = 0;
184 if (chunkCount == 0U && ((tailroom < dataSize) || (tailroom == 0 && dataSize == 0)) && tailroom < Page::CHUNK_MAX_SIZE/10) {
185 /** This is the first chunk and tailroom is too small ***/
186 if (page.state() != Page::PageState::FULL) {
187 err = page.markFull();
188 if (err != ESP_OK) {
189 return err;
190 }
191 }
192 err = mPageManager.requestNewPage();
193 if (err != ESP_OK) {
194 return err;
195 } else if(getCurrentPage().getVarDataTailroom() == tailroom) {
196 /* We got the same page or we are not improving.*/
197 return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
198 } else {
199 continue;
200 }
201 } else if (!tailroom) {
202 err = ESP_ERR_NVS_NOT_ENOUGH_SPACE;
203 break;
204 }
205
206 /* Split the blob into two and store the chunk of available size onto the current page */
207 assert(tailroom != 0);
208 chunkSize = (remainingSize > tailroom)? tailroom : remainingSize;
209 remainingSize -= chunkSize;
210
211 err = page.writeItem(nsIndex, ItemType::BLOB_DATA, key,
212 static_cast<const uint8_t*> (data) + offset, chunkSize, static_cast<uint8_t> (chunkStart) + chunkCount);
213 chunkCount++;
214 assert(err != ESP_ERR_NVS_PAGE_FULL);
215 if (err != ESP_OK) {
216 break;
217 } else {
218 UsedPageNode* node = new (std::nothrow) UsedPageNode();
219 if (!node) {
220 err = ESP_ERR_NO_MEM;
221 break;
222 }
223 node->mPage = &page;
224 usedPages.push_back(node);
225 if (remainingSize || (tailroom - chunkSize) < Page::ENTRY_SIZE) {
226 if (page.state() != Page::PageState::FULL) {
227 err = page.markFull();
228 if (err != ESP_OK) {
229 break;
230 }
231 }
232 err = mPageManager.requestNewPage();
233 if (err != ESP_OK) {
234 break;
235 }
236 }
237 }
238 offset += chunkSize;
239 if (!remainingSize) {
240 /* All pages are stored. Now store the index.*/
241 Item item;
242 std::fill_n(item.data, sizeof(item.data), 0xff);
243 item.blobIndex.dataSize = dataSize;
244 item.blobIndex.chunkCount = chunkCount;
245 item.blobIndex.chunkStart = chunkStart;
246
247 err = getCurrentPage().writeItem(nsIndex, ItemType::BLOB_IDX, key, item.data, sizeof(item.data));
248 assert(err != ESP_ERR_NVS_PAGE_FULL);
249 break;
250 }
251 } while (1);
252
253 if (err != ESP_OK) {
254 /* Anything failed, then we should erase all the written chunks*/
255 int ii=0;
256 for (auto it = std::begin(usedPages); it != std::end(usedPages); it++) {
257 it->mPage->eraseItem(nsIndex, ItemType::BLOB_DATA, key, ii++);
258 }
259 }
260 usedPages.clearAndFreeNodes();
261 return err;
262 }
263
writeItem(uint8_t nsIndex,ItemType datatype,const char * key,const void * data,size_t dataSize)264 esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize)
265 {
266 if (mState != StorageState::ACTIVE) {
267 return ESP_ERR_NVS_NOT_INITIALIZED;
268 }
269
270 Page* findPage = nullptr;
271 Item item;
272
273 esp_err_t err;
274 if (datatype == ItemType::BLOB) {
275 err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item);
276 } else {
277 err = findItem(nsIndex, datatype, key, findPage, item);
278 }
279
280 if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
281 return err;
282 }
283
284 if (datatype == ItemType::BLOB) {
285 VerOffset prevStart, nextStart;
286 prevStart = nextStart = VerOffset::VER_0_OFFSET;
287 if (findPage) {
288 // Do a sanity check that the item in question is actually being modified.
289 // If it isn't, it is cheaper to purposefully not write out new data.
290 // since it may invoke an erasure of flash.
291 if (cmpMultiPageBlob(nsIndex, key, data, dataSize) == ESP_OK) {
292 return ESP_OK;
293 }
294
295 if (findPage->state() == Page::PageState::UNINITIALIZED ||
296 findPage->state() == Page::PageState::INVALID) {
297 ESP_ERROR_CHECK(findItem(nsIndex, datatype, key, findPage, item));
298 }
299 /* Get the version of the previous index with same <ns,key> */
300 prevStart = item.blobIndex.chunkStart;
301 assert(prevStart == VerOffset::VER_0_OFFSET || prevStart == VerOffset::VER_1_OFFSET);
302
303 /* Toggle the version by changing the offset */
304 nextStart
305 = (prevStart == VerOffset::VER_1_OFFSET) ? VerOffset::VER_0_OFFSET : VerOffset::VER_1_OFFSET;
306 }
307 /* Write the blob with new version*/
308 err = writeMultiPageBlob(nsIndex, key, data, dataSize, nextStart);
309
310 if (err == ESP_ERR_NVS_PAGE_FULL) {
311 return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
312 }
313 if (err != ESP_OK) {
314 return err;
315 }
316
317 if (findPage) {
318 /* Erase the blob with earlier version*/
319 err = eraseMultiPageBlob(nsIndex, key, prevStart);
320
321 if (err == ESP_ERR_FLASH_OP_FAIL) {
322 return ESP_ERR_NVS_REMOVE_FAILED;
323 }
324 if (err != ESP_OK) {
325 return err;
326 }
327
328 findPage = nullptr;
329 } else {
330 /* Support for earlier versions where BLOBS were stored without index */
331 err = findItem(nsIndex, datatype, key, findPage, item);
332 if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
333 return err;
334 }
335 }
336 } else {
337 // Do a sanity check that the item in question is actually being modified.
338 // If it isn't, it is cheaper to purposefully not write out new data.
339 // since it may invoke an erasure of flash.
340 if (findPage != nullptr &&
341 findPage->cmpItem(nsIndex, datatype, key, data, dataSize) == ESP_OK) {
342 return ESP_OK;
343 }
344
345 Page& page = getCurrentPage();
346 err = page.writeItem(nsIndex, datatype, key, data, dataSize);
347 if (err == ESP_ERR_NVS_PAGE_FULL) {
348 if (page.state() != Page::PageState::FULL) {
349 err = page.markFull();
350 if (err != ESP_OK) {
351 return err;
352 }
353 }
354 err = mPageManager.requestNewPage();
355 if (err != ESP_OK) {
356 return err;
357 }
358
359 err = getCurrentPage().writeItem(nsIndex, datatype, key, data, dataSize);
360 if (err == ESP_ERR_NVS_PAGE_FULL) {
361 return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
362 }
363 if (err != ESP_OK) {
364 return err;
365 }
366 } else if (err != ESP_OK) {
367 return err;
368 }
369 }
370
371 if (findPage) {
372 if (findPage->state() == Page::PageState::UNINITIALIZED ||
373 findPage->state() == Page::PageState::INVALID) {
374 ESP_ERROR_CHECK(findItem(nsIndex, datatype, key, findPage, item));
375 }
376 err = findPage->eraseItem(nsIndex, datatype, key);
377 if (err == ESP_ERR_FLASH_OP_FAIL) {
378 return ESP_ERR_NVS_REMOVE_FAILED;
379 }
380 if (err != ESP_OK) {
381 return err;
382 }
383 }
384 #ifdef DEBUG_STORAGE
385 debugCheck();
386 #endif
387 return ESP_OK;
388 }
389
createOrOpenNamespace(const char * nsName,bool canCreate,uint8_t & nsIndex)390 esp_err_t Storage::createOrOpenNamespace(const char* nsName, bool canCreate, uint8_t& nsIndex)
391 {
392 if (mState != StorageState::ACTIVE) {
393 return ESP_ERR_NVS_NOT_INITIALIZED;
394 }
395 auto it = std::find_if(mNamespaces.begin(), mNamespaces.end(), [=] (const NamespaceEntry& e) -> bool {
396 return strncmp(nsName, e.mName, sizeof(e.mName) - 1) == 0;
397 });
398 if (it == std::end(mNamespaces)) {
399 if (!canCreate) {
400 return ESP_ERR_NVS_NOT_FOUND;
401 }
402
403 uint8_t ns;
404 for (ns = 1; ns < 255; ++ns) {
405 if (mNamespaceUsage.get(ns) == false) {
406 break;
407 }
408 }
409
410 if (ns == 255) {
411 return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
412 }
413
414 auto err = writeItem(Page::NS_INDEX, ItemType::U8, nsName, &ns, sizeof(ns));
415 if (err != ESP_OK) {
416 return err;
417 }
418 mNamespaceUsage.set(ns, true);
419 nsIndex = ns;
420
421 NamespaceEntry* entry = new (std::nothrow) NamespaceEntry;
422 if (!entry) {
423 return ESP_ERR_NO_MEM;
424 }
425
426 entry->mIndex = ns;
427 strncpy(entry->mName, nsName, sizeof(entry->mName) - 1);
428 entry->mName[sizeof(entry->mName) - 1] = 0;
429 mNamespaces.push_back(entry);
430
431 } else {
432 nsIndex = it->mIndex;
433 }
434 return ESP_OK;
435 }
436
readMultiPageBlob(uint8_t nsIndex,const char * key,void * data,size_t dataSize)437 esp_err_t Storage::readMultiPageBlob(uint8_t nsIndex, const char* key, void* data, size_t dataSize)
438 {
439 Item item;
440 Page* findPage = nullptr;
441
442 /* First read the blob index */
443 auto err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item);
444 if (err != ESP_OK) {
445 return err;
446 }
447
448 uint8_t chunkCount = item.blobIndex.chunkCount;
449 VerOffset chunkStart = item.blobIndex.chunkStart;
450 size_t offset = 0;
451
452 assert(dataSize == item.blobIndex.dataSize);
453
454 /* Now read corresponding chunks */
455 for (uint8_t chunkNum = 0; chunkNum < chunkCount; chunkNum++) {
456 err = findItem(nsIndex, ItemType::BLOB_DATA, key, findPage, item, static_cast<uint8_t> (chunkStart) + chunkNum);
457 if (err != ESP_OK) {
458 if (err == ESP_ERR_NVS_NOT_FOUND) {
459 break;
460 }
461 return err;
462 }
463 err = findPage->readItem(nsIndex, ItemType::BLOB_DATA, key, static_cast<uint8_t*>(data) + offset, item.varLength.dataSize, static_cast<uint8_t> (chunkStart) + chunkNum);
464 if (err != ESP_OK) {
465 return err;
466 }
467 assert(static_cast<uint8_t> (chunkStart) + chunkNum == item.chunkIndex);
468 offset += item.varLength.dataSize;
469 }
470 if (err == ESP_OK) {
471 assert(offset == dataSize);
472 }
473 if (err == ESP_ERR_NVS_NOT_FOUND) {
474 eraseMultiPageBlob(nsIndex, key); // cleanup if a chunk is not found
475 }
476 return err;
477 }
478
cmpMultiPageBlob(uint8_t nsIndex,const char * key,const void * data,size_t dataSize)479 esp_err_t Storage::cmpMultiPageBlob(uint8_t nsIndex, const char* key, const void* data, size_t dataSize)
480 {
481 Item item;
482 Page* findPage = nullptr;
483
484 /* First read the blob index */
485 auto err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item);
486 if (err != ESP_OK) {
487 return err;
488 }
489
490 uint8_t chunkCount = item.blobIndex.chunkCount;
491 VerOffset chunkStart = item.blobIndex.chunkStart;
492 size_t readSize = item.blobIndex.dataSize;
493 size_t offset = 0;
494
495 if (dataSize != readSize) {
496 return ESP_ERR_NVS_CONTENT_DIFFERS;
497 }
498
499 /* Now read corresponding chunks */
500 for (uint8_t chunkNum = 0; chunkNum < chunkCount; chunkNum++) {
501 err = findItem(nsIndex, ItemType::BLOB_DATA, key, findPage, item, static_cast<uint8_t> (chunkStart) + chunkNum);
502 if (err != ESP_OK) {
503 if (err == ESP_ERR_NVS_NOT_FOUND) {
504 break;
505 }
506 return err;
507 }
508 err = findPage->cmpItem(nsIndex, ItemType::BLOB_DATA, key, static_cast<const uint8_t*>(data) + offset, item.varLength.dataSize, static_cast<uint8_t> (chunkStart) + chunkNum);
509 if (err != ESP_OK) {
510 return err;
511 }
512 assert(static_cast<uint8_t> (chunkStart) + chunkNum == item.chunkIndex);
513 offset += item.varLength.dataSize;
514 }
515 if (err == ESP_OK) {
516 assert(offset == dataSize);
517 }
518 return err;
519 }
520
readItem(uint8_t nsIndex,ItemType datatype,const char * key,void * data,size_t dataSize)521 esp_err_t Storage::readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize)
522 {
523 if (mState != StorageState::ACTIVE) {
524 return ESP_ERR_NVS_NOT_INITIALIZED;
525 }
526
527 Item item;
528 Page* findPage = nullptr;
529 if (datatype == ItemType::BLOB) {
530 auto err = readMultiPageBlob(nsIndex, key, data, dataSize);
531 if (err != ESP_ERR_NVS_NOT_FOUND) {
532 return err;
533 } // else check if the blob is stored with earlier version format without index
534 }
535
536 auto err = findItem(nsIndex, datatype, key, findPage, item);
537 if (err != ESP_OK) {
538 return err;
539 }
540 return findPage->readItem(nsIndex, datatype, key, data, dataSize);
541
542 }
543
eraseMultiPageBlob(uint8_t nsIndex,const char * key,VerOffset chunkStart)544 esp_err_t Storage::eraseMultiPageBlob(uint8_t nsIndex, const char* key, VerOffset chunkStart)
545 {
546 if (mState != StorageState::ACTIVE) {
547 return ESP_ERR_NVS_NOT_INITIALIZED;
548 }
549 Item item;
550 Page* findPage = nullptr;
551
552 auto err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item, Page::CHUNK_ANY, chunkStart);
553 if (err != ESP_OK) {
554 return err;
555 }
556 /* Erase the index first and make children blobs orphan*/
557 err = findPage->eraseItem(nsIndex, ItemType::BLOB_IDX, key, Page::CHUNK_ANY, chunkStart);
558 if (err != ESP_OK) {
559 return err;
560 }
561
562 uint8_t chunkCount = item.blobIndex.chunkCount;
563
564 if (chunkStart == VerOffset::VER_ANY) {
565 chunkStart = item.blobIndex.chunkStart;
566 } else {
567 assert(chunkStart == item.blobIndex.chunkStart);
568 }
569
570 /* Now erase corresponding chunks*/
571 for (uint8_t chunkNum = 0; chunkNum < chunkCount; chunkNum++) {
572 err = findItem(nsIndex, ItemType::BLOB_DATA, key, findPage, item, static_cast<uint8_t> (chunkStart) + chunkNum);
573
574 if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
575 return err;
576 } else if (err == ESP_ERR_NVS_NOT_FOUND) {
577 continue; // Keep erasing other chunks
578 }
579 err = findPage->eraseItem(nsIndex, ItemType::BLOB_DATA, key, static_cast<uint8_t> (chunkStart) + chunkNum);
580 if (err != ESP_OK) {
581 return err;
582 }
583
584 }
585
586 return ESP_OK;
587 }
588
eraseItem(uint8_t nsIndex,ItemType datatype,const char * key)589 esp_err_t Storage::eraseItem(uint8_t nsIndex, ItemType datatype, const char* key)
590 {
591 if (mState != StorageState::ACTIVE) {
592 return ESP_ERR_NVS_NOT_INITIALIZED;
593 }
594
595 if (datatype == ItemType::BLOB) {
596 return eraseMultiPageBlob(nsIndex, key);
597 }
598
599 Item item;
600 Page* findPage = nullptr;
601 auto err = findItem(nsIndex, datatype, key, findPage, item);
602 if (err != ESP_OK) {
603 return err;
604 }
605
606 if (item.datatype == ItemType::BLOB_DATA || item.datatype == ItemType::BLOB_IDX) {
607 return eraseMultiPageBlob(nsIndex, key);
608 }
609
610 return findPage->eraseItem(nsIndex, datatype, key);
611 }
612
eraseNamespace(uint8_t nsIndex)613 esp_err_t Storage::eraseNamespace(uint8_t nsIndex)
614 {
615 if (mState != StorageState::ACTIVE) {
616 return ESP_ERR_NVS_NOT_INITIALIZED;
617 }
618
619 for (auto it = std::begin(mPageManager); it != std::end(mPageManager); ++it) {
620 while (true) {
621 auto err = it->eraseItem(nsIndex, ItemType::ANY, nullptr);
622 if (err == ESP_ERR_NVS_NOT_FOUND) {
623 break;
624 }
625 else if (err != ESP_OK) {
626 return err;
627 }
628 }
629 }
630 return ESP_OK;
631
632 }
633
getItemDataSize(uint8_t nsIndex,ItemType datatype,const char * key,size_t & dataSize)634 esp_err_t Storage::getItemDataSize(uint8_t nsIndex, ItemType datatype, const char* key, size_t& dataSize)
635 {
636 if (mState != StorageState::ACTIVE) {
637 return ESP_ERR_NVS_NOT_INITIALIZED;
638 }
639
640 Item item;
641 Page* findPage = nullptr;
642 auto err = findItem(nsIndex, datatype, key, findPage, item);
643 if (err != ESP_OK) {
644 if (datatype != ItemType::BLOB) {
645 return err;
646 }
647 err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item);
648 if (err != ESP_OK) {
649 return err;
650 }
651 dataSize = item.blobIndex.dataSize;
652 return ESP_OK;
653 }
654
655 dataSize = item.varLength.dataSize;
656 return ESP_OK;
657 }
658
debugDump()659 void Storage::debugDump()
660 {
661 for (auto p = mPageManager.begin(); p != mPageManager.end(); ++p) {
662 p->debugDump();
663 }
664 }
665
666 #ifdef DEBUG_STORAGE
debugCheck()667 void Storage::debugCheck()
668 {
669 std::map<std::string, Page*> keys;
670
671 for (auto p = mPageManager.begin(); p != mPageManager.end(); ++p) {
672 size_t itemIndex = 0;
673 size_t usedCount = 0;
674 Item item;
675 while (p->findItem(Page::NS_ANY, ItemType::ANY, nullptr, itemIndex, item) == ESP_OK) {
676 std::stringstream keyrepr;
677 keyrepr << static_cast<unsigned>(item.nsIndex) << "_" << static_cast<unsigned>(item.datatype) << "_" << item.key <<"_"<<static_cast<unsigned>(item.chunkIndex);
678 std::string keystr = keyrepr.str();
679 if (keys.find(keystr) != std::end(keys)) {
680 printf("Duplicate key: %s\n", keystr.c_str());
681 debugDump();
682 assert(0);
683 }
684 keys.insert(std::make_pair(keystr, static_cast<Page*>(p)));
685 itemIndex += item.span;
686 usedCount += item.span;
687 }
688 assert(usedCount == p->getUsedEntryCount());
689 }
690 }
691 #endif //DEBUG_STORAGE
692
fillStats(nvs_stats_t & nvsStats)693 esp_err_t Storage::fillStats(nvs_stats_t& nvsStats)
694 {
695 nvsStats.namespace_count = mNamespaces.size();
696 return mPageManager.fillStats(nvsStats);
697 }
698
calcEntriesInNamespace(uint8_t nsIndex,size_t & usedEntries)699 esp_err_t Storage::calcEntriesInNamespace(uint8_t nsIndex, size_t& usedEntries)
700 {
701 usedEntries = 0;
702
703 if (mState != StorageState::ACTIVE) {
704 return ESP_ERR_NVS_NOT_INITIALIZED;
705 }
706
707 for (auto it = std::begin(mPageManager); it != std::end(mPageManager); ++it) {
708 size_t itemIndex = 0;
709 Item item;
710 while (true) {
711 auto err = it->findItem(nsIndex, ItemType::ANY, nullptr, itemIndex, item);
712 if (err == ESP_ERR_NVS_NOT_FOUND) {
713 break;
714 }
715 else if (err != ESP_OK) {
716 return err;
717 }
718 usedEntries += item.span;
719 itemIndex += item.span;
720 if (itemIndex >= it->ENTRY_COUNT) break;
721 }
722 }
723 return ESP_OK;
724 }
725
fillEntryInfo(Item & item,nvs_entry_info_t & info)726 void Storage::fillEntryInfo(Item &item, nvs_entry_info_t &info)
727 {
728 info.type = static_cast<nvs_type_t>(item.datatype);
729 strncpy(info.key, item.key, sizeof(info.key) - 1);
730 info.key[sizeof(info.key) - 1] = '\0';
731
732 for (auto &name : mNamespaces) {
733 if(item.nsIndex == name.mIndex) {
734 strncpy(info.namespace_name, name.mName, sizeof(info.namespace_name) - 1);
735 info.namespace_name[sizeof(info.namespace_name) -1] = '\0';
736 break;
737 }
738 }
739 }
740
findEntry(nvs_opaque_iterator_t * it,const char * namespace_name)741 bool Storage::findEntry(nvs_opaque_iterator_t* it, const char* namespace_name)
742 {
743 it->entryIndex = 0;
744 it->nsIndex = Page::NS_ANY;
745 it->page = mPageManager.begin();
746
747 if (namespace_name != nullptr) {
748 if(createOrOpenNamespace(namespace_name, false, it->nsIndex) != ESP_OK) {
749 return false;
750 }
751 }
752
753 return nextEntry(it);
754 }
755
isIterableItem(Item & item)756 inline bool isIterableItem(Item& item)
757 {
758 return (item.nsIndex != 0 &&
759 item.datatype != ItemType::BLOB &&
760 item.datatype != ItemType::BLOB_IDX);
761 }
762
isMultipageBlob(Item & item)763 inline bool isMultipageBlob(Item& item)
764 {
765 return (item.datatype == ItemType::BLOB_DATA &&
766 !(item.chunkIndex == static_cast<uint8_t>(VerOffset::VER_0_OFFSET)
767 || item.chunkIndex == static_cast<uint8_t>(VerOffset::VER_1_OFFSET)));
768 }
769
nextEntry(nvs_opaque_iterator_t * it)770 bool Storage::nextEntry(nvs_opaque_iterator_t* it)
771 {
772 Item item;
773 esp_err_t err;
774
775 for (auto page = it->page; page != mPageManager.end(); ++page) {
776 do {
777 err = page->findItem(it->nsIndex, (ItemType)it->type, nullptr, it->entryIndex, item);
778 it->entryIndex += item.span;
779 if(err == ESP_OK && isIterableItem(item) && !isMultipageBlob(item)) {
780 fillEntryInfo(item, it->entry_info);
781 it->page = page;
782 return true;
783 }
784 } while (err != ESP_ERR_NVS_NOT_FOUND);
785
786 it->entryIndex = 0;
787 }
788
789 return false;
790 }
791
792
793 }
794