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