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