e14b836fcc
This change removes the earlier limitation of 1984 bytes for storing data-blobs. Blobs larger than the sector size are split and stored on multiple sectors. For this purpose, two new datatypes (multi-page index and multi-page data) are added for entries stored in the sectors. The underlying read, write, erase and find operations are modified to support these large blobs. The change is transparent to users of the library and no special APIs need to be used to store these large blobs.
2207 lines
79 KiB
C++
2207 lines
79 KiB
C++
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
#include "catch.hpp"
|
|
#include "nvs.hpp"
|
|
#include "nvs_test_api.h"
|
|
#include "spi_flash_emulation.h"
|
|
#include <sstream>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <unistd.h>
|
|
#include <sys/wait.h>
|
|
|
|
#define TEST_ESP_ERR(rc, res) CHECK((rc) == (res))
|
|
#define TEST_ESP_OK(rc) CHECK((rc) == ESP_OK)
|
|
|
|
using namespace std;
|
|
using namespace nvs;
|
|
|
|
stringstream s_perf;
|
|
|
|
void dumpBytes(const uint8_t* data, size_t count)
|
|
{
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
if (i % 32 == 0) {
|
|
printf("%08x ", i);
|
|
}
|
|
printf("%02x ", data[i]);
|
|
if ((i + 1) % 32 == 0) {
|
|
printf("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("crc32 behaves as expected", "[nvs]")
|
|
{
|
|
Item item1;
|
|
item1.datatype = ItemType::I32;
|
|
item1.nsIndex = 1;
|
|
item1.crc32 = 0;
|
|
item1.chunkIndex = 0xff;
|
|
fill_n(item1.key, sizeof(item1.key), 0xbb);
|
|
fill_n(item1.data, sizeof(item1.data), 0xaa);
|
|
|
|
auto crc32_1 = item1.calculateCrc32();
|
|
|
|
Item item2 = item1;
|
|
item2.crc32 = crc32_1;
|
|
|
|
CHECK(crc32_1 == item2.calculateCrc32());
|
|
|
|
item2 = item1;
|
|
item2.nsIndex = 2;
|
|
CHECK(crc32_1 != item2.calculateCrc32());
|
|
|
|
item2 = item1;
|
|
item2.datatype = ItemType::U32;
|
|
CHECK(crc32_1 != item2.calculateCrc32());
|
|
|
|
item2 = item1;
|
|
strncpy(item2.key, "foo", Item::MAX_KEY_LENGTH);
|
|
CHECK(crc32_1 != item2.calculateCrc32());
|
|
}
|
|
|
|
TEST_CASE("starting with empty flash, page is in uninitialized state", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(1);
|
|
Page page;
|
|
CHECK(page.state() == Page::PageState::INVALID);
|
|
CHECK(page.load(0) == ESP_OK);
|
|
CHECK(page.state() == Page::PageState::UNINITIALIZED);
|
|
}
|
|
|
|
TEST_CASE("can distinguish namespaces", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(1);
|
|
Page page;
|
|
CHECK(page.load(0) == ESP_OK);
|
|
int32_t val1 = 0x12345678;
|
|
CHECK(page.writeItem(1, ItemType::I32, "intval1", &val1, sizeof(val1)) == ESP_OK);
|
|
int32_t val2 = 0x23456789;
|
|
CHECK(page.writeItem(2, ItemType::I32, "intval1", &val2, sizeof(val2)) == ESP_OK);
|
|
|
|
int32_t readVal;
|
|
CHECK(page.readItem(2, ItemType::I32, "intval1", &readVal, sizeof(readVal)) == ESP_OK);
|
|
CHECK(readVal == val2);
|
|
|
|
}
|
|
|
|
TEST_CASE("reading with different type causes type mismatch error", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(1);
|
|
Page page;
|
|
CHECK(page.load(0) == ESP_OK);
|
|
int32_t val = 0x12345678;
|
|
CHECK(page.writeItem(1, ItemType::I32, "intval1", &val, sizeof(val)) == ESP_OK);
|
|
CHECK(page.readItem(1, ItemType::U32, "intval1", &val, sizeof(val)) == ESP_ERR_NVS_TYPE_MISMATCH);
|
|
}
|
|
|
|
TEST_CASE("when page is erased, it's state becomes UNITIALIZED", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(1);
|
|
Page page;
|
|
CHECK(page.load(0) == ESP_OK);
|
|
int32_t val = 0x12345678;
|
|
CHECK(page.writeItem(1, ItemType::I32, "intval1", &val, sizeof(val)) == ESP_OK);
|
|
CHECK(page.erase() == ESP_OK);
|
|
CHECK(page.state() == Page::PageState::UNINITIALIZED);
|
|
}
|
|
|
|
TEST_CASE("when writing and erasing, used/erased counts are updated correctly", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(1);
|
|
Page page;
|
|
CHECK(page.load(0) == ESP_OK);
|
|
CHECK(page.getUsedEntryCount() == 0);
|
|
CHECK(page.getErasedEntryCount() == 0);
|
|
uint32_t foo1 = 0;
|
|
CHECK(page.writeItem(1, "foo1", foo1) == ESP_OK);
|
|
CHECK(page.getUsedEntryCount() == 1);
|
|
CHECK(page.writeItem(2, "foo1", foo1) == ESP_OK);
|
|
CHECK(page.getUsedEntryCount() == 2);
|
|
CHECK(page.eraseItem<uint32_t>(2, "foo1") == ESP_OK);
|
|
CHECK(page.getUsedEntryCount() == 1);
|
|
CHECK(page.getErasedEntryCount() == 1);
|
|
for (size_t i = 0; i < Page::ENTRY_COUNT - 2; ++i) {
|
|
char name[16];
|
|
snprintf(name, sizeof(name), "i%ld", (long int)i);
|
|
CHECK(page.writeItem(1, name, i) == ESP_OK);
|
|
}
|
|
CHECK(page.getUsedEntryCount() == Page::ENTRY_COUNT - 1);
|
|
CHECK(page.getErasedEntryCount() == 1);
|
|
for (size_t i = 0; i < Page::ENTRY_COUNT - 2; ++i) {
|
|
char name[16];
|
|
snprintf(name, sizeof(name), "i%ld", (long int)i);
|
|
CHECK(page.eraseItem(1, itemTypeOf<size_t>(), name) == ESP_OK);
|
|
}
|
|
CHECK(page.getUsedEntryCount() == 1);
|
|
CHECK(page.getErasedEntryCount() == Page::ENTRY_COUNT - 1);
|
|
}
|
|
|
|
TEST_CASE("when page is full, adding an element fails", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(1);
|
|
Page page;
|
|
CHECK(page.load(0) == ESP_OK);
|
|
for (size_t i = 0; i < Page::ENTRY_COUNT; ++i) {
|
|
char name[16];
|
|
snprintf(name, sizeof(name), "i%ld", (long int)i);
|
|
CHECK(page.writeItem(1, name, i) == ESP_OK);
|
|
}
|
|
CHECK(page.writeItem(1, "foo", 64UL) == ESP_ERR_NVS_PAGE_FULL);
|
|
}
|
|
|
|
TEST_CASE("page maintains its seq number")
|
|
{
|
|
SpiFlashEmulator emu(1);
|
|
{
|
|
Page page;
|
|
CHECK(page.load(0) == ESP_OK);
|
|
CHECK(page.setSeqNumber(123) == ESP_OK);
|
|
int32_t val = 42;
|
|
CHECK(page.writeItem(1, ItemType::I32, "dummy", &val, sizeof(val)) == ESP_OK);
|
|
}
|
|
{
|
|
Page page;
|
|
CHECK(page.load(0) == ESP_OK);
|
|
uint32_t seqno;
|
|
CHECK(page.getSeqNumber(seqno) == ESP_OK);
|
|
CHECK(seqno == 123);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("can write and read variable length data", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(1);
|
|
Page page;
|
|
CHECK(page.load(0) == ESP_OK);
|
|
const char str[] = "foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234";
|
|
size_t len = strlen(str);
|
|
CHECK(page.writeItem(1, "stuff1", 42) == ESP_OK);
|
|
CHECK(page.writeItem(1, "stuff2", 1) == ESP_OK);
|
|
CHECK(page.writeItem(1, ItemType::SZ, "foobaar", str, len + 1) == ESP_OK);
|
|
CHECK(page.writeItem(1, "stuff3", 2) == ESP_OK);
|
|
CHECK(page.writeItem(1, ItemType::BLOB, "baz", str, len) == ESP_OK);
|
|
CHECK(page.writeItem(1, "stuff4", 0x7abbccdd) == ESP_OK);
|
|
|
|
char buf[sizeof(str) + 16];
|
|
int32_t value;
|
|
CHECK(page.readItem(1, "stuff1", value) == ESP_OK);
|
|
CHECK(value == 42);
|
|
CHECK(page.readItem(1, "stuff2", value) == ESP_OK);
|
|
CHECK(value == 1);
|
|
CHECK(page.readItem(1, "stuff3", value) == ESP_OK);
|
|
CHECK(value == 2);
|
|
CHECK(page.readItem(1, "stuff4", value) == ESP_OK);
|
|
CHECK(value == 0x7abbccdd);
|
|
|
|
fill_n(buf, sizeof(buf), 0xff);
|
|
CHECK(page.readItem(1, ItemType::SZ, "foobaar", buf, sizeof(buf)) == ESP_OK);
|
|
CHECK(memcmp(buf, str, strlen(str) + 1) == 0);
|
|
|
|
fill_n(buf, sizeof(buf), 0xff);
|
|
CHECK(page.readItem(1, ItemType::BLOB, "baz", buf, sizeof(buf)) == ESP_OK);
|
|
CHECK(memcmp(buf, str, strlen(str)) == 0);
|
|
}
|
|
|
|
TEST_CASE("different key names are distinguished even if the pointer is the same", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(1);
|
|
Page page;
|
|
TEST_ESP_OK(page.load(0));
|
|
TEST_ESP_OK(page.writeItem(1, "i1", 1));
|
|
TEST_ESP_OK(page.writeItem(1, "i2", 2));
|
|
int32_t value;
|
|
char keyname[10] = {0};
|
|
for (int i = 0; i < 2; ++i) {
|
|
strncpy(keyname, "i1", sizeof(keyname) - 1);
|
|
TEST_ESP_OK(page.readItem(1, keyname, value));
|
|
CHECK(value == 1);
|
|
strncpy(keyname, "i2", sizeof(keyname) - 1);
|
|
TEST_ESP_OK(page.readItem(1, keyname, value));
|
|
CHECK(value == 2);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Page validates key size", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(4);
|
|
Page page;
|
|
TEST_ESP_OK(page.load(0));
|
|
// 16-character key fails
|
|
TEST_ESP_ERR(page.writeItem(1, "0123456789123456", 1), ESP_ERR_NVS_KEY_TOO_LONG);
|
|
// 15-character key is okay
|
|
TEST_ESP_OK(page.writeItem(1, "012345678912345", 1));
|
|
}
|
|
|
|
TEST_CASE("Page validates blob size", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(4);
|
|
Page page;
|
|
TEST_ESP_OK(page.load(0));
|
|
|
|
char buf[2048] = { 0 };
|
|
// There are two potential errors here:
|
|
// - not enough space in the page (because one value has been written already)
|
|
// - value is too long
|
|
// Check that the second one is actually returned.
|
|
TEST_ESP_ERR(page.writeItem(1, ItemType::BLOB, "2", buf, Page::ENTRY_COUNT * Page::ENTRY_SIZE), ESP_ERR_NVS_VALUE_TOO_LONG);
|
|
// Should fail as well
|
|
TEST_ESP_ERR(page.writeItem(1, ItemType::BLOB, "2", buf, Page::CHUNK_MAX_SIZE + 1), ESP_ERR_NVS_VALUE_TOO_LONG);
|
|
TEST_ESP_OK(page.writeItem(1, ItemType::BLOB, "2", buf, Page::CHUNK_MAX_SIZE));
|
|
}
|
|
|
|
TEST_CASE("Page handles invalid CRC of variable length items", "[nvs][cur]")
|
|
{
|
|
SpiFlashEmulator emu(4);
|
|
{
|
|
Page page;
|
|
TEST_ESP_OK(page.load(0));
|
|
char buf[128] = {0};
|
|
TEST_ESP_OK(page.writeItem(1, ItemType::BLOB, "1", buf, sizeof(buf)));
|
|
}
|
|
// corrupt header of the item (64 is the offset of the first item in page)
|
|
uint32_t overwrite_buf = 0;
|
|
emu.write(64, &overwrite_buf, 4);
|
|
// load page again
|
|
{
|
|
Page page;
|
|
TEST_ESP_OK(page.load(0));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("can init PageManager in empty flash", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(4);
|
|
PageManager pm;
|
|
CHECK(pm.load(0, 4) == ESP_OK);
|
|
}
|
|
|
|
TEST_CASE("PageManager adds page in the correct order", "[nvs]")
|
|
{
|
|
const size_t pageCount = 8;
|
|
SpiFlashEmulator emu(pageCount);
|
|
uint32_t pageNo[pageCount] = { -1U, 50, 11, -1U, 23, 22, 24, 49};
|
|
|
|
for (uint32_t i = 0; i < pageCount; ++i) {
|
|
Page p;
|
|
p.load(i);
|
|
if (pageNo[i] != -1U) {
|
|
p.setSeqNumber(pageNo[i]);
|
|
p.writeItem(1, "foo", 10U);
|
|
}
|
|
}
|
|
|
|
PageManager pageManager;
|
|
CHECK(pageManager.load(0, pageCount) == ESP_OK);
|
|
|
|
uint32_t lastSeqNo = 0;
|
|
for (auto it = std::begin(pageManager); it != std::end(pageManager); ++it) {
|
|
uint32_t seqNo;
|
|
CHECK(it->getSeqNumber(seqNo) == ESP_OK);
|
|
CHECK(seqNo > lastSeqNo);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("can init storage in empty flash", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(8);
|
|
Storage storage;
|
|
emu.setBounds(4, 8);
|
|
CHECK(storage.init(4, 4) == ESP_OK);
|
|
s_perf << "Time to init empty storage (4 sectors): " << emu.getTotalTime() << " us" << std::endl;
|
|
}
|
|
|
|
TEST_CASE("storage doesn't add duplicates within one page", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(8);
|
|
Storage storage;
|
|
emu.setBounds(4, 8);
|
|
CHECK(storage.init(4, 4) == ESP_OK);
|
|
int bar = 0;
|
|
CHECK(storage.writeItem(1, "bar", bar) == ESP_OK);
|
|
CHECK(storage.writeItem(1, "bar", bar) == ESP_OK);
|
|
|
|
Page page;
|
|
page.load(4);
|
|
CHECK(page.getUsedEntryCount() == 1);
|
|
CHECK(page.getErasedEntryCount() == 1);
|
|
}
|
|
|
|
TEST_CASE("can write one item a thousand times", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(8);
|
|
Storage storage;
|
|
emu.setBounds(4, 8);
|
|
CHECK(storage.init(4, 4) == ESP_OK);
|
|
for (size_t i = 0; i < Page::ENTRY_COUNT * 4 * 2; ++i) {
|
|
REQUIRE(storage.writeItem(1, "i", static_cast<int>(i)) == ESP_OK);
|
|
}
|
|
s_perf << "Time to write one item a thousand times: " << emu.getTotalTime() << " us (" << emu.getEraseOps() << " " << emu.getWriteOps() << " " << emu.getReadOps() << " " << emu.getWriteBytes() << " " << emu.getReadBytes() << ")" << std::endl;
|
|
}
|
|
|
|
TEST_CASE("storage doesn't add duplicates within multiple pages", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(8);
|
|
Storage storage;
|
|
emu.setBounds(4, 8);
|
|
CHECK(storage.init(4, 4) == ESP_OK);
|
|
int bar = 0;
|
|
CHECK(storage.writeItem(1, "bar", bar) == ESP_OK);
|
|
for (size_t i = 0; i < Page::ENTRY_COUNT; ++i) {
|
|
CHECK(storage.writeItem(1, "foo", static_cast<int>(bar)) == ESP_OK);
|
|
}
|
|
CHECK(storage.writeItem(1, "bar", bar) == ESP_OK);
|
|
|
|
Page page;
|
|
page.load(4);
|
|
CHECK(page.findItem(1, itemTypeOf<int>(), "bar") == ESP_ERR_NVS_NOT_FOUND);
|
|
page.load(5);
|
|
CHECK(page.findItem(1, itemTypeOf<int>(), "bar") == ESP_OK);
|
|
}
|
|
|
|
TEST_CASE("storage can find items on second page if first is not fully written and has cached search data", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(3);
|
|
Storage storage;
|
|
CHECK(storage.init(0, 3) == ESP_OK);
|
|
int bar = 0;
|
|
uint8_t bigdata[(Page::CHUNK_MAX_SIZE - Page::ENTRY_SIZE)/2] = {0};
|
|
// write one big chunk of data
|
|
ESP_ERROR_CHECK(storage.writeItem(0, ItemType::BLOB, "1", bigdata, sizeof(bigdata)));
|
|
// write another big chunk of data
|
|
ESP_ERROR_CHECK(storage.writeItem(0, ItemType::BLOB, "2", bigdata, sizeof(bigdata)));
|
|
|
|
// write third one; it will not fit into the first page
|
|
ESP_ERROR_CHECK(storage.writeItem(0, ItemType::BLOB, "3", bigdata, sizeof(bigdata)));
|
|
|
|
size_t size;
|
|
ESP_ERROR_CHECK(storage.getItemDataSize(0, ItemType::BLOB, "1", size));
|
|
CHECK(size == sizeof(bigdata));
|
|
ESP_ERROR_CHECK(storage.getItemDataSize(0, ItemType::BLOB, "3", size));
|
|
CHECK(size == sizeof(bigdata));
|
|
}
|
|
|
|
|
|
TEST_CASE("can write and read variable length data lots of times", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(8);
|
|
Storage storage;
|
|
emu.setBounds(4, 8);
|
|
CHECK(storage.init(4, 4) == ESP_OK);
|
|
const char str[] = "foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234";
|
|
char buf[sizeof(str) + 16];
|
|
size_t len = strlen(str);
|
|
for (size_t i = 0; i < Page::ENTRY_COUNT * 4 * 2; ++i) {
|
|
CAPTURE(i);
|
|
CHECK(storage.writeItem(1, ItemType::SZ, "foobaar", str, len + 1) == ESP_OK);
|
|
CHECK(storage.writeItem(1, "foo", static_cast<uint32_t>(i)) == ESP_OK);
|
|
|
|
uint32_t value;
|
|
CHECK(storage.readItem(1, "foo", value) == ESP_OK);
|
|
CHECK(value == i);
|
|
|
|
fill_n(buf, sizeof(buf), 0xff);
|
|
CHECK(storage.readItem(1, ItemType::SZ, "foobaar", buf, sizeof(buf)) == ESP_OK);
|
|
CHECK(memcmp(buf, str, strlen(str) + 1) == 0);
|
|
}
|
|
s_perf << "Time to write one string and one integer a thousand times: " << emu.getTotalTime() << " us (" << emu.getEraseOps() << " " << emu.getWriteOps() << " " << emu.getReadOps() << " " << emu.getWriteBytes() << " " << emu.getReadBytes() << ")" << std::endl;
|
|
}
|
|
|
|
|
|
TEST_CASE("can get length of variable length data", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(8);
|
|
emu.randomize(200);
|
|
Storage storage;
|
|
emu.setBounds(4, 8);
|
|
CHECK(storage.init(4, 4) == ESP_OK);
|
|
const char str[] = "foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234";
|
|
size_t len = strlen(str);
|
|
CHECK(storage.writeItem(1, ItemType::SZ, "foobaar", str, len + 1) == ESP_OK);
|
|
size_t dataSize;
|
|
CHECK(storage.getItemDataSize(1, ItemType::SZ, "foobaar", dataSize) == ESP_OK);
|
|
CHECK(dataSize == len + 1);
|
|
|
|
CHECK(storage.writeItem(2, ItemType::BLOB, "foobaar", str, len) == ESP_OK);
|
|
CHECK(storage.getItemDataSize(2, ItemType::BLOB, "foobaar", dataSize) == ESP_OK);
|
|
CHECK(dataSize == len);
|
|
}
|
|
|
|
|
|
TEST_CASE("can create namespaces", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(8);
|
|
Storage storage;
|
|
emu.setBounds(4, 8);
|
|
CHECK(storage.init(4, 4) == ESP_OK);
|
|
uint8_t nsi;
|
|
CHECK(storage.createOrOpenNamespace("wifi", false, nsi) == ESP_ERR_NVS_NOT_FOUND);
|
|
|
|
CHECK(storage.createOrOpenNamespace("wifi", true, nsi) == ESP_OK);
|
|
Page page;
|
|
page.load(4);
|
|
CHECK(page.findItem(Page::NS_INDEX, ItemType::U8, "wifi") == ESP_OK);
|
|
}
|
|
|
|
TEST_CASE("storage may become full", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(8);
|
|
Storage storage;
|
|
emu.setBounds(4, 8);
|
|
CHECK(storage.init(4, 4) == ESP_OK);
|
|
for (size_t i = 0; i < Page::ENTRY_COUNT * 3; ++i) {
|
|
char name[Item::MAX_KEY_LENGTH + 1];
|
|
snprintf(name, sizeof(name), "key%05d", static_cast<int>(i));
|
|
REQUIRE(storage.writeItem(1, name, static_cast<int>(i)) == ESP_OK);
|
|
}
|
|
REQUIRE(storage.writeItem(1, "foo", 10) == ESP_ERR_NVS_NOT_ENOUGH_SPACE);
|
|
}
|
|
|
|
TEST_CASE("can modify an item on a page which will be erased", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(2);
|
|
Storage storage;
|
|
CHECK(storage.init(0, 2) == ESP_OK);
|
|
for (size_t i = 0; i < Page::ENTRY_COUNT * 3 + 1; ++i) {
|
|
REQUIRE(storage.writeItem(1, "foo", 42U) == ESP_OK);
|
|
}
|
|
}
|
|
|
|
|
|
TEST_CASE("can erase items", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(3);
|
|
Storage storage;
|
|
CHECK(storage.init(0, 3) == ESP_OK);
|
|
for (size_t i = 0; i < Page::ENTRY_COUNT * 2 - 3; ++i) {
|
|
char name[Item::MAX_KEY_LENGTH + 1];
|
|
snprintf(name, sizeof(name), "key%05d", static_cast<int>(i));
|
|
REQUIRE(storage.writeItem(3, name, static_cast<int>(i)) == ESP_OK);
|
|
}
|
|
CHECK(storage.writeItem(1, "foo", 32) == ESP_OK);
|
|
CHECK(storage.writeItem(2, "foo", 64) == ESP_OK);
|
|
CHECK(storage.eraseItem(2, "foo") == ESP_OK);
|
|
int val;
|
|
CHECK(storage.readItem(1, "foo", val) == ESP_OK);
|
|
CHECK(val == 32);
|
|
CHECK(storage.eraseNamespace(3) == ESP_OK);
|
|
CHECK(storage.readItem(2, "foo", val) == ESP_ERR_NVS_NOT_FOUND);
|
|
CHECK(storage.readItem(3, "key00222", val) == ESP_ERR_NVS_NOT_FOUND);
|
|
}
|
|
|
|
TEST_CASE("nvs api tests", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(10);
|
|
emu.randomize(100);
|
|
|
|
nvs_handle handle_1;
|
|
const uint32_t NVS_FLASH_SECTOR = 6;
|
|
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3;
|
|
emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);
|
|
|
|
|
|
TEST_ESP_ERR(nvs_open("namespace1", NVS_READWRITE, &handle_1), ESP_ERR_NVS_NOT_INITIALIZED);
|
|
for (uint16_t i = NVS_FLASH_SECTOR; i <NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; ++i) {
|
|
spi_flash_erase_sector(i);
|
|
}
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN));
|
|
|
|
TEST_ESP_ERR(nvs_open("namespace1", NVS_READONLY, &handle_1), ESP_ERR_NVS_NOT_FOUND);
|
|
|
|
// TEST_ESP_ERR(nvs_set_i32(handle_1, "foo", 0x12345678), ESP_ERR_NVS_READ_ONLY);
|
|
// nvs_close(handle_1);
|
|
|
|
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle_1));
|
|
TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x12345678));
|
|
TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x23456789));
|
|
|
|
nvs_handle handle_2;
|
|
TEST_ESP_OK(nvs_open("namespace2", NVS_READWRITE, &handle_2));
|
|
TEST_ESP_OK(nvs_set_i32(handle_2, "foo", 0x3456789a));
|
|
const char* str = "value 0123456789abcdef0123456789abcdef";
|
|
TEST_ESP_OK(nvs_set_str(handle_2, "key", str));
|
|
|
|
int32_t v1;
|
|
TEST_ESP_OK(nvs_get_i32(handle_1, "foo", &v1));
|
|
CHECK(0x23456789 == v1);
|
|
|
|
int32_t v2;
|
|
TEST_ESP_OK(nvs_get_i32(handle_2, "foo", &v2));
|
|
CHECK(0x3456789a == v2);
|
|
|
|
char buf[strlen(str) + 1];
|
|
size_t buf_len = sizeof(buf);
|
|
|
|
size_t buf_len_needed;
|
|
TEST_ESP_OK(nvs_get_str(handle_2, "key", NULL, &buf_len_needed));
|
|
CHECK(buf_len_needed == buf_len);
|
|
|
|
size_t buf_len_short = buf_len - 1;
|
|
TEST_ESP_ERR(ESP_ERR_NVS_INVALID_LENGTH, nvs_get_str(handle_2, "key", buf, &buf_len_short));
|
|
CHECK(buf_len_short == buf_len);
|
|
|
|
size_t buf_len_long = buf_len + 1;
|
|
TEST_ESP_OK(nvs_get_str(handle_2, "key", buf, &buf_len_long));
|
|
CHECK(buf_len_long == buf_len);
|
|
|
|
TEST_ESP_OK(nvs_get_str(handle_2, "key", buf, &buf_len));
|
|
|
|
CHECK(0 == strcmp(buf, str));
|
|
}
|
|
|
|
TEST_CASE("wifi test", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(10);
|
|
emu.randomize(10);
|
|
|
|
|
|
const uint32_t NVS_FLASH_SECTOR = 5;
|
|
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3;
|
|
emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN));
|
|
|
|
nvs_handle misc_handle;
|
|
TEST_ESP_OK(nvs_open("nvs.net80211", NVS_READWRITE, &misc_handle));
|
|
char log[33];
|
|
size_t log_size = sizeof(log);
|
|
TEST_ESP_ERR(nvs_get_str(misc_handle, "log", log, &log_size), ESP_ERR_NVS_NOT_FOUND);
|
|
strcpy(log, "foobarbazfizzz");
|
|
TEST_ESP_OK(nvs_set_str(misc_handle, "log", log));
|
|
|
|
nvs_handle net80211_handle;
|
|
TEST_ESP_OK(nvs_open("nvs.net80211", NVS_READWRITE, &net80211_handle));
|
|
|
|
uint8_t opmode = 2;
|
|
TEST_ESP_ERR(nvs_get_u8(net80211_handle, "wifi.opmode", &opmode), ESP_ERR_NVS_NOT_FOUND);
|
|
|
|
TEST_ESP_OK(nvs_set_u8(net80211_handle, "wifi.opmode", opmode));
|
|
|
|
uint8_t country = 0;
|
|
TEST_ESP_ERR(nvs_get_u8(net80211_handle, "wifi.country", &opmode), ESP_ERR_NVS_NOT_FOUND);
|
|
TEST_ESP_OK(nvs_set_u8(net80211_handle, "wifi.country", opmode));
|
|
|
|
char ssid[36];
|
|
size_t size = sizeof(ssid);
|
|
TEST_ESP_ERR(nvs_get_blob(net80211_handle, "sta.ssid", ssid, &size), ESP_ERR_NVS_NOT_FOUND);
|
|
strcpy(ssid, "my android AP");
|
|
TEST_ESP_OK(nvs_set_blob(net80211_handle, "sta.ssid", ssid, size));
|
|
|
|
char mac[6];
|
|
size = sizeof(mac);
|
|
TEST_ESP_ERR(nvs_get_blob(net80211_handle, "sta.mac", mac, &size), ESP_ERR_NVS_NOT_FOUND);
|
|
memset(mac, 0xab, 6);
|
|
TEST_ESP_OK(nvs_set_blob(net80211_handle, "sta.mac", mac, size));
|
|
|
|
uint8_t authmode = 1;
|
|
TEST_ESP_ERR(nvs_get_u8(net80211_handle, "sta.authmode", &authmode), ESP_ERR_NVS_NOT_FOUND);
|
|
TEST_ESP_OK(nvs_set_u8(net80211_handle, "sta.authmode", authmode));
|
|
|
|
char pswd[65];
|
|
size = sizeof(pswd);
|
|
TEST_ESP_ERR(nvs_get_blob(net80211_handle, "sta.pswd", pswd, &size), ESP_ERR_NVS_NOT_FOUND);
|
|
strcpy(pswd, "`123456788990-=");
|
|
TEST_ESP_OK(nvs_set_blob(net80211_handle, "sta.pswd", pswd, size));
|
|
|
|
char pmk[32];
|
|
size = sizeof(pmk);
|
|
TEST_ESP_ERR(nvs_get_blob(net80211_handle, "sta.pmk", pmk, &size), ESP_ERR_NVS_NOT_FOUND);
|
|
memset(pmk, 1, size);
|
|
TEST_ESP_OK(nvs_set_blob(net80211_handle, "sta.pmk", pmk, size));
|
|
|
|
uint8_t chan = 1;
|
|
TEST_ESP_ERR(nvs_get_u8(net80211_handle, "sta.chan", &chan), ESP_ERR_NVS_NOT_FOUND);
|
|
TEST_ESP_OK(nvs_set_u8(net80211_handle, "sta.chan", chan));
|
|
|
|
uint8_t autoconn = 1;
|
|
TEST_ESP_ERR(nvs_get_u8(net80211_handle, "auto.conn", &autoconn), ESP_ERR_NVS_NOT_FOUND);
|
|
TEST_ESP_OK(nvs_set_u8(net80211_handle, "auto.conn", autoconn));
|
|
|
|
uint8_t bssid_set = 1;
|
|
TEST_ESP_ERR(nvs_get_u8(net80211_handle, "bssid.set", &bssid_set), ESP_ERR_NVS_NOT_FOUND);
|
|
TEST_ESP_OK(nvs_set_u8(net80211_handle, "bssid.set", bssid_set));
|
|
|
|
char bssid[6];
|
|
size = sizeof(bssid);
|
|
TEST_ESP_ERR(nvs_get_blob(net80211_handle, "sta.bssid", bssid, &size), ESP_ERR_NVS_NOT_FOUND);
|
|
memset(mac, 0xcd, 6);
|
|
TEST_ESP_OK(nvs_set_blob(net80211_handle, "sta.bssid", bssid, size));
|
|
|
|
uint8_t phym = 3;
|
|
TEST_ESP_ERR(nvs_get_u8(net80211_handle, "sta.phym", &phym), ESP_ERR_NVS_NOT_FOUND);
|
|
TEST_ESP_OK(nvs_set_u8(net80211_handle, "sta.phym", phym));
|
|
|
|
uint8_t phybw = 2;
|
|
TEST_ESP_ERR(nvs_get_u8(net80211_handle, "sta.phybw", &phybw), ESP_ERR_NVS_NOT_FOUND);
|
|
TEST_ESP_OK(nvs_set_u8(net80211_handle, "sta.phybw", phybw));
|
|
|
|
char apsw[2];
|
|
size = sizeof(apsw);
|
|
TEST_ESP_ERR(nvs_get_blob(net80211_handle, "sta.apsw", apsw, &size), ESP_ERR_NVS_NOT_FOUND);
|
|
memset(apsw, 0x2, size);
|
|
TEST_ESP_OK(nvs_set_blob(net80211_handle, "sta.apsw", apsw, size));
|
|
|
|
char apinfo[700];
|
|
size = sizeof(apinfo);
|
|
TEST_ESP_ERR(nvs_get_blob(net80211_handle, "sta.apinfo", apinfo, &size), ESP_ERR_NVS_NOT_FOUND);
|
|
memset(apinfo, 0, size);
|
|
TEST_ESP_OK(nvs_set_blob(net80211_handle, "sta.apinfo", apinfo, size));
|
|
|
|
size = sizeof(ssid);
|
|
TEST_ESP_ERR(nvs_get_blob(net80211_handle, "ap.ssid", ssid, &size), ESP_ERR_NVS_NOT_FOUND);
|
|
strcpy(ssid, "ESP_A2F340");
|
|
TEST_ESP_OK(nvs_set_blob(net80211_handle, "ap.ssid", ssid, size));
|
|
|
|
size = sizeof(mac);
|
|
TEST_ESP_ERR(nvs_get_blob(net80211_handle, "ap.mac", mac, &size), ESP_ERR_NVS_NOT_FOUND);
|
|
memset(mac, 0xac, 6);
|
|
TEST_ESP_OK(nvs_set_blob(net80211_handle, "ap.mac", mac, size));
|
|
|
|
size = sizeof(pswd);
|
|
TEST_ESP_ERR(nvs_get_blob(net80211_handle, "ap.passwd", pswd, &size), ESP_ERR_NVS_NOT_FOUND);
|
|
strcpy(pswd, "");
|
|
TEST_ESP_OK(nvs_set_blob(net80211_handle, "ap.passwd", pswd, size));
|
|
|
|
size = sizeof(pmk);
|
|
TEST_ESP_ERR(nvs_get_blob(net80211_handle, "ap.pmk", pmk, &size), ESP_ERR_NVS_NOT_FOUND);
|
|
memset(pmk, 1, size);
|
|
TEST_ESP_OK(nvs_set_blob(net80211_handle, "ap.pmk", pmk, size));
|
|
|
|
chan = 6;
|
|
TEST_ESP_ERR(nvs_get_u8(net80211_handle, "ap.chan", &chan), ESP_ERR_NVS_NOT_FOUND);
|
|
TEST_ESP_OK(nvs_set_u8(net80211_handle, "ap.chan", chan));
|
|
|
|
authmode = 0;
|
|
TEST_ESP_ERR(nvs_get_u8(net80211_handle, "ap.authmode", &authmode), ESP_ERR_NVS_NOT_FOUND);
|
|
TEST_ESP_OK(nvs_set_u8(net80211_handle, "ap.authmode", authmode));
|
|
|
|
uint8_t hidden = 0;
|
|
TEST_ESP_ERR(nvs_get_u8(net80211_handle, "ap.hidden", &hidden), ESP_ERR_NVS_NOT_FOUND);
|
|
TEST_ESP_OK(nvs_set_u8(net80211_handle, "ap.hidden", hidden));
|
|
|
|
uint8_t max_conn = 4;
|
|
TEST_ESP_ERR(nvs_get_u8(net80211_handle, "ap.max.conn", &max_conn), ESP_ERR_NVS_NOT_FOUND);
|
|
TEST_ESP_OK(nvs_set_u8(net80211_handle, "ap.max.conn", max_conn));
|
|
|
|
uint8_t bcn_interval = 2;
|
|
TEST_ESP_ERR(nvs_get_u8(net80211_handle, "bcn_interval", &bcn_interval), ESP_ERR_NVS_NOT_FOUND);
|
|
TEST_ESP_OK(nvs_set_u8(net80211_handle, "bcn_interval", bcn_interval));
|
|
|
|
s_perf << "Time to simulate nvs init with wifi libs: " << emu.getTotalTime() << " us (" << emu.getEraseOps() << "E " << emu.getWriteOps() << "W " << emu.getReadOps() << "R " << emu.getWriteBytes() << "Wb " << emu.getReadBytes() << "Rb)" << std::endl;
|
|
|
|
}
|
|
|
|
|
|
TEST_CASE("can init storage from flash with random contents", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(10);
|
|
emu.randomize(42);
|
|
|
|
nvs_handle handle;
|
|
const uint32_t NVS_FLASH_SECTOR = 5;
|
|
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3;
|
|
emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN));
|
|
|
|
TEST_ESP_OK(nvs_open("nvs.net80211", NVS_READWRITE, &handle));
|
|
|
|
uint8_t opmode = 2;
|
|
if (nvs_get_u8(handle, "wifi.opmode", &opmode) != ESP_OK) {
|
|
TEST_ESP_OK(nvs_set_u8(handle, "wifi.opmode", opmode));
|
|
}
|
|
}
|
|
|
|
|
|
TEST_CASE("nvs api tests, starting with random data in flash", "[nvs][long]")
|
|
{
|
|
const size_t testIters = 3000;
|
|
int lastPercent = -1;
|
|
for (size_t count = 0; count < testIters; ++count) {
|
|
int percentDone = (int) (count * 100 / testIters);
|
|
if (percentDone != lastPercent) {
|
|
lastPercent = percentDone;
|
|
printf("%d%%\n", percentDone);
|
|
}
|
|
SpiFlashEmulator emu(10);
|
|
emu.randomize(static_cast<uint32_t>(count));
|
|
|
|
const uint32_t NVS_FLASH_SECTOR = 6;
|
|
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3;
|
|
emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);
|
|
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN));
|
|
|
|
nvs_handle handle_1;
|
|
TEST_ESP_ERR(nvs_open("namespace1", NVS_READONLY, &handle_1), ESP_ERR_NVS_NOT_FOUND);
|
|
|
|
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle_1));
|
|
TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x12345678));
|
|
for (size_t i = 0; i < 500; ++i) {
|
|
nvs_handle handle_2;
|
|
TEST_ESP_OK(nvs_open("namespace2", NVS_READWRITE, &handle_2));
|
|
TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x23456789 % (i + 1)));
|
|
TEST_ESP_OK(nvs_set_i32(handle_2, "foo", static_cast<int32_t>(i)));
|
|
const char* str = "value 0123456789abcdef0123456789abcdef %09d";
|
|
char str_buf[128];
|
|
snprintf(str_buf, sizeof(str_buf), str, i + count * 1024);
|
|
TEST_ESP_OK(nvs_set_str(handle_2, "key", str_buf));
|
|
|
|
int32_t v1;
|
|
TEST_ESP_OK(nvs_get_i32(handle_1, "foo", &v1));
|
|
CHECK(0x23456789 % (i + 1) == v1);
|
|
|
|
int32_t v2;
|
|
TEST_ESP_OK(nvs_get_i32(handle_2, "foo", &v2));
|
|
CHECK(static_cast<int32_t>(i) == v2);
|
|
|
|
char buf[128];
|
|
size_t buf_len = sizeof(buf);
|
|
|
|
TEST_ESP_OK(nvs_get_str(handle_2, "key", buf, &buf_len));
|
|
|
|
CHECK(0 == strcmp(buf, str_buf));
|
|
nvs_close(handle_2);
|
|
}
|
|
nvs_close(handle_1);
|
|
}
|
|
}
|
|
extern "C" void nvs_dump(const char *partName);
|
|
|
|
class RandomTest {
|
|
|
|
#ifdef CONFIG_MP_BLOB_SUPPORT
|
|
static const size_t nKeys = 11;
|
|
#else
|
|
static const size_t nKeys = 9;
|
|
#endif
|
|
int32_t v1 = 0, v2 = 0;
|
|
uint64_t v3 = 0, v4 = 0;
|
|
static const size_t strBufLen = 1024;
|
|
#ifdef CONFIG_MP_BLOB_SUPPORT
|
|
static const size_t smallBlobLen = Page::CHUNK_MAX_SIZE / 3;
|
|
static const size_t largeBlobLen = Page::CHUNK_MAX_SIZE * 3;
|
|
#endif
|
|
char v5[strBufLen], v6[strBufLen], v7[strBufLen], v8[strBufLen], v9[strBufLen];
|
|
#ifdef CONFIG_MP_BLOB_SUPPORT
|
|
uint8_t v10[smallBlobLen], v11[largeBlobLen];
|
|
#endif
|
|
bool written[nKeys];
|
|
|
|
public:
|
|
RandomTest()
|
|
{
|
|
std::fill_n(written, nKeys, false);
|
|
}
|
|
|
|
template<typename TGen>
|
|
esp_err_t doRandomThings(nvs_handle handle, TGen gen, size_t& count) {
|
|
|
|
#ifdef CONFIG_MP_BLOB_SUPPORT
|
|
const char* keys[] = {"foo", "bar", "longkey_0123456", "another key", "param1", "param2", "param3", "param4", "param5", "singlepage", "multipage"};
|
|
const ItemType types[] = {ItemType::I32, ItemType::I32, ItemType::U64, ItemType::U64, ItemType::SZ, ItemType::SZ, ItemType::SZ, ItemType::SZ, ItemType::SZ, ItemType::BLOB, ItemType::BLOB};
|
|
|
|
void* values[] = {&v1, &v2, &v3, &v4, &v5, &v6, &v7, &v8, &v9, &v10, &v11};
|
|
#else
|
|
const char* keys[] = {"foo", "bar", "longkey_0123456", "another key", "param1", "param2", "param3", "param4", "param5"};
|
|
const ItemType types[] = {ItemType::I32, ItemType::I32, ItemType::U64, ItemType::U64, ItemType::SZ, ItemType::SZ, ItemType::SZ, ItemType::SZ, ItemType::SZ};
|
|
|
|
void* values[] = {&v1, &v2, &v3, &v4, &v5, &v6, &v7, &v8, &v9};
|
|
#endif
|
|
|
|
const size_t nKeys = sizeof(keys) / sizeof(keys[0]);
|
|
static_assert(nKeys == sizeof(types) / sizeof(types[0]), "");
|
|
static_assert(nKeys == sizeof(values) / sizeof(values[0]), "");
|
|
|
|
auto randomRead = [&](size_t index) -> esp_err_t {
|
|
switch (types[index]) {
|
|
case ItemType::I32:
|
|
{
|
|
int32_t val;
|
|
auto err = nvs_get_i32(handle, keys[index], &val);
|
|
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
|
return err;
|
|
}
|
|
if (!written[index]) {
|
|
REQUIRE(err == ESP_ERR_NVS_NOT_FOUND);
|
|
}
|
|
else {
|
|
REQUIRE(err == ESP_OK);
|
|
REQUIRE(val == *reinterpret_cast<int32_t*>(values[index]));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ItemType::U64:
|
|
{
|
|
uint64_t val;
|
|
auto err = nvs_get_u64(handle, keys[index], &val);
|
|
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
|
return err;
|
|
}
|
|
if (!written[index]) {
|
|
REQUIRE(err == ESP_ERR_NVS_NOT_FOUND);
|
|
}
|
|
else {
|
|
REQUIRE(err == ESP_OK);
|
|
REQUIRE(val == *reinterpret_cast<uint64_t*>(values[index]));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ItemType::SZ:
|
|
{
|
|
char buf[strBufLen];
|
|
size_t len = strBufLen;
|
|
auto err = nvs_get_str(handle, keys[index], buf, &len);
|
|
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
|
return err;
|
|
}
|
|
if (!written[index]) {
|
|
REQUIRE(err == ESP_ERR_NVS_NOT_FOUND);
|
|
}
|
|
else {
|
|
REQUIRE(err == ESP_OK);
|
|
REQUIRE(strncmp(buf, reinterpret_cast<const char*>(values[index]), strBufLen) == 0);
|
|
}
|
|
break;
|
|
}
|
|
#ifdef CONFIG_MP_BLOB_SUPPORT
|
|
case ItemType::BLOB:
|
|
{
|
|
uint32_t blobBufLen = 0;
|
|
if(strncmp(keys[index],"singlepage", sizeof("singlepage")) == 0) {
|
|
blobBufLen = smallBlobLen ;
|
|
} else {
|
|
blobBufLen = largeBlobLen ;
|
|
|
|
}
|
|
uint8_t buf[blobBufLen];
|
|
memset(buf, 0, blobBufLen);
|
|
|
|
size_t len = blobBufLen;
|
|
auto err = nvs_get_blob(handle, keys[index], buf, &len);
|
|
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
|
return err;
|
|
}
|
|
if (!written[index]) {
|
|
REQUIRE(err == ESP_ERR_NVS_NOT_FOUND);
|
|
}
|
|
else {
|
|
REQUIRE(err == ESP_OK);
|
|
REQUIRE(memcmp(buf, reinterpret_cast<const uint8_t*>(values[index]), blobBufLen) == 0);
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
default:
|
|
assert(0);
|
|
}
|
|
return ESP_OK;
|
|
};
|
|
|
|
auto randomWrite = [&](size_t index) -> esp_err_t {
|
|
switch (types[index]) {
|
|
case ItemType::I32:
|
|
{
|
|
int32_t val = static_cast<int32_t>(gen());
|
|
|
|
auto err = nvs_set_i32(handle, keys[index], val);
|
|
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
|
return err;
|
|
}
|
|
if (err == ESP_ERR_NVS_REMOVE_FAILED) {
|
|
written[index] = true;
|
|
*reinterpret_cast<int32_t*>(values[index]) = val;
|
|
return ESP_ERR_FLASH_OP_FAIL;
|
|
}
|
|
REQUIRE(err == ESP_OK);
|
|
written[index] = true;
|
|
*reinterpret_cast<int32_t*>(values[index]) = val;
|
|
break;
|
|
}
|
|
|
|
case ItemType::U64:
|
|
{
|
|
uint64_t val = static_cast<uint64_t>(gen());
|
|
|
|
auto err = nvs_set_u64(handle, keys[index], val);
|
|
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
|
return err;
|
|
}
|
|
if (err == ESP_ERR_NVS_REMOVE_FAILED) {
|
|
written[index] = true;
|
|
*reinterpret_cast<uint64_t*>(values[index]) = val;
|
|
return ESP_ERR_FLASH_OP_FAIL;
|
|
}
|
|
REQUIRE(err == ESP_OK);
|
|
written[index] = true;
|
|
*reinterpret_cast<uint64_t*>(values[index]) = val;
|
|
break;
|
|
}
|
|
|
|
case ItemType::SZ:
|
|
{
|
|
char buf[strBufLen];
|
|
size_t len = strBufLen;
|
|
|
|
size_t strLen = gen() % (strBufLen - 1);
|
|
std::generate_n(buf, strLen, [&]() -> char {
|
|
const char c = static_cast<char>(gen() % 127);
|
|
return (c < 32) ? 32 : c;
|
|
});
|
|
buf[strLen] = 0;
|
|
|
|
auto err = nvs_set_str(handle, keys[index], buf);
|
|
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
|
return err;
|
|
}
|
|
if (err == ESP_ERR_NVS_REMOVE_FAILED) {
|
|
written[index] = true;
|
|
strncpy(reinterpret_cast<char*>(values[index]), buf, strBufLen);
|
|
return ESP_ERR_FLASH_OP_FAIL;
|
|
}
|
|
REQUIRE(err == ESP_OK);
|
|
written[index] = true;
|
|
strncpy(reinterpret_cast<char*>(values[index]), buf, strBufLen);
|
|
break;
|
|
}
|
|
#ifdef CONFIG_MP_BLOB_SUPPORT
|
|
case ItemType::BLOB:
|
|
{
|
|
uint32_t blobBufLen = 0;
|
|
if(strncmp(keys[index],"singlepage", sizeof("singlepage")) == 0) {
|
|
blobBufLen = smallBlobLen ;
|
|
} else {
|
|
blobBufLen = largeBlobLen ;
|
|
}
|
|
uint8_t buf[blobBufLen];
|
|
memset(buf, 0, blobBufLen);
|
|
size_t blobLen = gen() % blobBufLen;
|
|
std::generate_n(buf, blobLen, [&]() -> uint8_t {
|
|
return static_cast<uint8_t>(gen() % 256);
|
|
});
|
|
|
|
auto err = nvs_set_blob(handle, keys[index], buf, blobLen);
|
|
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
|
return err;
|
|
}
|
|
if (err == ESP_ERR_NVS_REMOVE_FAILED) {
|
|
written[index] = true;
|
|
memcpy(reinterpret_cast<uint8_t*>(values[index]), buf, blobBufLen);
|
|
return ESP_ERR_FLASH_OP_FAIL;
|
|
}
|
|
REQUIRE(err == ESP_OK);
|
|
written[index] = true;
|
|
memcpy(reinterpret_cast<char*>(values[index]), buf, blobBufLen);
|
|
break;
|
|
}
|
|
#endif
|
|
default:
|
|
assert(0);
|
|
}
|
|
return ESP_OK;
|
|
};
|
|
|
|
|
|
for (; count != 0; --count) {
|
|
size_t index = gen() % (nKeys);
|
|
switch (gen() % 3) {
|
|
case 0: // read, 1/3
|
|
if (randomRead(index) == ESP_ERR_FLASH_OP_FAIL) {
|
|
return ESP_ERR_FLASH_OP_FAIL;
|
|
}
|
|
break;
|
|
|
|
default: // write, 2/3
|
|
if (randomWrite(index) == ESP_ERR_FLASH_OP_FAIL) {
|
|
return ESP_ERR_FLASH_OP_FAIL;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
#ifdef CONFIG_MP_BLOB_SUPPORT
|
|
esp_err_t handleExternalWriteAtIndex(uint8_t index, const void* value, const size_t len ) {
|
|
if(index == 9) { /* This is only done for small-page blobs for now*/
|
|
if(len > smallBlobLen) {
|
|
return ESP_FAIL;
|
|
}
|
|
memcpy(v10, value, len);
|
|
written[index] = true;
|
|
return ESP_OK;
|
|
} else {
|
|
return ESP_FAIL;
|
|
}
|
|
}
|
|
#endif
|
|
};
|
|
|
|
TEST_CASE("monkey test", "[nvs][monkey]")
|
|
{
|
|
std::random_device rd;
|
|
std::mt19937 gen(rd());
|
|
uint32_t seed = 3;
|
|
gen.seed(seed);
|
|
|
|
SpiFlashEmulator emu(10);
|
|
emu.randomize(seed);
|
|
emu.clearStats();
|
|
|
|
const uint32_t NVS_FLASH_SECTOR = 2;
|
|
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 8;
|
|
emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);
|
|
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN));
|
|
|
|
nvs_handle handle;
|
|
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle));
|
|
RandomTest test;
|
|
size_t count = 1000;
|
|
CHECK(test.doRandomThings(handle, gen, count) == ESP_OK);
|
|
|
|
s_perf << "Monkey test: nErase=" << emu.getEraseOps() << " nWrite=" << emu.getWriteOps() << std::endl;
|
|
}
|
|
|
|
TEST_CASE("test recovery from sudden poweroff", "[long][nvs][recovery][monkey]")
|
|
{
|
|
std::random_device rd;
|
|
std::mt19937 gen(rd());
|
|
uint32_t seed = 3;
|
|
gen.seed(seed);
|
|
const size_t iter_count = 2000;
|
|
|
|
SpiFlashEmulator emu(10);
|
|
|
|
const uint32_t NVS_FLASH_SECTOR = 2;
|
|
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 8;
|
|
emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);
|
|
|
|
size_t totalOps = 0;
|
|
int lastPercent = -1;
|
|
for (uint32_t errDelay = 0; ; ++errDelay) {
|
|
INFO(errDelay);
|
|
emu.randomize(seed);
|
|
emu.clearStats();
|
|
emu.failAfter(errDelay);
|
|
RandomTest test;
|
|
|
|
if (totalOps != 0) {
|
|
int percent = errDelay * 100 / totalOps;
|
|
if (percent > lastPercent) {
|
|
printf("%d/%d (%d%%)\r\n", errDelay, static_cast<int>(totalOps), percent);
|
|
lastPercent = percent;
|
|
}
|
|
}
|
|
|
|
|
|
nvs_handle handle;
|
|
size_t count = iter_count;
|
|
|
|
if (nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) == ESP_OK) {
|
|
if (nvs_open("namespace1", NVS_READWRITE, &handle) == ESP_OK) {
|
|
if(test.doRandomThings(handle, gen, count) != ESP_ERR_FLASH_OP_FAIL) {
|
|
nvs_close(handle);
|
|
break;
|
|
}
|
|
nvs_close(handle);
|
|
}
|
|
}
|
|
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN));
|
|
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle));
|
|
auto res = test.doRandomThings(handle, gen, count);
|
|
if (res != ESP_OK) {
|
|
nvs_dump(NVS_DEFAULT_PART_NAME);
|
|
CHECK(0);
|
|
}
|
|
nvs_close(handle);
|
|
totalOps = emu.getEraseOps() + emu.getWriteBytes() / 4;
|
|
}
|
|
}
|
|
TEST_CASE("test for memory leaks in open/set", "[leaks]")
|
|
{
|
|
SpiFlashEmulator emu(10);
|
|
const uint32_t NVS_FLASH_SECTOR = 6;
|
|
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3;
|
|
emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN));
|
|
|
|
for (int i = 0; i < 100000; ++i) {
|
|
nvs_handle light_handle = 0;
|
|
char lightbulb[1024] = {12, 13, 14, 15, 16};
|
|
TEST_ESP_OK(nvs_open("light", NVS_READWRITE, &light_handle));
|
|
TEST_ESP_OK(nvs_set_blob(light_handle, "key", lightbulb, sizeof(lightbulb)));
|
|
TEST_ESP_OK(nvs_commit(light_handle));
|
|
nvs_close(light_handle);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("duplicate items are removed", "[nvs][dupes]")
|
|
{
|
|
SpiFlashEmulator emu(3);
|
|
{
|
|
// create one item
|
|
nvs::Page p;
|
|
p.load(0);
|
|
p.writeItem<uint8_t>(1, "opmode", 3);
|
|
}
|
|
{
|
|
// add another two without deleting the first one
|
|
nvs::Item item(1, ItemType::U8, 1, "opmode");
|
|
item.data[0] = 2;
|
|
item.crc32 = item.calculateCrc32();
|
|
emu.write(3 * 32, reinterpret_cast<const uint32_t*>(&item), sizeof(item));
|
|
emu.write(4 * 32, reinterpret_cast<const uint32_t*>(&item), sizeof(item));
|
|
uint32_t mask = 0xFFFFFFEA;
|
|
emu.write(32, &mask, 4);
|
|
}
|
|
{
|
|
// load page and check that second item persists
|
|
nvs::Storage s;
|
|
s.init(0, 3);
|
|
uint8_t val;
|
|
ESP_ERROR_CHECK(s.readItem(1, "opmode", val));
|
|
CHECK(val == 2);
|
|
}
|
|
{
|
|
Page p;
|
|
p.load(0);
|
|
CHECK(p.getErasedEntryCount() == 2);
|
|
CHECK(p.getUsedEntryCount() == 1);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("recovery after failure to write data", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(3);
|
|
const char str[] = "value 0123456789abcdef012345678value 0123456789abcdef012345678";
|
|
|
|
// make flash write fail exactly in Page::writeEntryData
|
|
emu.failAfter(17);
|
|
{
|
|
Storage storage;
|
|
TEST_ESP_OK(storage.init(0, 3));
|
|
|
|
TEST_ESP_ERR(storage.writeItem(1, ItemType::SZ, "key", str, strlen(str)), ESP_ERR_FLASH_OP_FAIL);
|
|
|
|
// check that repeated operations cause an error
|
|
TEST_ESP_ERR(storage.writeItem(1, ItemType::SZ, "key", str, strlen(str)), ESP_ERR_NVS_INVALID_STATE);
|
|
|
|
uint8_t val;
|
|
TEST_ESP_ERR(storage.readItem(1, ItemType::U8, "key", &val, sizeof(val)), ESP_ERR_NVS_NOT_FOUND);
|
|
}
|
|
{
|
|
// load page and check that data was erased
|
|
Page p;
|
|
p.load(0);
|
|
CHECK(p.getErasedEntryCount() == 3);
|
|
CHECK(p.getUsedEntryCount() == 0);
|
|
|
|
// try to write again
|
|
TEST_ESP_OK(p.writeItem(1, ItemType::SZ, "key", str, strlen(str)));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("crc errors in item header are handled", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(3);
|
|
Storage storage;
|
|
// prepare some data
|
|
TEST_ESP_OK(storage.init(0, 3));
|
|
TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast<uint8_t>(1)));
|
|
TEST_ESP_OK(storage.writeItem(1, "value1", static_cast<uint32_t>(1)));
|
|
TEST_ESP_OK(storage.writeItem(1, "value2", static_cast<uint32_t>(2)));
|
|
|
|
// corrupt item header
|
|
uint32_t val = 0;
|
|
emu.write(32 * 3, &val, 4);
|
|
|
|
// check that storage can recover
|
|
TEST_ESP_OK(storage.init(0, 3));
|
|
TEST_ESP_OK(storage.readItem(1, "value2", val));
|
|
CHECK(val == 2);
|
|
// check that the corrupted item is no longer present
|
|
TEST_ESP_ERR(ESP_ERR_NVS_NOT_FOUND, storage.readItem(1, "value1", val));
|
|
|
|
// add more items to make the page full
|
|
for (size_t i = 0; i < Page::ENTRY_COUNT; ++i) {
|
|
char item_name[Item::MAX_KEY_LENGTH + 1];
|
|
snprintf(item_name, sizeof(item_name), "item_%ld", (long int)i);
|
|
TEST_ESP_OK(storage.writeItem(1, item_name, static_cast<uint32_t>(i)));
|
|
}
|
|
|
|
// corrupt another item on the full page
|
|
val = 0;
|
|
emu.write(32 * 4, &val, 4);
|
|
|
|
// check that storage can recover
|
|
TEST_ESP_OK(storage.init(0, 3));
|
|
// check that the corrupted item is no longer present
|
|
TEST_ESP_ERR(ESP_ERR_NVS_NOT_FOUND, storage.readItem(1, "value2", val));
|
|
}
|
|
|
|
TEST_CASE("crc error in variable length item is handled", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(3);
|
|
const uint64_t before_val = 0xbef04e;
|
|
const uint64_t after_val = 0xaf7e4;
|
|
// write some data
|
|
{
|
|
Page p;
|
|
p.load(0);
|
|
TEST_ESP_OK(p.writeItem<uint64_t>(0, "before", before_val));
|
|
const char* str = "foobar";
|
|
TEST_ESP_OK(p.writeItem(0, ItemType::SZ, "key", str, strlen(str)));
|
|
TEST_ESP_OK(p.writeItem<uint64_t>(0, "after", after_val));
|
|
}
|
|
// corrupt some data
|
|
uint32_t w;
|
|
CHECK(emu.read(&w, 32 * 3 + 8, sizeof(w)));
|
|
w &= 0xf000000f;
|
|
CHECK(emu.write(32 * 3 + 8, &w, sizeof(w)));
|
|
// load and check
|
|
{
|
|
Page p;
|
|
p.load(0);
|
|
CHECK(p.getUsedEntryCount() == 2);
|
|
CHECK(p.getErasedEntryCount() == 2);
|
|
|
|
uint64_t val;
|
|
TEST_ESP_OK(p.readItem<uint64_t>(0, "before", val));
|
|
CHECK(val == before_val);
|
|
TEST_ESP_ERR(p.findItem(0, ItemType::SZ, "key"), ESP_ERR_NVS_NOT_FOUND);
|
|
TEST_ESP_OK(p.readItem<uint64_t>(0, "after", val));
|
|
CHECK(val == after_val);
|
|
}
|
|
}
|
|
|
|
|
|
TEST_CASE("read/write failure (TW8406)", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(3);
|
|
nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 3);
|
|
for (int attempts = 0; attempts < 3; ++attempts) {
|
|
int i = 0;
|
|
nvs_handle light_handle = 0;
|
|
char key[15] = {0};
|
|
char data[76] = {12, 13, 14, 15, 16};
|
|
uint8_t number = 20;
|
|
size_t data_len = sizeof(data);
|
|
|
|
ESP_ERROR_CHECK(nvs_open("LIGHT", NVS_READWRITE, &light_handle));
|
|
ESP_ERROR_CHECK(nvs_set_u8(light_handle, "RecordNum", number));
|
|
for (i = 0; i < number; ++i) {
|
|
sprintf(key, "light%d", i);
|
|
ESP_ERROR_CHECK(nvs_set_blob(light_handle, key, data, sizeof(data)));
|
|
}
|
|
nvs_commit(light_handle);
|
|
|
|
uint8_t get_number = 0;
|
|
ESP_ERROR_CHECK(nvs_get_u8(light_handle, "RecordNum", &get_number));
|
|
REQUIRE(number == get_number);
|
|
for (i = 0; i < number; ++i) {
|
|
char data[76] = {0};
|
|
sprintf(key, "light%d", i);
|
|
ESP_ERROR_CHECK(nvs_get_blob(light_handle, key, data, &data_len));
|
|
}
|
|
nvs_close(light_handle);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("nvs_flash_init checks for an empty page", "[nvs]")
|
|
{
|
|
#ifdef CONFIG_MP_BLOB_SUPPORT
|
|
const size_t blob_size = Page::CHUNK_MAX_SIZE/2;
|
|
#else
|
|
const size_t blob_size = Page::CHUNK_MAX_SIZE;
|
|
#endif
|
|
uint8_t blob[blob_size] = {0};
|
|
SpiFlashEmulator emu(5);
|
|
TEST_ESP_OK( nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 5) );
|
|
nvs_handle handle;
|
|
TEST_ESP_OK( nvs_open("test", NVS_READWRITE, &handle) );
|
|
// Fill first page
|
|
TEST_ESP_OK( nvs_set_blob(handle, "1a", blob, blob_size) );
|
|
TEST_ESP_OK( nvs_set_blob(handle, "1b", blob, blob_size) );
|
|
// Fill second page
|
|
TEST_ESP_OK( nvs_set_blob(handle, "2a", blob, blob_size) );
|
|
TEST_ESP_OK( nvs_set_blob(handle, "2b", blob, blob_size) );
|
|
// Fill third page
|
|
TEST_ESP_OK( nvs_set_blob(handle, "3a", blob, blob_size) );
|
|
TEST_ESP_OK( nvs_set_blob(handle, "3b", blob, blob_size) );
|
|
TEST_ESP_OK( nvs_commit(handle) );
|
|
nvs_close(handle);
|
|
// first two pages are now full, third one is writable, last two are empty
|
|
// init should fail
|
|
TEST_ESP_ERR( nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 3), ESP_ERR_NVS_NO_FREE_PAGES );
|
|
}
|
|
|
|
TEST_CASE("multiple partitions access check", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(10);
|
|
TEST_ESP_OK( nvs_flash_init_custom("nvs1", 0, 5) );
|
|
TEST_ESP_OK( nvs_flash_init_custom("nvs2", 5, 5) );
|
|
nvs_handle handle1, handle2;
|
|
TEST_ESP_OK( nvs_open_from_partition("nvs1", "test", NVS_READWRITE, &handle1) );
|
|
TEST_ESP_OK( nvs_open_from_partition("nvs2", "test", NVS_READWRITE, &handle2) );
|
|
TEST_ESP_OK( nvs_set_i32(handle1, "foo", 0xdeadbeef));
|
|
TEST_ESP_OK( nvs_set_i32(handle2, "foo", 0xcafebabe));
|
|
int32_t v1, v2;
|
|
TEST_ESP_OK( nvs_get_i32(handle1, "foo", &v1));
|
|
TEST_ESP_OK( nvs_get_i32(handle2, "foo", &v2));
|
|
CHECK(v1 == 0xdeadbeef);
|
|
CHECK(v2 == 0xcafebabe);
|
|
}
|
|
|
|
TEST_CASE("nvs page selection takes into account free entries also not just erased entries", "[nvs]")
|
|
{
|
|
#ifdef CONFIG_MP_BLOB_SUPPORT
|
|
const size_t blob_size = Page::CHUNK_MAX_SIZE/2;
|
|
#else
|
|
const size_t blob_size = Page::CHUNK_MAX_SIZE;
|
|
#endif
|
|
|
|
uint8_t blob[blob_size] = {0};
|
|
SpiFlashEmulator emu(3);
|
|
TEST_ESP_OK( nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 3) );
|
|
nvs_handle handle;
|
|
TEST_ESP_OK( nvs_open("test", NVS_READWRITE, &handle) );
|
|
// Fill first page
|
|
TEST_ESP_OK( nvs_set_blob(handle, "1a", blob, blob_size/3) );
|
|
TEST_ESP_OK( nvs_set_blob(handle, "1b", blob, blob_size) );
|
|
// Fill second page
|
|
TEST_ESP_OK( nvs_set_blob(handle, "2a", blob, blob_size) );
|
|
TEST_ESP_OK( nvs_set_blob(handle, "2b", blob, blob_size) );
|
|
|
|
// The item below should be able to fit the first page.
|
|
TEST_ESP_OK( nvs_set_blob(handle, "3a", blob, 4) );
|
|
TEST_ESP_OK( nvs_commit(handle) );
|
|
nvs_close(handle);
|
|
}
|
|
|
|
TEST_CASE("calculate used and free space", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(6);
|
|
nvs_flash_deinit();
|
|
TEST_ESP_ERR(nvs_get_stats(NULL, NULL), ESP_ERR_INVALID_ARG);
|
|
nvs_stats_t stat1;
|
|
nvs_stats_t stat2;
|
|
TEST_ESP_ERR(nvs_get_stats(NULL, &stat1), ESP_ERR_NVS_NOT_INITIALIZED);
|
|
CHECK(stat1.free_entries == 0);
|
|
CHECK(stat1.namespace_count == 0);
|
|
CHECK(stat1.total_entries == 0);
|
|
CHECK(stat1.used_entries == 0);
|
|
|
|
nvs_handle handle = 0;
|
|
size_t h_count_entries;
|
|
TEST_ESP_ERR(nvs_get_used_entry_count(handle, &h_count_entries), ESP_ERR_NVS_INVALID_HANDLE);
|
|
CHECK(h_count_entries == 0);
|
|
|
|
// init nvs
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 6));
|
|
|
|
TEST_ESP_ERR(nvs_get_used_entry_count(handle, &h_count_entries), ESP_ERR_NVS_INVALID_HANDLE);
|
|
CHECK(h_count_entries == 0);
|
|
|
|
Page p;
|
|
// after erase. empty partition
|
|
TEST_ESP_OK(nvs_get_stats(NULL, &stat1));
|
|
CHECK(stat1.free_entries != 0);
|
|
CHECK(stat1.namespace_count == 0);
|
|
CHECK(stat1.total_entries == 6 * p.ENTRY_COUNT);
|
|
CHECK(stat1.used_entries == 0);
|
|
|
|
// create namespace test_k1
|
|
nvs_handle handle_1;
|
|
TEST_ESP_OK(nvs_open("test_k1", NVS_READWRITE, &handle_1));
|
|
TEST_ESP_OK(nvs_get_stats(NULL, &stat2));
|
|
CHECK(stat2.free_entries + 1 == stat1.free_entries);
|
|
CHECK(stat2.namespace_count == 1);
|
|
CHECK(stat2.total_entries == stat1.total_entries);
|
|
CHECK(stat2.used_entries == 1);
|
|
|
|
// create pair key-value com
|
|
TEST_ESP_OK(nvs_set_i32(handle_1, "com", 0x12345678));
|
|
TEST_ESP_OK(nvs_get_stats(NULL, &stat1));
|
|
CHECK(stat1.free_entries + 1 == stat2.free_entries);
|
|
CHECK(stat1.namespace_count == 1);
|
|
CHECK(stat1.total_entries == stat2.total_entries);
|
|
CHECK(stat1.used_entries == 2);
|
|
|
|
// change value in com
|
|
TEST_ESP_OK(nvs_set_i32(handle_1, "com", 0x01234567));
|
|
TEST_ESP_OK(nvs_get_stats(NULL, &stat2));
|
|
CHECK(stat2.free_entries == stat1.free_entries);
|
|
CHECK(stat2.namespace_count == 1);
|
|
CHECK(stat2.total_entries != 0);
|
|
CHECK(stat2.used_entries == 2);
|
|
|
|
// create pair key-value ru
|
|
TEST_ESP_OK(nvs_set_i32(handle_1, "ru", 0x00FF00FF));
|
|
TEST_ESP_OK(nvs_get_stats(NULL, &stat1));
|
|
CHECK(stat1.free_entries + 1 == stat2.free_entries);
|
|
CHECK(stat1.namespace_count == 1);
|
|
CHECK(stat1.total_entries != 0);
|
|
CHECK(stat1.used_entries == 3);
|
|
|
|
// amount valid pair in namespace 1
|
|
size_t h1_count_entries;
|
|
TEST_ESP_OK(nvs_get_used_entry_count(handle_1, &h1_count_entries));
|
|
CHECK(h1_count_entries == 2);
|
|
|
|
nvs_handle handle_2;
|
|
// create namespace test_k2
|
|
TEST_ESP_OK(nvs_open("test_k2", NVS_READWRITE, &handle_2));
|
|
TEST_ESP_OK(nvs_get_stats(NULL, &stat2));
|
|
CHECK(stat2.free_entries + 1 == stat1.free_entries);
|
|
CHECK(stat2.namespace_count == 2);
|
|
CHECK(stat2.total_entries == stat1.total_entries);
|
|
CHECK(stat2.used_entries == 4);
|
|
|
|
// create pair key-value
|
|
TEST_ESP_OK(nvs_set_i32(handle_2, "su1", 0x00000001));
|
|
TEST_ESP_OK(nvs_set_i32(handle_2, "su2", 0x00000002));
|
|
TEST_ESP_OK(nvs_set_i32(handle_2, "sus", 0x00000003));
|
|
TEST_ESP_OK(nvs_get_stats(NULL, &stat1));
|
|
CHECK(stat1.free_entries + 3 == stat2.free_entries);
|
|
CHECK(stat1.namespace_count == 2);
|
|
CHECK(stat1.total_entries == stat2.total_entries);
|
|
CHECK(stat1.used_entries == 7);
|
|
|
|
CHECK(stat1.total_entries == (stat1.used_entries + stat1.free_entries));
|
|
|
|
// amount valid pair in namespace 2
|
|
size_t h2_count_entries;
|
|
TEST_ESP_OK(nvs_get_used_entry_count(handle_2, &h2_count_entries));
|
|
CHECK(h2_count_entries == 3);
|
|
|
|
CHECK(stat1.used_entries == (h1_count_entries + h2_count_entries + stat1.namespace_count));
|
|
|
|
nvs_close(handle_1);
|
|
nvs_close(handle_2);
|
|
|
|
size_t temp = h2_count_entries;
|
|
TEST_ESP_ERR(nvs_get_used_entry_count(handle_1, &h2_count_entries), ESP_ERR_NVS_INVALID_HANDLE);
|
|
CHECK(h2_count_entries == 0);
|
|
h2_count_entries = temp;
|
|
TEST_ESP_ERR(nvs_get_used_entry_count(handle_1, NULL), ESP_ERR_INVALID_ARG);
|
|
|
|
nvs_handle handle_3;
|
|
// create namespace test_k3
|
|
TEST_ESP_OK(nvs_open("test_k3", NVS_READWRITE, &handle_3));
|
|
TEST_ESP_OK(nvs_get_stats(NULL, &stat2));
|
|
CHECK(stat2.free_entries + 1 == stat1.free_entries);
|
|
CHECK(stat2.namespace_count == 3);
|
|
CHECK(stat2.total_entries == stat1.total_entries);
|
|
CHECK(stat2.used_entries == 8);
|
|
|
|
// create pair blobs
|
|
uint32_t blob[12];
|
|
TEST_ESP_OK(nvs_set_blob(handle_3, "bl1", &blob, sizeof(blob)));
|
|
TEST_ESP_OK(nvs_get_stats(NULL, &stat1));
|
|
#ifdef CONFIG_MP_BLOB_SUPPORT
|
|
CHECK(stat1.free_entries + 4 == stat2.free_entries);
|
|
#else
|
|
CHECK(stat1.free_entries + 3 == stat2.free_entries);
|
|
#endif
|
|
CHECK(stat1.namespace_count == 3);
|
|
CHECK(stat1.total_entries == stat2.total_entries);
|
|
#ifdef CONFIG_MP_BLOB_SUPPORT
|
|
CHECK(stat1.used_entries == 12);
|
|
#else
|
|
CHECK(stat1.used_entries == 11);
|
|
#endif
|
|
// amount valid pair in namespace 2
|
|
size_t h3_count_entries;
|
|
TEST_ESP_OK(nvs_get_used_entry_count(handle_3, &h3_count_entries));
|
|
#ifdef CONFIG_MP_BLOB_SUPPORT
|
|
CHECK(h3_count_entries == 4);
|
|
#else
|
|
CHECK(h3_count_entries == 3);
|
|
#endif
|
|
|
|
CHECK(stat1.used_entries == (h1_count_entries + h2_count_entries + h3_count_entries + stat1.namespace_count));
|
|
|
|
nvs_close(handle_3);
|
|
}
|
|
|
|
TEST_CASE("Recovery from power-off when the entry being erased is not on active page", "[nvs]")
|
|
{
|
|
#ifdef CONFIG_MP_BLOB_SUPPORT
|
|
const size_t blob_size = Page::CHUNK_MAX_SIZE/2;
|
|
#else
|
|
const size_t blob_size = Page::CHUNK_MAX_SIZE;
|
|
#endif
|
|
size_t read_size = blob_size;
|
|
uint8_t blob[blob_size] = {0x11};
|
|
SpiFlashEmulator emu(3);
|
|
TEST_ESP_OK( nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 3) );
|
|
nvs_handle handle;
|
|
TEST_ESP_OK( nvs_open("test", NVS_READWRITE, &handle) );
|
|
|
|
emu.clearStats();
|
|
#ifdef CONFIG_MP_BLOB_SUPPORT
|
|
emu.failAfter(Page::CHUNK_MAX_SIZE/4 + 75);
|
|
#else
|
|
emu.failAfter(2 * Page::CHUNK_MAX_SIZE/4 + 36);
|
|
#endif
|
|
TEST_ESP_OK( nvs_set_blob(handle, "1a", blob, blob_size) );
|
|
TEST_ESP_OK( nvs_set_blob(handle, "1b", blob, blob_size) );
|
|
|
|
TEST_ESP_ERR( nvs_erase_key(handle, "1a"), ESP_ERR_FLASH_OP_FAIL );
|
|
|
|
TEST_ESP_OK( nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 3) );
|
|
|
|
/* Check 1a is erased fully*/
|
|
TEST_ESP_ERR( nvs_get_blob(handle, "1a", blob, &read_size), ESP_ERR_NVS_NOT_FOUND);
|
|
|
|
/* Check 2b is still accessible*/
|
|
TEST_ESP_OK( nvs_get_blob(handle, "1b", blob, &read_size));
|
|
|
|
nvs_close(handle);
|
|
}
|
|
|
|
TEST_CASE("Recovery from power-off when page is being freed.", "[nvs]")
|
|
{
|
|
const size_t blob_size = (Page::ENTRY_COUNT-3) * Page::ENTRY_SIZE;
|
|
size_t read_size = blob_size/2;
|
|
uint8_t blob[blob_size] = {0};
|
|
SpiFlashEmulator emu(3);
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 3));
|
|
nvs_handle handle;
|
|
TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle));
|
|
// Fill first page
|
|
TEST_ESP_OK(nvs_set_blob(handle, "1a", blob, blob_size/3));
|
|
TEST_ESP_OK(nvs_set_blob(handle, "1b", blob, blob_size/3));
|
|
TEST_ESP_OK(nvs_set_blob(handle, "1c", blob, blob_size/4));
|
|
// Fill second page
|
|
TEST_ESP_OK(nvs_set_blob(handle, "2a", blob, blob_size/2));
|
|
TEST_ESP_OK(nvs_set_blob(handle, "2b", blob, blob_size/2));
|
|
|
|
TEST_ESP_OK(nvs_erase_key(handle, "1c"));
|
|
|
|
emu.clearStats();
|
|
emu.failAfter(6 * Page::ENTRY_COUNT);
|
|
TEST_ESP_ERR(nvs_set_blob(handle, "1d", blob, blob_size/4), ESP_ERR_FLASH_OP_FAIL);
|
|
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 3));
|
|
|
|
read_size = blob_size/3;
|
|
TEST_ESP_OK( nvs_get_blob(handle, "1a", blob, &read_size));
|
|
TEST_ESP_OK( nvs_get_blob(handle, "1b", blob, &read_size));
|
|
|
|
read_size = blob_size /4;
|
|
TEST_ESP_ERR( nvs_get_blob(handle, "1c", blob, &read_size), ESP_ERR_NVS_NOT_FOUND);
|
|
TEST_ESP_ERR( nvs_get_blob(handle, "1d", blob, &read_size), ESP_ERR_NVS_NOT_FOUND);
|
|
|
|
read_size = blob_size /2;
|
|
TEST_ESP_OK( nvs_get_blob(handle, "2a", blob, &read_size));
|
|
TEST_ESP_OK( nvs_get_blob(handle, "2b", blob, &read_size));
|
|
|
|
TEST_ESP_OK(nvs_commit(handle));
|
|
nvs_close(handle);
|
|
}
|
|
|
|
#ifdef CONFIG_MP_BLOB_SUPPORT
|
|
|
|
TEST_CASE("Multi-page blobs are supported", "[nvs]")
|
|
{
|
|
const size_t blob_size = Page::CHUNK_MAX_SIZE *2;
|
|
uint8_t blob[blob_size] = {0};
|
|
SpiFlashEmulator emu(5);
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 5));
|
|
nvs_handle handle;
|
|
TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle));
|
|
TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size));
|
|
TEST_ESP_OK(nvs_commit(handle));
|
|
nvs_close(handle);
|
|
}
|
|
|
|
TEST_CASE("Failures are handled while storing multi-page blobs", "[nvs]")
|
|
{
|
|
const size_t blob_size = Page::CHUNK_MAX_SIZE *7;
|
|
uint8_t blob[blob_size] = {0};
|
|
SpiFlashEmulator emu(5);
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 5));
|
|
nvs_handle handle;
|
|
TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle));
|
|
TEST_ESP_ERR(nvs_set_blob(handle, "abc", blob, blob_size), ESP_ERR_NVS_VALUE_TOO_LONG);
|
|
TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, Page::CHUNK_MAX_SIZE*2));
|
|
TEST_ESP_OK(nvs_commit(handle));
|
|
nvs_close(handle);
|
|
}
|
|
|
|
TEST_CASE("Reading multi-page blobs", "[nvs]")
|
|
{
|
|
const size_t blob_size = Page::CHUNK_MAX_SIZE *3;
|
|
uint8_t blob[blob_size];
|
|
uint8_t blob_read[blob_size];
|
|
size_t read_size = blob_size;
|
|
SpiFlashEmulator emu(5);
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 5));
|
|
nvs_handle handle;
|
|
memset(blob, 0x11, blob_size);
|
|
memset(blob_read, 0xee, blob_size);
|
|
TEST_ESP_OK(nvs_open("readTest", NVS_READWRITE, &handle));
|
|
TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size));
|
|
TEST_ESP_OK(nvs_get_blob(handle, "abc", blob_read, &read_size));
|
|
CHECK(memcmp(blob, blob_read, blob_size) == 0);
|
|
TEST_ESP_OK(nvs_commit(handle));
|
|
nvs_close(handle);
|
|
}
|
|
|
|
TEST_CASE("Modification of values for Multi-page blobs are supported", "[nvs]")
|
|
{
|
|
const size_t blob_size = Page::CHUNK_MAX_SIZE *2;
|
|
uint8_t blob[blob_size] = {0};
|
|
uint8_t blob_read[blob_size] = {0xfe};;
|
|
uint8_t blob2[blob_size] = {0x11};
|
|
uint8_t blob3[blob_size] = {0x22};
|
|
uint8_t blob4[blob_size] ={ 0x33};
|
|
size_t read_size = blob_size;
|
|
SpiFlashEmulator emu(6);
|
|
TEST_ESP_OK( nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 6) );
|
|
nvs_handle handle;
|
|
memset(blob, 0x11, blob_size);
|
|
memset(blob2, 0x22, blob_size);
|
|
memset(blob3, 0x33, blob_size);
|
|
memset(blob4, 0x44, blob_size);
|
|
memset(blob_read, 0xff, blob_size);
|
|
TEST_ESP_OK( nvs_open("test", NVS_READWRITE, &handle) );
|
|
TEST_ESP_OK( nvs_set_blob(handle, "abc", blob, blob_size) );
|
|
TEST_ESP_OK( nvs_set_blob(handle, "abc", blob2, blob_size) );
|
|
TEST_ESP_OK( nvs_set_blob(handle, "abc", blob3, blob_size) );
|
|
TEST_ESP_OK( nvs_set_blob(handle, "abc", blob4, blob_size) );
|
|
TEST_ESP_OK( nvs_get_blob(handle, "abc", blob_read, &read_size));
|
|
CHECK(memcmp(blob4, blob_read, blob_size) == 0);
|
|
TEST_ESP_OK( nvs_commit(handle) );
|
|
nvs_close(handle);
|
|
}
|
|
|
|
TEST_CASE("Modification from single page blob to multi-page", "[nvs]")
|
|
{
|
|
const size_t blob_size = Page::CHUNK_MAX_SIZE *3;
|
|
uint8_t blob[blob_size] = {0};
|
|
uint8_t blob_read[blob_size] = {0xff};
|
|
size_t read_size = blob_size;
|
|
SpiFlashEmulator emu(5);
|
|
TEST_ESP_OK( nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 5) );
|
|
nvs_handle handle;
|
|
TEST_ESP_OK(nvs_open("Test", NVS_READWRITE, &handle) );
|
|
TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, Page::CHUNK_MAX_SIZE/2));
|
|
TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size));
|
|
TEST_ESP_OK(nvs_get_blob(handle, "abc", blob_read, &read_size));
|
|
CHECK(memcmp(blob, blob_read, blob_size) == 0);
|
|
TEST_ESP_OK(nvs_commit(handle) );
|
|
nvs_close(handle);
|
|
}
|
|
|
|
TEST_CASE("Modification from multi-page to single page", "[nvs]")
|
|
{
|
|
const size_t blob_size = Page::CHUNK_MAX_SIZE *3;
|
|
uint8_t blob[blob_size] = {0};
|
|
uint8_t blob_read[blob_size] = {0xff};
|
|
size_t read_size = blob_size;
|
|
SpiFlashEmulator emu(5);
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 5) );
|
|
nvs_handle handle;
|
|
TEST_ESP_OK(nvs_open("Test", NVS_READWRITE, &handle) );
|
|
TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size));
|
|
TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, Page::CHUNK_MAX_SIZE/2));
|
|
TEST_ESP_OK(nvs_set_blob(handle, "abc2", blob, blob_size));
|
|
TEST_ESP_OK(nvs_get_blob(handle, "abc", blob_read, &read_size));
|
|
CHECK(memcmp(blob, blob_read, Page::CHUNK_MAX_SIZE) == 0);
|
|
TEST_ESP_OK(nvs_commit(handle) );
|
|
nvs_close(handle);
|
|
}
|
|
|
|
|
|
TEST_CASE("Check that orphaned blobs are erased during init", "[nvs]")
|
|
{
|
|
const size_t blob_size = Page::CHUNK_MAX_SIZE *3 ;
|
|
uint8_t blob[blob_size] = {0x11};
|
|
uint8_t blob2[blob_size] = {0x22};
|
|
uint8_t blob3[blob_size] = {0x33};
|
|
SpiFlashEmulator emu(5);
|
|
Storage storage;
|
|
|
|
TEST_ESP_OK(storage.init(0, 5));
|
|
|
|
TEST_ESP_OK(storage.writeItem(1, ItemType::BLOB, "key", blob, sizeof(blob)));
|
|
|
|
|
|
TEST_ESP_OK(storage.init(0, 5));
|
|
/* Check that multi-page item is still available.**/
|
|
TEST_ESP_OK(storage.readItem(1, ItemType::BLOB, "key", blob, sizeof(blob)));
|
|
|
|
TEST_ESP_ERR(storage.writeItem(1, ItemType::BLOB, "key2", blob, sizeof(blob)), ESP_ERR_NVS_NOT_ENOUGH_SPACE);
|
|
|
|
Page p;
|
|
p.load(3); // This is where index will be placed.
|
|
p.erase();
|
|
|
|
TEST_ESP_OK(storage.init(0, 5));
|
|
|
|
TEST_ESP_ERR(storage.readItem(1, ItemType::BLOB, "key", blob, sizeof(blob)), ESP_ERR_NVS_NOT_FOUND);
|
|
TEST_ESP_OK(storage.writeItem(1, ItemType::BLOB, "key3", blob, sizeof(blob)));
|
|
}
|
|
|
|
TEST_CASE("nvs code handles errors properly when partition is near to full", "[nvs]")
|
|
{
|
|
const size_t blob_size = Page::CHUNK_MAX_SIZE * 0.3 ;
|
|
uint8_t blob[blob_size] = {0x11};
|
|
SpiFlashEmulator emu(5);
|
|
Storage storage;
|
|
char nvs_key[16] = "";
|
|
|
|
TEST_ESP_OK(storage.init(0, 5));
|
|
|
|
/* Four pages should fit roughly 12 blobs*/
|
|
for(uint8_t count = 1; count <= 12; count++) {
|
|
sprintf(nvs_key, "key:%u", count);
|
|
TEST_ESP_OK(storage.writeItem(1, ItemType::BLOB, nvs_key, blob, sizeof(blob)));
|
|
}
|
|
|
|
for(uint8_t count = 13; count <= 20; count++) {
|
|
sprintf(nvs_key, "key:%u", count);
|
|
TEST_ESP_ERR(storage.writeItem(1, ItemType::BLOB, nvs_key, blob, sizeof(blob)), ESP_ERR_NVS_NOT_ENOUGH_SPACE);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Check for nvs version incompatibility", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu(3);
|
|
|
|
int32_t val1 = 0x12345678;
|
|
Page p;
|
|
p.load(0);
|
|
TEST_ESP_OK(p.setVersion(Page::NVS_VERSION - 1));
|
|
TEST_ESP_OK(p.writeItem(1, ItemType::I32, "foo", &val1, sizeof(val1)));
|
|
|
|
TEST_ESP_ERR(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 3), ESP_ERR_NVS_NEW_VERSION_FOUND);
|
|
}
|
|
|
|
TEST_CASE("Check that NVS supports old blob format without blob index", "[nvs]")
|
|
{
|
|
SpiFlashEmulator emu("../nvs_partition_generator/part_old_blob_format.bin");
|
|
nvs_handle handle;
|
|
|
|
TEST_ESP_OK( nvs_flash_init_custom("test", 0, 2) );
|
|
TEST_ESP_OK( nvs_open_from_partition("test", "dummyNamespace", NVS_READONLY, &handle));
|
|
|
|
char buf[64] = {0};
|
|
size_t buflen = 64;
|
|
uint8_t hexdata[] = {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef};
|
|
TEST_ESP_OK( nvs_get_blob(handle, "dummyHex2BinKey", buf, &buflen));
|
|
CHECK(memcmp(buf, hexdata, buflen) == 0);
|
|
|
|
buflen = 64;
|
|
uint8_t base64data[] = {'1', '2', '3', 'a', 'b', 'c'};
|
|
TEST_ESP_OK( nvs_get_blob(handle, "dummyBase64Key", buf, &buflen));
|
|
CHECK(memcmp(buf, base64data, buflen) == 0);
|
|
|
|
Page p;
|
|
p.load(0);
|
|
|
|
/* Check that item is stored in old format without blob index*/
|
|
TEST_ESP_OK(p.findItem(1, ItemType::BLOB, "dummyHex2BinKey"));
|
|
|
|
/* Modify the blob so that it is stored in the new format*/
|
|
hexdata[0] = hexdata[1] = hexdata[2] = 0x99;
|
|
TEST_ESP_OK(nvs_set_blob(handle, "dummyHex2BinKey", hexdata, sizeof(hexdata)));
|
|
|
|
Page p2;
|
|
p2.load(0);
|
|
|
|
/* Check the type of the blob. Expect type mismatch since the blob is stored in new format*/
|
|
TEST_ESP_ERR(p2.findItem(1, ItemType::BLOB, "dummyHex2BinKey"), ESP_ERR_NVS_TYPE_MISMATCH);
|
|
|
|
/* Check that index is present for the modified blob according to new format*/
|
|
TEST_ESP_OK(p2.findItem(1, ItemType::BLOB_IDX, "dummyHex2BinKey"));
|
|
|
|
/* Read the blob in new format and check the contents*/
|
|
buflen = 64;
|
|
TEST_ESP_OK( nvs_get_blob(handle, "dummyBase64Key", buf, &buflen));
|
|
CHECK(memcmp(buf, base64data, buflen) == 0);
|
|
|
|
}
|
|
|
|
TEST_CASE("monkey test with old-format blob present", "[nvs][monkey]")
|
|
{
|
|
std::random_device rd;
|
|
std::mt19937 gen(rd());
|
|
uint32_t seed = 3;
|
|
gen.seed(seed);
|
|
|
|
SpiFlashEmulator emu(10);
|
|
emu.randomize(seed);
|
|
emu.clearStats();
|
|
|
|
const uint32_t NVS_FLASH_SECTOR = 2;
|
|
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 8;
|
|
static const size_t smallBlobLen = Page::CHUNK_MAX_SIZE / 3;
|
|
|
|
emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);
|
|
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN));
|
|
|
|
nvs_handle handle;
|
|
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle));
|
|
RandomTest test;
|
|
|
|
for ( uint8_t it = 0; it < 10; it++) {
|
|
size_t count = 200;
|
|
|
|
/* Erase index and chunks for the blob with "singlepage" key */
|
|
for (uint8_t num = NVS_FLASH_SECTOR; num < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; num++) {
|
|
Page p;
|
|
p.load(num);
|
|
p.eraseItem(1, ItemType::BLOB, "singlepage", Item::CHUNK_ANY, VerOffset::VER_ANY);
|
|
p.eraseItem(1, ItemType::BLOB_IDX, "singlepage", Item::CHUNK_ANY, VerOffset::VER_ANY);
|
|
p.eraseItem(1, ItemType::BLOB_DATA, "singlepage", Item::CHUNK_ANY, VerOffset::VER_ANY);
|
|
}
|
|
|
|
/* Now write "singlepage" blob in old format*/
|
|
for (uint8_t num = NVS_FLASH_SECTOR; num < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; num++) {
|
|
Page p;
|
|
p.load(num);
|
|
if (p.state() == Page::PageState::ACTIVE) {
|
|
uint8_t buf[smallBlobLen];
|
|
size_t blobLen = gen() % smallBlobLen;
|
|
|
|
if(blobLen > p.getVarDataTailroom()) {
|
|
blobLen = p.getVarDataTailroom();
|
|
}
|
|
|
|
std::generate_n(buf, blobLen, [&]() -> uint8_t {
|
|
return static_cast<uint8_t>(gen() % 256);
|
|
});
|
|
|
|
TEST_ESP_OK(p.writeItem(1, ItemType::BLOB, "singlepage", buf, blobLen, Item::CHUNK_ANY));
|
|
TEST_ESP_OK(p.findItem(1, ItemType::BLOB, "singlepage"));
|
|
test.handleExternalWriteAtIndex(9, buf, blobLen); // This assumes "singlepage" is always at index 9
|
|
|
|
break;
|
|
}
|
|
}
|
|
/* Initialize again */
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN));
|
|
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle));
|
|
|
|
/* Perform random things */
|
|
auto res = test.doRandomThings(handle, gen, count);
|
|
if (res != ESP_OK) {
|
|
nvs_dump(NVS_DEFAULT_PART_NAME);
|
|
CHECK(0);
|
|
}
|
|
|
|
/* Check that only one version is present for "singlepage". Its possible that last iteration did not write
|
|
* anything for "singlepage". So either old version or new version should be present.*/
|
|
bool oldVerPresent = false, newVerPresent = false;
|
|
|
|
for (uint8_t num = NVS_FLASH_SECTOR; num < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; num++) {
|
|
Page p;
|
|
p.load(num);
|
|
if(!oldVerPresent && p.findItem(1, ItemType::BLOB, "singlepage", Item::CHUNK_ANY, VerOffset::VER_ANY) == ESP_OK) {
|
|
oldVerPresent = true;
|
|
}
|
|
|
|
if(!newVerPresent && p.findItem(1, ItemType::BLOB_IDX, "singlepage", Item::CHUNK_ANY, VerOffset::VER_ANY) == ESP_OK) {
|
|
newVerPresent = true;
|
|
}
|
|
}
|
|
CHECK(oldVerPresent != newVerPresent);
|
|
}
|
|
|
|
s_perf << "Monkey test: nErase=" << emu.getEraseOps() << " nWrite=" << emu.getWriteOps() << std::endl;
|
|
}
|
|
|
|
TEST_CASE("Recovery from power-off during modification of blob present in old-format (same page)", "[nvs]")
|
|
{
|
|
std::random_device rd;
|
|
std::mt19937 gen(rd());
|
|
uint32_t seed = 3;
|
|
gen.seed(seed);
|
|
|
|
SpiFlashEmulator emu(3);
|
|
emu.clearStats();
|
|
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 3));
|
|
|
|
nvs_handle handle;
|
|
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle));
|
|
|
|
uint8_t hexdata[] = {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef};
|
|
uint8_t hexdata_old[] = {0x11, 0x12, 0x13, 0xbb, 0xcc, 0xee};
|
|
size_t buflen = sizeof(hexdata);
|
|
uint8_t buf[Page::CHUNK_MAX_SIZE];
|
|
|
|
/* Power-off when blob was being written on the same page where its old version in old format
|
|
* was present*/
|
|
Page p;
|
|
p.load(0);
|
|
/* Write blob in old-format*/
|
|
TEST_ESP_OK(p.writeItem(1, ItemType::BLOB, "singlepage", hexdata_old, sizeof(hexdata_old)));
|
|
|
|
/* Write blob in new format*/
|
|
TEST_ESP_OK(p.writeItem(1, ItemType::BLOB_DATA, "singlepage", hexdata, sizeof(hexdata), 0));
|
|
/* All pages are stored. Now store the index.*/
|
|
Item item;
|
|
item.blobIndex.dataSize = sizeof(hexdata);
|
|
item.blobIndex.chunkCount = 1;
|
|
item.blobIndex.chunkStart = VerOffset::VER_0_OFFSET;
|
|
|
|
TEST_ESP_OK(p.writeItem(1, ItemType::BLOB_IDX, "singlepage", item.data, sizeof(item.data)));
|
|
|
|
TEST_ESP_OK(p.findItem(1, ItemType::BLOB, "singlepage"));
|
|
|
|
/* Initialize again */
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 3));
|
|
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle));
|
|
|
|
TEST_ESP_OK( nvs_get_blob(handle, "singlepage", buf, &buflen));
|
|
CHECK(memcmp(buf, hexdata, buflen) == 0);
|
|
|
|
Page p2;
|
|
p2.load(0);
|
|
TEST_ESP_ERR(p2.findItem(1, ItemType::BLOB, "singlepage"), ESP_ERR_NVS_TYPE_MISMATCH);
|
|
|
|
}
|
|
|
|
TEST_CASE("Recovery from power-off during modification of blob present in old-format (different page)", "[nvs]")
|
|
{
|
|
std::random_device rd;
|
|
std::mt19937 gen(rd());
|
|
uint32_t seed = 3;
|
|
gen.seed(seed);
|
|
|
|
SpiFlashEmulator emu(3);
|
|
emu.clearStats();
|
|
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 3));
|
|
|
|
nvs_handle handle;
|
|
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle));
|
|
|
|
uint8_t hexdata[] = {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef};
|
|
uint8_t hexdata_old[] = {0x11, 0x12, 0x13, 0xbb, 0xcc, 0xee};
|
|
size_t buflen = sizeof(hexdata);
|
|
uint8_t buf[Page::CHUNK_MAX_SIZE];
|
|
|
|
|
|
/* Power-off when blob was being written on the different page where its old version in old format
|
|
* was present*/
|
|
Page p;
|
|
p.load(0);
|
|
/* Write blob in old-format*/
|
|
TEST_ESP_OK(p.writeItem(1, ItemType::BLOB, "singlepage", hexdata_old, sizeof(hexdata_old)));
|
|
|
|
/* Write blob in new format*/
|
|
TEST_ESP_OK(p.writeItem(1, ItemType::BLOB_DATA, "singlepage", hexdata, sizeof(hexdata), 0));
|
|
/* All pages are stored. Now store the index.*/
|
|
Item item;
|
|
item.blobIndex.dataSize = sizeof(hexdata);
|
|
item.blobIndex.chunkCount = 1;
|
|
item.blobIndex.chunkStart = VerOffset::VER_0_OFFSET;
|
|
p.markFull();
|
|
Page p2;
|
|
p2.load(1);
|
|
p2.setSeqNumber(1);
|
|
|
|
TEST_ESP_OK(p2.writeItem(1, ItemType::BLOB_IDX, "singlepage", item.data, sizeof(item.data)));
|
|
|
|
TEST_ESP_OK(p.findItem(1, ItemType::BLOB, "singlepage"));
|
|
|
|
/* Initialize again */
|
|
TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 3));
|
|
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle));
|
|
|
|
TEST_ESP_OK( nvs_get_blob(handle, "singlepage", buf, &buflen));
|
|
CHECK(memcmp(buf, hexdata, buflen) == 0);
|
|
|
|
Page p3;
|
|
p3.load(0);
|
|
TEST_ESP_ERR(p3.findItem(1, ItemType::BLOB, "singlepage"), ESP_ERR_NVS_NOT_FOUND);
|
|
}
|
|
#endif
|
|
/* Add new tests above */
|
|
/* This test has to be the final one */
|
|
|
|
TEST_CASE("check partition generation utility with multipage blob support disabled", "[nvs_part_gen]")
|
|
{
|
|
int childpid = fork();
|
|
if (childpid == 0) {
|
|
exit(execlp("python", "python",
|
|
"../nvs_partition_generator/nvs_partition_gen.py",
|
|
"../nvs_partition_generator/sample_singlepage_blob.csv",
|
|
"../nvs_partition_generator/partition_single_page.bin",
|
|
"--version",
|
|
"v1",NULL));
|
|
} else {
|
|
CHECK(childpid > 0);
|
|
int status;
|
|
waitpid(childpid, &status, 0);
|
|
CHECK(WEXITSTATUS(status) != -1);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("read data from partition generated via partition generation utility with multipage blob support disabled", "[nvs_part_gen]")
|
|
{
|
|
SpiFlashEmulator emu("../nvs_partition_generator/partition_single_page.bin");
|
|
nvs_handle handle;
|
|
TEST_ESP_OK( nvs_flash_init_custom("test", 0, 2) );
|
|
TEST_ESP_OK( nvs_open_from_partition("test", "dummyNamespace", NVS_READONLY, &handle));
|
|
uint8_t u8v;
|
|
TEST_ESP_OK( nvs_get_u8(handle, "dummyU8Key", &u8v));
|
|
CHECK(u8v == 127);
|
|
int8_t i8v;
|
|
TEST_ESP_OK( nvs_get_i8(handle, "dummyI8Key", &i8v));
|
|
CHECK(i8v == -128);
|
|
uint16_t u16v;
|
|
TEST_ESP_OK( nvs_get_u16(handle, "dummyU16Key", &u16v));
|
|
CHECK(u16v == 32768);
|
|
uint32_t u32v;
|
|
TEST_ESP_OK( nvs_get_u32(handle, "dummyU32Key", &u32v));
|
|
CHECK(u32v == 4294967295);
|
|
int32_t i32v;
|
|
TEST_ESP_OK( nvs_get_i32(handle, "dummyI32Key", &i32v));
|
|
CHECK(i32v == -2147483648);
|
|
|
|
char buf[64] = {0};
|
|
size_t buflen = 64;
|
|
TEST_ESP_OK( nvs_get_str(handle, "dummyStringKey", buf, &buflen));
|
|
CHECK(strncmp(buf, "0A:0B:0C:0D:0E:0F", buflen) == 0);
|
|
|
|
uint8_t hexdata[] = {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef};
|
|
buflen = 64;
|
|
int j;
|
|
TEST_ESP_OK( nvs_get_blob(handle, "dummyHex2BinKey", buf, &buflen));
|
|
CHECK(memcmp(buf, hexdata, buflen) == 0);
|
|
|
|
uint8_t base64data[] = {'1', '2', '3', 'a', 'b', 'c'};
|
|
TEST_ESP_OK( nvs_get_blob(handle, "dummyBase64Key", buf, &buflen));
|
|
CHECK(memcmp(buf, base64data, buflen) == 0);
|
|
|
|
buflen = 64;
|
|
uint8_t hexfiledata[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
|
|
TEST_ESP_OK( nvs_get_blob(handle, "hexFileKey", buf, &buflen));
|
|
CHECK(memcmp(buf, hexfiledata, buflen) == 0);
|
|
|
|
buflen = 64;
|
|
uint8_t strfiledata[64] = "abcdefghijklmnopqrstuvwxyz\0";
|
|
TEST_ESP_OK( nvs_get_str(handle, "stringFileKey", buf, &buflen));
|
|
CHECK(memcmp(buf, strfiledata, buflen) == 0);
|
|
|
|
char bin_data[5200];
|
|
size_t bin_len = sizeof(bin_data);
|
|
char binfiledata[5200];
|
|
ifstream file;
|
|
file.open("../nvs_partition_generator/testdata/sample_singlepage_blob.bin");
|
|
file.read(binfiledata,5200);
|
|
TEST_ESP_OK( nvs_get_blob(handle, "binFileKey", bin_data, &bin_len));
|
|
CHECK(memcmp(bin_data, binfiledata, bin_len) == 0);
|
|
|
|
file.close();
|
|
|
|
}
|
|
|
|
#ifdef CONFIG_MP_BLOB_SUPPORT
|
|
TEST_CASE("check partition generation utility with multipage blob support enabled", "[nvs_part_gen]")
|
|
{
|
|
int childpid = fork();
|
|
if (childpid == 0) {
|
|
exit(execlp("python", "python",
|
|
"../nvs_partition_generator/nvs_partition_gen.py",
|
|
"../nvs_partition_generator/sample_multipage_blob.csv",
|
|
"../nvs_partition_generator/partition_multipage_blob.bin",
|
|
"--version",
|
|
"v2",NULL));
|
|
} else {
|
|
CHECK(childpid > 0);
|
|
int status;
|
|
waitpid(childpid, &status, 0);
|
|
CHECK(WEXITSTATUS(status) != -1);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("read data from partition generated via partition generation utility with multipage blob support enabled", "[nvs_part_gen]")
|
|
{
|
|
SpiFlashEmulator emu("../nvs_partition_generator/partition_multipage_blob.bin");
|
|
nvs_handle handle;
|
|
TEST_ESP_OK( nvs_flash_init_custom("test", 0, 3) );
|
|
TEST_ESP_OK( nvs_open_from_partition("test", "dummyNamespace", NVS_READONLY, &handle));
|
|
uint8_t u8v;
|
|
TEST_ESP_OK( nvs_get_u8(handle, "dummyU8Key", &u8v));
|
|
CHECK(u8v == 127);
|
|
int8_t i8v;
|
|
TEST_ESP_OK( nvs_get_i8(handle, "dummyI8Key", &i8v));
|
|
CHECK(i8v == -128);
|
|
uint16_t u16v;
|
|
TEST_ESP_OK( nvs_get_u16(handle, "dummyU16Key", &u16v));
|
|
CHECK(u16v == 32768);
|
|
uint32_t u32v;
|
|
TEST_ESP_OK( nvs_get_u32(handle, "dummyU32Key", &u32v));
|
|
CHECK(u32v == 4294967295);
|
|
int32_t i32v;
|
|
TEST_ESP_OK( nvs_get_i32(handle, "dummyI32Key", &i32v));
|
|
CHECK(i32v == -2147483648);
|
|
|
|
char buf[64] = {0};
|
|
size_t buflen = 64;
|
|
TEST_ESP_OK( nvs_get_str(handle, "dummyStringKey", buf, &buflen));
|
|
CHECK(strncmp(buf, "0A:0B:0C:0D:0E:0F", buflen) == 0);
|
|
|
|
uint8_t hexdata[] = {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef};
|
|
buflen = 64;
|
|
int j;
|
|
TEST_ESP_OK( nvs_get_blob(handle, "dummyHex2BinKey", buf, &buflen));
|
|
CHECK(memcmp(buf, hexdata, buflen) == 0);
|
|
|
|
uint8_t base64data[] = {'1', '2', '3', 'a', 'b', 'c'};
|
|
TEST_ESP_OK( nvs_get_blob(handle, "dummyBase64Key", buf, &buflen));
|
|
CHECK(memcmp(buf, base64data, buflen) == 0);
|
|
|
|
buflen = 64;
|
|
uint8_t hexfiledata[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
|
|
TEST_ESP_OK( nvs_get_blob(handle, "hexFileKey", buf, &buflen));
|
|
CHECK(memcmp(buf, hexfiledata, buflen) == 0);
|
|
|
|
buflen = 64;
|
|
uint8_t strfiledata[64] = "abcdefghijklmnopqrstuvwxyz\0";
|
|
TEST_ESP_OK( nvs_get_str(handle, "stringFileKey", buf, &buflen));
|
|
CHECK(memcmp(buf, strfiledata, buflen) == 0);
|
|
|
|
char bin_data[5200];
|
|
size_t bin_len = sizeof(bin_data);
|
|
char binfiledata[5200];
|
|
ifstream file;
|
|
file.open("../nvs_partition_generator/testdata/sample_multipage_blob.bin");
|
|
file.read(binfiledata,5200);
|
|
TEST_ESP_OK( nvs_get_blob(handle, "binFileKey", bin_data, &bin_len));
|
|
CHECK(memcmp(bin_data, binfiledata, bin_len) == 0);
|
|
|
|
file.close();
|
|
|
|
}
|
|
#endif
|
|
|
|
TEST_CASE("dump all performance data", "[nvs]")
|
|
{
|
|
std::cout << "====================" << std::endl << "Dumping benchmarks" << std::endl;
|
|
std::cout << s_perf.str() << std::endl;
|
|
std::cout << "====================" << std::endl;
|
|
}
|