Merge branch 'bugfix/nvs_entry_sanity_check_v5.1' into 'release/v5.1'

Bugfix/added nvs entry header sanity checks (v5.1)

See merge request espressif/esp-idf!34295
This commit is contained in:
Martin Vychodil
2024-11-14 22:39:00 +08:00
5 changed files with 272 additions and 167 deletions

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -547,8 +547,8 @@ TEST_CASE("readonly handle fails on writing", "[nvs]")
TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);)
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(),
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
// first, creating namespace...
TEST_ESP_OK(nvs_open("ro_ns", NVS_READWRITE, &handle_1));
@@ -575,14 +575,13 @@ TEST_CASE("nvs api tests", "[nvs]")
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3;
TEMPORARILY_DISABLED(f.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) {
TEMPORARILY_DISABLED(f.emu.erase(i);)
}
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(),
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
TEST_ESP_ERR(nvs_open("namespace1", NVS_READONLY, &handle_1), ESP_ERR_NVS_NOT_FOUND);
@@ -643,11 +642,11 @@ TEST_CASE("deinit partition doesn't affect other partition's open handles", "[nv
TEMPORARILY_DISABLED(f_other.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);)
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(),
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f_other.part(),
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
TEST_ESP_OK(nvs_open_from_partition(OTHER_PARTITION_NAME, "ns", NVS_READWRITE, &handle_1));
@@ -715,8 +714,8 @@ TEST_CASE("nvs iterators tests", "[nvs]")
TEMPORARILY_DISABLED(f.emu.erase(i);)
}
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(),
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
nvs_iterator_t it;
nvs_entry_info_t info;
@@ -748,8 +747,7 @@ TEST_CASE("nvs iterators tests", "[nvs]")
int count = 0;
nvs_iterator_t it = nullptr;
esp_err_t res = nvs_entry_find(part, name, type, &it);
for (count = 0; res == ESP_OK; count++)
{
for (count = 0; res == ESP_OK; count++) {
res = nvs_entry_next(&it);
}
CHECK(res == ESP_ERR_NVS_NOT_FOUND); // after finishing the loop or if no entry was found to begin with,
@@ -858,14 +856,13 @@ TEST_CASE("nvs iterators tests", "[nvs]")
nvs_release_iterator(it);
}
SECTION("Iterating over multiple pages works correctly") {
nvs_handle_t handle_3;
const char *name_3 = "namespace3";
const int entries_created = 250;
TEST_ESP_OK(nvs_open(name_3, NVS_READWRITE, &handle_3));
for (size_t i = 0; i < entries_created; i++) {
for (size_t i = 0; i < entries_created; i++) {
TEST_ESP_OK(nvs_set_u8(handle_3, to_string(i).c_str(), 123));
}
@@ -922,8 +919,8 @@ TEST_CASE("Iterator with not matching type iterates correctly", "[nvs]")
TEMPORARILY_DISABLED(f.emu.erase(i);)
}
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(),
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
// writing string to namespace (a type which spans multiple entries)
TEST_ESP_OK(nvs_open(NAMESPACE, NVS_READWRITE, &my_handle));
@@ -936,8 +933,8 @@ TEST_CASE("Iterator with not matching type iterates correctly", "[nvs]")
// re-init to trigger cleaning up of broken items -> a corrupted string will be erased
nvs_flash_deinit();
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(),
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
CHECK(nvs_entry_find(NVS_DEFAULT_PART_NAME, NAMESPACE, NVS_TYPE_STR, &it) == ESP_OK);
nvs_release_iterator(it);
@@ -955,8 +952,8 @@ TEST_CASE("wifi test", "[nvs]")
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3;
TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);)
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(),
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
nvs_handle_t misc_handle;
TEST_ESP_OK(nvs_open("nvs.net80211", NVS_READWRITE, &misc_handle));
@@ -1123,8 +1120,7 @@ public:
static_assert(nKeys == sizeof(values) / sizeof(values[0]), "");
auto randomRead = [&](size_t index) -> esp_err_t {
switch (types[index])
{
switch (types[index]) {
case nvs::ItemType::I32: {
int32_t val;
auto err = nvs_get_i32(handle, keys[index], &val);
@@ -1204,8 +1200,7 @@ public:
};
auto randomWrite = [&](size_t index) -> esp_err_t {
switch (types[index])
{
switch (types[index]) {
case nvs::ItemType::I32: {
int32_t val = static_cast<int32_t>(gen());
@@ -1323,7 +1318,7 @@ public:
return ESP_OK;
}
esp_err_t handleExternalWriteAtIndex(uint8_t index, const void *value, const size_t len )
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) {
@@ -1354,8 +1349,8 @@ TEST_CASE("monkey test", "[nvs][monkey]")
TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);)
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(),
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
nvs_handle_t handle;
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle));
@@ -1375,8 +1370,8 @@ TEST_CASE("test for memory leaks in open/set", "[leaks]")
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3;
TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);)
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(),
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
for (int i = 0; i < 100000; ++i) {
nvs_handle_t light_handle = 0;
@@ -1405,7 +1400,7 @@ TEST_CASE("read/write failure (TW8406)", "[nvs]")
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);
snprintf(key, sizeof(key), "light%d", i);
ESP_ERROR_CHECK(nvs_set_blob(light_handle, key, data, sizeof(data)));
}
nvs_commit(light_handle);
@@ -1415,7 +1410,7 @@ TEST_CASE("read/write failure (TW8406)", "[nvs]")
REQUIRE(number == get_number);
for (i = 0; i < number; ++i) {
char data[76] = {0};
sprintf(key, "light%d", i);
snprintf(key, sizeof(key), "light%d", i);
ESP_ERROR_CHECK(nvs_get_blob(light_handle, key, data, &data_len));
}
nvs_close(light_handle);
@@ -1429,22 +1424,22 @@ TEST_CASE("nvs_flash_init checks for an empty page", "[nvs]")
const size_t blob_size = nvs::Page::CHUNK_MAX_SIZE;
uint8_t blob[blob_size] = {0};
PartitionEmulationFixture f(0, 8);
TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5) );
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5));
nvs_handle_t handle;
TEST_ESP_OK( nvs_open("test", NVS_READWRITE, &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, "1a", blob, blob_size));
// Fill second page
TEST_ESP_OK( nvs_set_blob(handle, "2a", blob, blob_size) );
TEST_ESP_OK(nvs_set_blob(handle, "2a", blob, blob_size));
// Fill third page
TEST_ESP_OK( nvs_set_blob(handle, "3a", blob, blob_size) );
TEST_ESP_OK( nvs_commit(handle) );
TEST_ESP_OK(nvs_set_blob(handle, "3a", blob, blob_size));
TEST_ESP_OK(nvs_commit(handle));
nvs_close(handle);
TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME));
// first two pages are now full, third one is writable, last two are empty
// init should fail
TEST_ESP_ERR( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3),
ESP_ERR_NVS_NO_FREE_PAGES );
TEST_ESP_ERR(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3),
ESP_ERR_NVS_NO_FREE_PAGES);
// in case this test fails, to not affect other tests
nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME);
@@ -1455,19 +1450,19 @@ TEST_CASE("nvs page selection takes into account free entries also not just eras
const size_t blob_size = nvs::Page::CHUNK_MAX_SIZE / 2;
uint8_t blob[blob_size] = {0};
PartitionEmulationFixture f(0, 3);
TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3) );
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3));
nvs_handle_t handle;
TEST_ESP_OK( nvs_open("test", NVS_READWRITE, &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) );
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) );
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) );
TEST_ESP_OK(nvs_set_blob(handle, "3a", blob, 4));
TEST_ESP_OK(nvs_commit(handle));
nvs_close(handle);
TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name()));
@@ -1672,21 +1667,21 @@ TEST_CASE("Modification of values for Multi-page blobs are supported", "[nvs]")
uint8_t blob4[blob_size] = { 0x33};
size_t read_size = blob_size;
PartitionEmulationFixture f(0, 6);
TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 6) );
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 6));
nvs_handle_t 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));
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) );
TEST_ESP_OK(nvs_commit(handle));
nvs_close(handle);
TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name()));
@@ -1699,14 +1694,14 @@ TEST_CASE("Modification from single page blob to multi-page", "[nvs]")
uint8_t blob_read[blob_size] = {0xff};
size_t read_size = blob_size;
PartitionEmulationFixture f(0, 5);
TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5) );
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5));
nvs_handle_t handle;
TEST_ESP_OK(nvs_open("Test", NVS_READWRITE, &handle) );
TEST_ESP_OK(nvs_open("Test", NVS_READWRITE, &handle));
TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, nvs::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) );
TEST_ESP_OK(nvs_commit(handle));
nvs_close(handle);
TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name()));
@@ -1719,15 +1714,15 @@ TEST_CASE("Modification from multi-page to single page", "[nvs]")
uint8_t blob_read[blob_size] = {0xff};
size_t read_size = blob_size;
PartitionEmulationFixture f(0, 5);
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5) );
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5));
nvs_handle_t handle;
TEST_ESP_OK(nvs_open("Test", NVS_READWRITE, &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, nvs::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, nvs::Page::CHUNK_MAX_SIZE) == 0);
TEST_ESP_OK(nvs_commit(handle) );
TEST_ESP_OK(nvs_commit(handle));
nvs_close(handle);
TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name()));
@@ -1765,7 +1760,6 @@ TEST_CASE("Check that orphaned blobs are erased during init", "[nvs]")
TEST_ESP_OK(storage.writeItem(1, nvs::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, nvs::ItemType::BLOB, "key", blob, sizeof(blob)));
@@ -1785,21 +1779,21 @@ TEST_CASE("Check that orphaned blobs are erased during init", "[nvs]")
TEST_CASE("nvs blob fragmentation test", "[nvs]")
{
PartitionEmulationFixture f(0, 4);
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 4) );
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 4));
const size_t BLOB_SIZE = 3500;
uint8_t *blob = (uint8_t *) malloc(BLOB_SIZE);
CHECK(blob != NULL);
memset(blob, 0xEE, BLOB_SIZE);
const uint32_t magic = 0xff33eaeb;
nvs_handle_t h;
TEST_ESP_OK( nvs_open("blob_tests", NVS_READWRITE, &h) );
TEST_ESP_OK(nvs_open("blob_tests", NVS_READWRITE, &h));
for (int i = 0; i < 128; i++) {
INFO("Iteration " << i << "...\n");
TEST_ESP_OK( nvs_set_u32(h, "magic", magic) );
TEST_ESP_OK( nvs_set_blob(h, "blob", blob, BLOB_SIZE) );
TEST_ESP_OK(nvs_set_u32(h, "magic", magic));
TEST_ESP_OK(nvs_set_blob(h, "blob", blob, BLOB_SIZE));
char seq_buf[16];
sprintf(seq_buf, "seq%d", i);
TEST_ESP_OK( nvs_set_u32(h, seq_buf, i) );
snprintf(seq_buf, sizeof(seq_buf), "seq%d", i);
TEST_ESP_OK(nvs_set_u32(h, seq_buf, i));
}
free(blob);
@@ -1818,12 +1812,12 @@ TEST_CASE("nvs code handles errors properly when partition is near to full", "[n
/* Four pages should fit roughly 12 blobs*/
for (uint8_t count = 1; count <= 12; count++) {
sprintf(nvs_key, "key:%u", count);
snprintf(nvs_key, sizeof(nvs_key), "key:%u", count);
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::BLOB, nvs_key, blob, sizeof(blob)));
}
for (uint8_t count = 13; count <= 20; count++) {
sprintf(nvs_key, "key:%u", count);
snprintf(nvs_key, sizeof(nvs_key), "key:%u", count);
TEST_ESP_ERR(storage.writeItem(1, nvs::ItemType::BLOB, nvs_key, blob, sizeof(blob)), ESP_ERR_NVS_NOT_ENOUGH_SPACE);
}
}
@@ -1864,14 +1858,14 @@ TEST_CASE("monkey test with old-format blob present", "[nvs][monkey]")
TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);)
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(),
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
nvs_handle_t handle;
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle));
RandomTest test;
for ( uint8_t it = 0; it < 10; it++) {
for (uint8_t it = 0; it < 10; it++) {
size_t count = 200;
/* Erase index and chunks for the blob with "singlepage" key */
@@ -1910,8 +1904,8 @@ TEST_CASE("monkey test with old-format blob present", "[nvs][monkey]")
TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name()));
/* Initialize again */
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(),
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle));
/* Perform random things */
@@ -1987,7 +1981,7 @@ TEST_CASE("Recovery from power-off during modification of blob present in old-fo
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3));
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle));
TEST_ESP_OK( nvs_get_blob(handle, "singlepage", buf, &buflen));
TEST_ESP_OK(nvs_get_blob(handle, "singlepage", buf, &buflen));
CHECK(memcmp(buf, hexdata, buflen) == 0);
nvs::Page p2;
@@ -2017,7 +2011,6 @@ TEST_CASE("Recovery from power-off during modification of blob present in old-fo
size_t buflen = sizeof(hexdata);
uint8_t buf[nvs::Page::CHUNK_MAX_SIZE];
/* Power-off when blob was being written on the different page where its old version in old format
* was present*/
nvs::Page p;
@@ -2046,7 +2039,7 @@ TEST_CASE("Recovery from power-off during modification of blob present in old-fo
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3));
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle));
TEST_ESP_OK( nvs_get_blob(handle, "singlepage", buf, &buflen));
TEST_ESP_OK(nvs_get_blob(handle, "singlepage", buf, &buflen));
CHECK(memcmp(buf, hexdata, buflen) == 0);
nvs::Page p3;

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -9,16 +9,15 @@
#include <cstring>
#include "nvs_internal.h"
namespace nvs
{
namespace nvs {
Page::Page() : mPartition(nullptr) { }
uint32_t Page::Header::calculateCrc32()
{
return esp_rom_crc32_le(0xffffffff,
reinterpret_cast<uint8_t*>(this) + offsetof(Header, mSeqNumber),
offsetof(Header, mCrc32) - offsetof(Header, mSeqNumber));
reinterpret_cast<uint8_t*>(this) + offsetof(Header, mSeqNumber),
offsetof(Header, mCrc32) - offsetof(Header, mSeqNumber));
}
esp_err_t Page::load(Partition *partition, uint32_t sectorNumber)
@@ -45,7 +44,9 @@ esp_err_t Page::load(Partition *partition, uint32_t sectorNumber)
const int BLOCK_SIZE = 128;
uint32_t* block = new (std::nothrow) uint32_t[BLOCK_SIZE];
if (!block) return ESP_ERR_NO_MEM;
if (!block) {
return ESP_ERR_NO_MEM;
}
for (uint32_t i = 0; i < SPI_FLASH_SEC_SIZE; i += 4 * BLOCK_SIZE) {
rc = mPartition->read_raw(mBaseAddress + i, block, 4 * BLOCK_SIZE);
@@ -66,7 +67,7 @@ esp_err_t Page::load(Partition *partition, uint32_t sectorNumber)
} else {
mState = header.mState;
mSeqNumber = header.mSeqNumber;
if(header.mVersion < NVS_VERSION) {
if (header.mVersion < NVS_VERSION) {
return ESP_ERR_NVS_NEW_VERSION_FOUND;
} else {
mVersion = header.mVersion;
@@ -91,7 +92,7 @@ esp_err_t Page::load(Partition *partition, uint32_t sectorNumber)
return ESP_OK;
}
esp_err_t Page::writeEntry(const Item& item)
esp_err_t Page::writeEntry(const Item &item)
{
uint32_t phyAddr;
esp_err_t err = getEntryAddress(mNextFreeEntry, &phyAddr);
@@ -100,7 +101,6 @@ esp_err_t Page::writeEntry(const Item& item)
}
err = mPartition->write(phyAddr, &item, sizeof(item));
if (err != ESP_OK) {
mState = PageState::INVALID;
return err;
@@ -189,7 +189,7 @@ esp_err_t Page::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, c
// primitive types should fit into one entry
NVS_ASSERT_OR_RETURN(totalSize == ENTRY_SIZE ||
isVariableLengthType(datatype), ESP_ERR_NVS_VALUE_TOO_LONG);
isVariableLengthType(datatype), ESP_ERR_NVS_VALUE_TOO_LONG);
if (mNextFreeEntry == INVALID_ENTRY || mNextFreeEntry + entriesCount > ENTRY_COUNT) {
// page will not fit this amount of data
@@ -282,12 +282,12 @@ esp_err_t Page::readItem(uint8_t nsIndex, ItemType datatype, const char* key, vo
return rc;
}
size_t willCopy = ENTRY_SIZE;
willCopy = (left < willCopy)?left:willCopy;
willCopy = (left < willCopy) ? left : willCopy;
memcpy(dst, ditem.rawData, willCopy);
left -= willCopy;
dst += willCopy;
}
if (Item::calculateCrc32(reinterpret_cast<uint8_t*>(data), item.varLength.dataSize) != item.varLength.dataCrc32) {
if (Item::calculateCrc32(reinterpret_cast<uint8_t * >(data), item.varLength.dataSize) != item.varLength.dataCrc32) {
rc = eraseEntryAndSpan(index);
if (rc != ESP_OK) {
return rc;
@@ -335,14 +335,14 @@ esp_err_t Page::cmpItem(uint8_t nsIndex, ItemType datatype, const char* key, con
return rc;
}
size_t willCopy = ENTRY_SIZE;
willCopy = (left < willCopy)?left:willCopy;
willCopy = (left < willCopy) ? left : willCopy;
if (memcmp(dst, ditem.rawData, willCopy)) {
return ESP_ERR_NVS_CONTENT_DIFFERS;
}
left -= willCopy;
dst += willCopy;
}
if (Item::calculateCrc32(reinterpret_cast<const uint8_t*>(data), item.varLength.dataSize) != item.varLength.dataCrc32) {
if (Item::calculateCrc32(reinterpret_cast<const uint8_t * >(data), item.varLength.dataSize) != item.varLength.dataCrc32) {
return ESP_ERR_NVS_NOT_FOUND;
}
@@ -385,7 +385,7 @@ esp_err_t Page::eraseEntryAndSpan(size_t index)
if (rc != ESP_OK) {
return rc;
}
if (item.calculateCrc32() != item.crc32) {
if (!item.checkHeaderConsistency(index)) {
mHashList.erase(index);
rc = alterEntryState(index, EntryState::ERASED);
--mUsedEntryCount;
@@ -459,7 +459,7 @@ esp_err_t Page::updateFirstUsedEntry(size_t index, size_t span)
return ESP_OK;
}
esp_err_t Page::copyItems(Page& other)
esp_err_t Page::copyItems(Page &other)
{
if (mFirstUsedEntry == INVALID_ENTRY) {
return ESP_ERR_NVS_NOT_FOUND;
@@ -507,7 +507,10 @@ esp_err_t Page::copyItems(Page& other)
NVS_ASSERT_OR_RETURN(end <= ENTRY_COUNT, ESP_FAIL);
for (size_t i = readEntryIndex + 1; i < end; ++i) {
readEntry(i, entry);
err = readEntry(i, entry);
if (err != ESP_OK) {
return err;
}
err = other.writeEntry(entry);
if (err != ESP_OK) {
return err;
@@ -526,7 +529,7 @@ esp_err_t Page::mLoadEntryTable()
mState == PageState::FULL ||
mState == PageState::FREEING) {
auto rc = mPartition->read_raw(mBaseAddress + ENTRY_TABLE_OFFSET, mEntryTable.data(),
mEntryTable.byteSize());
mEntryTable.byteSize());
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
@@ -598,8 +601,7 @@ esp_err_t Page::mLoadEntryTable()
--mUsedEntryCount;
}
++mErasedEntryCount;
}
else {
} else {
break;
}
}
@@ -641,7 +643,7 @@ esp_err_t Page::mLoadEntryTable()
return err;
}
if (item.crc32 != item.calculateCrc32()) {
if (!item.checkHeaderConsistency(i)) {
err = eraseEntryAndSpan(i);
if (err != ESP_OK) {
mState = PageState::INVALID;
@@ -721,7 +723,7 @@ esp_err_t Page::mLoadEntryTable()
return err;
}
if (item.crc32 != item.calculateCrc32()) {
if (!item.checkHeaderConsistency(i)) {
err = eraseEntryAndSpan(i);
if (err != ESP_OK) {
mState = PageState::INVALID;
@@ -761,7 +763,6 @@ esp_err_t Page::mLoadEntryTable()
return ESP_OK;
}
esp_err_t Page::initialize()
{
NVS_ASSERT_OR_RETURN(mState == PageState::UNINITIALIZED, ESP_FAIL);
@@ -793,7 +794,7 @@ esp_err_t Page::alterEntryState(size_t index, EntryState state)
size_t wordToWrite = mEntryTable.getWordIndex(index);
uint32_t word = mEntryTable.data()[wordToWrite];
err = mPartition->write_raw(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast<uint32_t>(wordToWrite) * 4,
&word, sizeof(word));
&word, sizeof(word));
if (err != ESP_OK) {
mState = PageState::INVALID;
return err;
@@ -809,7 +810,7 @@ esp_err_t Page::alterEntryRangeState(size_t begin, size_t end, EntryState state)
esp_err_t err;
for (ptrdiff_t i = end - 1; i >= static_cast<ptrdiff_t>(begin); --i) {
err = mEntryTable.set(i, state);
if (err != ESP_OK){
if (err != ESP_OK) {
return err;
}
size_t nextWordIndex;
@@ -821,7 +822,7 @@ esp_err_t Page::alterEntryRangeState(size_t begin, size_t end, EntryState state)
if (nextWordIndex != wordIndex) {
uint32_t word = mEntryTable.data()[wordIndex];
auto rc = mPartition->write_raw(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast<uint32_t>(wordIndex) * 4,
&word, 4);
&word, 4);
if (rc != ESP_OK) {
return rc;
}
@@ -843,7 +844,7 @@ esp_err_t Page::alterPageState(PageState state)
return ESP_OK;
}
esp_err_t Page::readEntry(size_t index, Item& dst) const
esp_err_t Page::readEntry(size_t index, Item &dst) const
{
uint32_t phyAddr;
esp_err_t rc = getEntryAddress(index, &phyAddr);
@@ -857,7 +858,7 @@ esp_err_t Page::readEntry(size_t index, Item& dst) const
return ESP_OK;
}
esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, size_t &itemIndex, Item& item, uint8_t chunkIdx, VerOffset chunkStart)
esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, size_t &itemIndex, Item &item, uint8_t chunkIdx, VerOffset chunkStart)
{
if (mState == PageState::CORRUPT || mState == PageState::INVALID || mState == PageState::UNINITIALIZED) {
return ESP_ERR_NVS_NOT_FOUND;
@@ -909,8 +910,7 @@ esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, si
return rc;
}
auto crc32 = item.calculateCrc32();
if (item.crc32 != crc32) {
if (!item.checkHeaderConsistency(i)) {
rc = eraseEntryAndSpan(i);
if (rc != ESP_OK) {
mState = PageState::INVALID;
@@ -974,7 +974,6 @@ esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, si
continue;
}
if (datatype != ItemType::ANY && item.datatype != datatype) {
if (key == nullptr && nsIndex == NS_ANY && chunkIdx == CHUNK_ANY) {
continue; // continue for bruteforce search on blob indices.
@@ -991,7 +990,7 @@ esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, si
return ESP_ERR_NVS_NOT_FOUND;
}
esp_err_t Page::getSeqNumber(uint32_t& seqNumber) const
esp_err_t Page::getSeqNumber(uint32_t &seqNumber) const
{
if (mState != PageState::UNINITIALIZED && mState != PageState::INVALID && mState != PageState::CORRUPT) {
seqNumber = mSeqNumber;
@@ -1000,7 +999,6 @@ esp_err_t Page::getSeqNumber(uint32_t& seqNumber) const
return ESP_ERR_NVS_NOT_INITIALIZED;
}
esp_err_t Page::setSeqNumber(uint32_t seqNumber)
{
if (mState != PageState::UNINITIALIZED) {
@@ -1058,40 +1056,41 @@ size_t Page::getVarDataTailroom() const
} else if (mState == PageState::FULL) {
return 0;
}
/* Skip one entry for blob data item precessing the data */
return ((mNextFreeEntry < (ENTRY_COUNT-1)) ? ((ENTRY_COUNT - mNextFreeEntry - 1) * ENTRY_SIZE): 0);
/* Skip one entry for blob data item processing the data */
return ((mNextFreeEntry < (ENTRY_COUNT - 1)) ? ((ENTRY_COUNT - mNextFreeEntry - 1) * ENTRY_SIZE) : 0);
}
const char* Page::pageStateToName(PageState ps)
{
switch (ps) {
case PageState::CORRUPT:
return "CORRUPT";
case PageState::CORRUPT:
return "CORRUPT";
case PageState::ACTIVE:
return "ACTIVE";
case PageState::ACTIVE:
return "ACTIVE";
case PageState::FREEING:
return "FREEING";
case PageState::FREEING:
return "FREEING";
case PageState::FULL:
return "FULL";
case PageState::FULL:
return "FULL";
case PageState::INVALID:
return "INVALID";
case PageState::INVALID:
return "INVALID";
case PageState::UNINITIALIZED:
return "UNINITIALIZED";
case PageState::UNINITIALIZED:
return "UNINITIALIZED";
default:
assert(0 && "invalid state value");
return "";
default:
assert(0 && "invalid state value");
return "";
}
}
void Page::debugDump() const
{
printf("state=%x (%s) addr=%x seq=%d\nfirstUsed=%d nextFree=%d used=%d erased=%d\n", (uint32_t) mState, pageStateToName(mState), mBaseAddress, mSeqNumber, static_cast<int>(mFirstUsedEntry), static_cast<int>(mNextFreeEntry), mUsedEntryCount, mErasedEntryCount);
printf("state=%x (%s) addr=%x seq=%d\nfirstUsed=%d nextFree=%d used=%d erased=%d\n",
(uint32_t) mState, pageStateToName(mState), mBaseAddress, mSeqNumber, static_cast<int>(mFirstUsedEntry), static_cast<int>(mNextFreeEntry), mUsedEntryCount, mErasedEntryCount);
size_t skip = 0;
for (size_t i = 0; i < ENTRY_COUNT; ++i) {
printf("%3d: ", static_cast<int>(i));
@@ -1108,7 +1107,9 @@ void Page::debugDump() const
Item item;
readEntry(i, item);
if (skip == 0) {
printf("W ns=%2u type=%2u span=%3u key=\"%s\" chunkIdx=%d len=%d\n", item.nsIndex, static_cast<unsigned>(item.datatype), item.span, item.key, item.chunkIndex, (item.span != 1)?((int)item.varLength.dataSize):-1);
printf("W ns=%2u type=%2u span=%3u key=\"%s\" chunkIdx=%d len=%d\n",
item.nsIndex, static_cast<unsigned>(item.datatype), item.span, item.key, item.chunkIndex, (item.span != 1)?((int)item.varLength.dataSize):-1);
if (item.span > 0 && item.span <= ENTRY_COUNT - i) {
skip = item.span - 1;
} else {
@@ -1129,24 +1130,24 @@ esp_err_t Page::calcEntries(nvs_stats_t &nvsStats)
nvsStats.total_entries += ENTRY_COUNT;
switch (mState) {
case PageState::UNINITIALIZED:
case PageState::CORRUPT:
nvsStats.free_entries += ENTRY_COUNT;
break;
case PageState::UNINITIALIZED:
case PageState::CORRUPT:
nvsStats.free_entries += ENTRY_COUNT;
break;
case PageState::FULL:
case PageState::ACTIVE:
nvsStats.used_entries += mUsedEntryCount;
nvsStats.free_entries += ENTRY_COUNT - mUsedEntryCount; // it's equivalent free + erase entries.
break;
case PageState::FULL:
case PageState::ACTIVE:
nvsStats.used_entries += mUsedEntryCount;
nvsStats.free_entries += ENTRY_COUNT - mUsedEntryCount; // it's equivalent free + erase entries.
break;
case PageState::INVALID:
return ESP_ERR_INVALID_STATE;
break;
case PageState::INVALID:
return ESP_ERR_INVALID_STATE;
break;
default:
assert(false && "Unhandled state");
break;
default:
assert(false && "Unhandled state");
break;
}
return ESP_OK;
}

View File

@@ -1,28 +1,22 @@
// 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.
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "nvs_types.hpp"
#include "nvs_page.hpp"
#include "esp_log.h"
#include "esp_rom_crc.h"
namespace nvs
{
#define TAG "nvs"
namespace nvs {
uint32_t Item::calculateCrc32() const
{
uint32_t result = 0xffffffff;
const uint8_t* p = reinterpret_cast<const uint8_t*>(this);
result = esp_rom_crc32_le(result, p + offsetof(Item, nsIndex),
offsetof(Item, crc32) - offsetof(Item, nsIndex));
offsetof(Item, crc32) - offsetof(Item, nsIndex));
result = esp_rom_crc32_le(result, p + offsetof(Item, key), sizeof(key));
result = esp_rom_crc32_le(result, p + offsetof(Item, data), sizeof(data));
return result;
@@ -33,7 +27,7 @@ uint32_t Item::calculateCrc32WithoutValue() const
uint32_t result = 0xffffffff;
const uint8_t* p = reinterpret_cast<const uint8_t*>(this);
result = esp_rom_crc32_le(result, p + offsetof(Item, nsIndex),
offsetof(Item, datatype) - offsetof(Item, nsIndex));
offsetof(Item, datatype) - offsetof(Item, nsIndex));
result = esp_rom_crc32_le(result, p + offsetof(Item, key), sizeof(key));
result = esp_rom_crc32_le(result, p + offsetof(Item, chunkIndex), sizeof(chunkIndex));
return result;
@@ -46,4 +40,114 @@ uint32_t Item::calculateCrc32(const uint8_t* data, size_t size)
return result;
}
bool Item::checkHeaderConsistency(const uint8_t entryIndex) const
{
// calculate and check the crc32
if (crc32 != calculateCrc32()) {
ESP_LOGD(TAG, "CRC32 mismatch for entry %d", entryIndex);
return false;
}
// validate the datatype and check the rest of the header fields
switch (datatype) {
// Entries occupying just one entry
case ItemType::U8:
case ItemType::I8:
case ItemType::U16:
case ItemType::I16:
case ItemType::U32:
case ItemType::I32:
case ItemType::U64:
case ItemType::I64: {
if (span != 1) {
ESP_LOGD(TAG, "Invalid span %u for datatype %#04x", (unsigned int)span, (unsigned int)datatype);
return false;
}
break;
}
// Special case for BLOB_IDX
case ItemType::BLOB_IDX: {
// span must be 1
if (span != 1) {
ESP_LOGD(TAG, "Invalid span %u for BLOB_IDX", (unsigned int)span);
return false;
}
// chunkIndex must be CHUNK_ANY
if (chunkIndex != CHUNK_ANY) {
ESP_LOGD(TAG, "Invalid chunk index %u for BLOB_IDX", (unsigned int)chunkIndex);
return false;
}
// check maximum data length
// the maximal data length is determined by:
// maximum number of chunks. Chunks are stored in uin8_t, but are logically divided into two "VerOffset" ranges of values (0 based and 128 based)
// maximum theoretical number of entries in the chunk (Page::ENTRY_COUNT - 1) and the number of bytes entry can store (Page::ENTRY_SIZE)
const uint32_t maxDataSize = (uint32_t)((UINT8_MAX / 2) * (Page::ENTRY_COUNT - 1) * Page::ENTRY_SIZE);
if (blobIndex.dataSize > maxDataSize) {
ESP_LOGD(TAG, "Data size %u bytes exceeds maximum possible size %u bytes for BLOB_IDX", (unsigned int)blobIndex.dataSize, (unsigned int)maxDataSize);
return false;
}
break;
}
// Entries with variable length data
case ItemType::SZ:
case ItemType::BLOB:
case ItemType::BLOB_DATA: {
uint16_t maxAvailableVDataSize;
uint8_t maxAvailablePageSpan;
uint8_t spanCalcFromLen;
// for BLOB_DATA, chunkIndex must NOT be CHUNK_ANY as this value is used to search ALL chunks in findItem
if (datatype == ItemType::BLOB_DATA) {
// chunkIndex must not be CHUNK_ANY
if (chunkIndex == CHUNK_ANY) {
ESP_LOGD(TAG, "Invalid chunk index %u for BLOB_DATA", (unsigned int)chunkIndex);
return false;
}
}
// variable length and span checks
// based on the entryIndex determine the maximum variable length capacity in bytes to the end of the page
maxAvailableVDataSize = ((Page::ENTRY_COUNT - entryIndex) - 1) * Page::ENTRY_SIZE;
// check if the variable data length is not exceeding the maximum capacity available till the end of the page
if (varLength.dataSize > maxAvailableVDataSize) {
ESP_LOGD(TAG, "Variable data length %u bytes exceeds page boundary. Maximum calculated from the current entry position within page is %u bytes for datatype %#04x ", (unsigned int)varLength.dataSize, (unsigned int)maxAvailableVDataSize, (unsigned int)datatype);
return false;
}
// based on the entryIndex determine the maximum possible span up to the end of the page
maxAvailablePageSpan = Page::ENTRY_COUNT - entryIndex;
// this check ensures no data is read beyond the end of the page
if (span > maxAvailablePageSpan) {
ESP_LOGD(TAG, "Span %u exceeds page boundary. Maximum calculated from the current entry position within page is %u for datatype %#04x ", (unsigned int)span, (unsigned int)maxAvailablePageSpan, (unsigned int)datatype);
return false;
}
// here we have both span and varLength.dataSize within the page boundary. Check if these values are consistent
spanCalcFromLen = (uint8_t)(((size_t) varLength.dataSize + Page::ENTRY_SIZE - 1) / Page::ENTRY_SIZE);
spanCalcFromLen ++; // add overhead entry
// this check ensures that the span is equal to the number of entries required to store the data plus the overhead entry
if (span != spanCalcFromLen) {
ESP_LOGD(TAG, "Span %i does not match span %u calculated from variable data length %u bytes for datatype %#04x", (unsigned int)span, (unsigned int)spanCalcFromLen, (unsigned int)varLength.dataSize, (unsigned int)datatype);
return false;
}
break;
}
// Invalid datatype
default: {
ESP_LOGD(TAG, "Invalid datatype %#04x", (unsigned int)datatype);
return false;
}
}
return true;
}
} // namespace nvs

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -108,6 +108,14 @@ public:
dst = *reinterpret_cast<T*>(data);
return ESP_OK;
}
// Returns true if item's header:
// crc32 matches the calculated crc32
// and datatype is one of the supported types
// and span is within the allowed range for the datatype and below the maximum calculated from the entryIndex
//
// Parameter entryIndex is used to calculate the maximum span for the given entry
bool checkHeaderConsistency(const uint8_t entryIndex) const;
};
} // namespace nvs

View File

@@ -719,7 +719,6 @@ components/nvs_flash/src/nvs_partition.cpp
components/nvs_flash/src/nvs_partition_lookup.cpp
components/nvs_flash/src/nvs_partition_lookup.hpp
components/nvs_flash/src/nvs_test_api.h
components/nvs_flash/src/nvs_types.cpp
components/nvs_flash/src/partition.hpp
components/nvs_flash/test/test_nvs.c
components/nvs_flash/test_nvs_host/esp_error_check_stub.cpp