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