Merge branch 'feature/nvs_flash_purge_erased_v6.0' into 'release/v6.0'

nvs_flash: Added purging of erased items at namespace (handle) level. (v6.0)

See merge request espressif/esp-idf!46909
This commit is contained in:
Martin Vychodil
2026-03-23 23:24:44 +08:00
20 changed files with 757 additions and 129 deletions
@@ -15,6 +15,7 @@
#include <unistd.h> // for close(), read(), write(), lseek()
#include "nvs_constants.h" // for NVS_CONST_PAGE_SIZE
#include "esp_log.h" // for ESP_LOGE
#include "nvs.h" // for ESP_ERR_NVS_NOT_FOUND
#define TAG "NVSPartitionTestHelper"
@@ -227,6 +228,48 @@ esp_err_t NVSPartitionTestHelper::load_from_file(const char *part_name, const ch
return ret;
}
esp_err_t NVSPartitionTestHelper::check_marker(const char *part_name, const uint8_t *marker, size_t marker_len)
{
if (marker == nullptr || marker_len == 0) {
return ESP_ERR_INVALID_ARG;
}
// Find the partition by name
const esp_partition_t *part = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_DATA_NVS,
part_name);
if (part == nullptr) {
return ESP_ERR_NOT_FOUND;
}
// Allocate buffer for reading partition data
uint8_t *read_buf = (uint8_t*)malloc(part->size);
if (read_buf == nullptr) {
return ESP_ERR_NO_MEM;
}
// Read the entire partition
esp_err_t ret = esp_partition_read(part, 0, read_buf, part->size);
if (ret != ESP_OK) {
free(read_buf);
return ret;
}
// Search for the marker in the partition data
esp_err_t result = ESP_ERR_NVS_NOT_FOUND;
for (size_t i = 0; i <= part->size - marker_len; i++) {
if (memcmp(read_buf + i, marker, marker_len) == 0) {
result = ESP_OK;
break;
}
}
free(read_buf);
return result;
}
void NVSPartitionTestHelper::clear_stats(void)
{
return esp_partition_clear_stats();
@@ -527,6 +570,54 @@ esp_err_t NVSPartitionTestHelper::load_from_file(const char *part_name, const ch
return ret;
}
esp_err_t NVSPartitionTestHelper::check_marker(const char *part_name, const uint8_t *marker, size_t marker_len)
{
if (marker == nullptr || marker_len == 0) {
return ESP_ERR_INVALID_ARG;
}
// Get the BDL handle for the partition
esp_blockdev_handle_t bdl_handle;
esp_err_t ret = esp_partition_get_blockdev(
ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_DATA_NVS,
part_name,
&bdl_handle);
if (ret != ESP_OK) {
return ret;
}
// Allocate buffer for reading partition data
size_t read_size = bdl_handle->geometry.disk_size;
uint8_t *read_buf = (uint8_t*)malloc(read_size);
if (read_buf == nullptr) {
return ESP_ERR_NO_MEM;
}
do {
// Read the entire partition
ret = bdl_handle->ops->read(bdl_handle, read_buf, read_size, 0, read_size);
if (ret != ESP_OK) {
break;
}
// Search for the marker in the partition data
ret = ESP_ERR_NVS_NOT_FOUND;
for (size_t i = 0; i <= bdl_handle->geometry.disk_size - marker_len; i++) {
if (memcmp(read_buf + i, marker, marker_len) == 0) {
ret = ESP_OK;
break;
}
}
} while (false);
free(read_buf);
return ret;
}
void NVSPartitionTestHelper::clear_stats(void)
{
// TODO: Once BDL implementation supports statistics, we will need to re-implement this function
@@ -56,6 +56,11 @@ public:
// Load the partition from a file
static esp_err_t load_from_file(const char *part_name, const char *file_name);
// Check if a specific marker (byte pattern) exists in the partition
// Returns ESP_OK if marker is found, ESP_ERR_NOT_FOUND if marker not found,
// or other error codes for technical failures
static esp_err_t check_marker(const char *part_name, const uint8_t *marker, size_t marker_len);
// Set of functions to access partition statistics
// At the moment these functions are global, it means that they do not distinguish between
// different partitions
@@ -32,6 +32,8 @@ using namespace std;
#define WD_PREFIX "./components/nvs_flash/host_test/nvs_host_test/" // path from ci cwd to the location of host test
#define TEST_DEFAULT_PURGE_AFTER_ERASE true // erase with purge after erase
#if defined(SEGGER_H) && defined(GLOBAL_H)
NVS_GUARD_SYSVIEW_MACRO_EXPANSION_PUSH();
#undef U8
@@ -155,6 +157,7 @@ TEST_CASE("Page when writing and erasing, used/erased counts are updated correct
// The remaining, not deleted entry is the namespace entry.
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Page page;
TEST_ESP_OK(page.load(&h, 0));
@@ -165,7 +168,7 @@ TEST_CASE("Page when writing and erasing, used/erased counts are updated correct
CHECK(page.getUsedEntryCount() == 1);
TEST_ESP_OK(page.writeItem(2, "foo1", foo1));
CHECK(page.getUsedEntryCount() == 2);
TEST_ESP_OK(page.eraseItem<uint32_t>(2, "foo1"));
TEST_ESP_OK(page.eraseItem<uint32_t>(2, "foo1", purgeAfterErase));
CHECK(page.getUsedEntryCount() == 1);
CHECK(page.getErasedEntryCount() == 1);
for (size_t i = 0; i < nvs::Page::ENTRY_COUNT - 2; ++i) {
@@ -178,7 +181,7 @@ TEST_CASE("Page when writing and erasing, used/erased counts are updated correct
for (size_t i = 0; i < nvs::Page::ENTRY_COUNT - 2; ++i) {
char name[16];
snprintf(name, sizeof(name), "i%ld", (long int)i);
TEST_ESP_OK(page.eraseItem(1, nvs::itemTypeOf<size_t>(), name));
TEST_ESP_OK(page.eraseItem(1, nvs::itemTypeOf<size_t>(), name, purgeAfterErase));
}
CHECK(page.getUsedEntryCount() == 1);
CHECK(page.getErasedEntryCount() == nvs::Page::ENTRY_COUNT - 1);
@@ -447,13 +450,14 @@ TEST_CASE("storage doesn't add duplicates within one page", "[nvs]")
// - overwriting the same item sets the erased entry count to 1
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
TEST_ESP_OK(storage.init(0, h.get_sectors()));
int bar = 0;
TEST_ESP_OK(storage.writeItem(1, "bar", ++bar));
TEST_ESP_OK(storage.writeItem(1, "bar", ++bar));
TEST_ESP_OK(storage.writeItem(1, "bar", ++bar, purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "bar", ++bar, purgeAfterErase));
nvs::Page page;
@@ -468,6 +472,7 @@ TEST_CASE("can write one item a thousand times", "[nvs]")
// TC verifies that Storage.writeItem can write one item a thousand times.
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
const size_t NO_WRITES = 1000;
nvs::Storage storage(&h);
@@ -477,7 +482,7 @@ TEST_CASE("can write one item a thousand times", "[nvs]")
NVSPartitionTestHelper::clear_stats(); // Clear statistics before writing
for (size_t i = 0; i < NO_WRITES; ++i) {
TEST_ESP_OK(storage.writeItem(1, "i", static_cast<int>(i)));
TEST_ESP_OK(storage.writeItem(1, "i", static_cast<int>(i), purgeAfterErase));
}
s_perf << "Time to write one item a " << NO_WRITES << " times: " << NVSPartitionTestHelper::get_total_time() << " us (" << NVSPartitionTestHelper::get_erase_ops() << " " << NVSPartitionTestHelper::get_write_ops() << " " << NVSPartitionTestHelper::get_read_ops() << " " << NVSPartitionTestHelper::get_write_bytes() << " " << NVSPartitionTestHelper::get_read_bytes() << ")" << std::endl;
}
@@ -493,17 +498,17 @@ TEST_CASE("storage doesn't add duplicates within multiple pages", "[nvs]")
// - reading the item "bar" from the second page works
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
TEST_ESP_OK(storage.init(0, h.get_sectors()));
int bar = 0;
TEST_ESP_OK(storage.writeItem(1, "bar", ++bar));
TEST_ESP_OK(storage.writeItem(1, "bar", ++bar, purgeAfterErase));
for (size_t i = 0; i < nvs::Page::ENTRY_COUNT; ++i) {
TEST_ESP_OK(storage.writeItem(1, "foo", static_cast<int>(++bar)));
TEST_ESP_OK(storage.writeItem(1, "foo", static_cast<int>(++bar), purgeAfterErase));
}
TEST_ESP_OK(storage.writeItem(1, "bar", ++bar));
TEST_ESP_OK(storage.writeItem(1, "bar", ++bar, purgeAfterErase));
nvs::Page page;
TEST_ESP_OK(page.load(&h, 0)); // first page attempt
CHECK(page.findItem(1, nvs::itemTypeOf<int>(), "bar") == ESP_ERR_NVS_NOT_FOUND);
@@ -517,18 +522,19 @@ TEST_CASE("storage can find items on second page if first is not fully written a
// The first page is occupied by the item "1" and "2" which occupy enough of the first page in order to not fit the third item "3".
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
TEST_ESP_OK(storage.init(0, h.get_sectors()));
uint8_t bigdata[(nvs::Page::CHUNK_MAX_SIZE - nvs::Page::ENTRY_SIZE) / 2] = {0};
// write one big chunk of data
ESP_ERROR_CHECK(storage.writeItem(1, nvs::ItemType::BLOB, "1", bigdata, sizeof(bigdata)));
ESP_ERROR_CHECK(storage.writeItem(1, nvs::ItemType::BLOB, "1", bigdata, sizeof(bigdata), purgeAfterErase));
// write another big chunk of data
ESP_ERROR_CHECK(storage.writeItem(1, nvs::ItemType::BLOB, "2", bigdata, sizeof(bigdata)));
ESP_ERROR_CHECK(storage.writeItem(1, nvs::ItemType::BLOB, "2", bigdata, sizeof(bigdata), purgeAfterErase));
// write third one; it will not fit into the first page
ESP_ERROR_CHECK(storage.writeItem(1, nvs::ItemType::BLOB, "3", bigdata, sizeof(bigdata)));
ESP_ERROR_CHECK(storage.writeItem(1, nvs::ItemType::BLOB, "3", bigdata, sizeof(bigdata), purgeAfterErase));
size_t size;
ESP_ERROR_CHECK(storage.getItemDataSize(1, nvs::ItemType::BLOB, "1", size));
@@ -543,6 +549,7 @@ TEST_CASE("can write and read variable length data lots of times", "[nvs]")
// of variable length data interleaved with fixed length data.
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
const size_t NO_WRITES = 1000;
nvs::Storage storage(&h);
@@ -556,8 +563,8 @@ TEST_CASE("can write and read variable length data lots of times", "[nvs]")
for (size_t i = 0; i < NO_WRITES; ++i) {
CAPTURE(i);
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "foobaar", str, len + 1));
TEST_ESP_OK(storage.writeItem(1, "foo", static_cast<uint32_t>(i)));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "foobaar", str, len + 1, purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "foo", static_cast<uint32_t>(i), purgeAfterErase));
uint32_t value;
TEST_ESP_OK(storage.readItem(1, "foo", value));
@@ -577,6 +584,7 @@ TEST_CASE("can get length of variable length data", "[nvs]")
// - getting the size of a string and blob works correctly and independently between namespaces
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
TEST_ESP_OK(NVSPartitionTestHelper::randomize_partition(TEST_DEFAULT_PARTITION_NAME, 200));
@@ -586,12 +594,12 @@ TEST_CASE("can get length of variable length data", "[nvs]")
const char str[] = "foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234";
size_t len = strlen(str);
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "foobaar", str, len + 1)); // namespace 1
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "foobaar", str, len + 1, purgeAfterErase)); // namespace 1
size_t dataSize;
TEST_ESP_OK(storage.getItemDataSize(1, nvs::ItemType::SZ, "foobaar", dataSize)); // namespace 1
CHECK(dataSize == len + 1);
TEST_ESP_OK(storage.writeItem(2, nvs::ItemType::BLOB, "foobaar", str, len)); // namespace 2
TEST_ESP_OK(storage.writeItem(2, nvs::ItemType::BLOB, "foobaar", str, len, purgeAfterErase)); // namespace 2
TEST_ESP_OK(storage.getItemDataSize(2, nvs::ItemType::BLOB, "foobaar", dataSize)); // namespace 2
CHECK(dataSize == len);
}
@@ -626,6 +634,7 @@ TEST_CASE("storage may become full", "[nvs]")
// - writing an item when the storage is full returns ESP_ERR_NVS_NOT_ENOUGH_SPACE
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
@@ -638,9 +647,9 @@ TEST_CASE("storage may become full", "[nvs]")
for (size_t i = 0; i < max_items; ++i) {
char name[nvs::Item::MAX_KEY_LENGTH + 1];
snprintf(name, sizeof(name), "key%05d", static_cast<int>(i));
TEST_ESP_OK(storage.writeItem(1, name, static_cast<int>(i)));
TEST_ESP_OK(storage.writeItem(1, name, static_cast<int>(i), purgeAfterErase));
}
REQUIRE(storage.writeItem(1, "foo", 10) == ESP_ERR_NVS_NOT_ENOUGH_SPACE);
REQUIRE(storage.writeItem(1, "foo", 10, purgeAfterErase) == ESP_ERR_NVS_NOT_ENOUGH_SPACE);
}
TEST_CASE("can reuse the space previously occupied by the overwritten item", "[nvs]")
@@ -651,6 +660,7 @@ TEST_CASE("can reuse the space previously occupied by the overwritten item", "[n
// marked as erased and thus space occupied by some of them will be reclaimed and page will be erased.
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
TEST_ESP_OK(storage.init(0, h.get_sectors()));
@@ -662,7 +672,7 @@ TEST_CASE("can reuse the space previously occupied by the overwritten item", "[n
max_usable_items += 1;
for (size_t i = 0; i < max_usable_items; ++i) {
TEST_ESP_OK(storage.writeItem(1, "foo", 42U));
TEST_ESP_OK(storage.writeItem(1, "foo", 42U, purgeAfterErase));
}
}
@@ -673,6 +683,7 @@ TEST_CASE("erase operations are distributed among sectors", "[nvs]")
// on the same item, and finally checks that erase counts are distributed among the remaining sectors.
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
// Test parameters
const size_t static_pages = 2; // Number of NVS pages to fill with stable items
@@ -694,14 +705,14 @@ TEST_CASE("erase operations are distributed among sectors", "[nvs]")
for (size_t i = 0; i < static_pages * nvs::Page::ENTRY_COUNT; ++i) {
char name[nvs::Item::MAX_KEY_LENGTH];
snprintf(name, sizeof(name), "static%d", (int) i);
TEST_ESP_OK(storage.writeItem(1, name, i));
TEST_ESP_OK(storage.writeItem(1, name, i, purgeAfterErase));
}
// Calculate how many write operations we need to perform to ensure that every page will be erased
size_t write_ops = (all_pages - static_pages) * nvs::Page::ENTRY_COUNT * erase_count;
for (size_t i = 0; i < write_ops; ++i) {
TEST_ESP_OK(storage.writeItem(1, "value", i));
TEST_ESP_OK(storage.writeItem(1, "value", i, purgeAfterErase));
}
const size_t max_erase_cnt = write_ops / nvs::Page::ENTRY_COUNT / (all_pages - static_pages);
@@ -735,6 +746,7 @@ TEST_CASE("can erase items", "[nvs]")
// - namespace id is respected in eraseItem and readItem
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
TEST_ESP_OK(storage.init(0, h.get_sectors()));
@@ -743,15 +755,15 @@ TEST_CASE("can erase items", "[nvs]")
for (size_t i = 0; i < nvs::Page::ENTRY_COUNT * 2 - 3; ++i) {
char name[nvs::Item::MAX_KEY_LENGTH + 1];
snprintf(name, sizeof(name), "key%05d", static_cast<int>(i));
TEST_ESP_OK(storage.writeItem(3, name, static_cast<int>(i)));
TEST_ESP_OK(storage.writeItem(3, name, static_cast<int>(i), purgeAfterErase));
}
TEST_ESP_OK(storage.writeItem(1, "foo", 32));
TEST_ESP_OK(storage.writeItem(2, "foo", 64));
TEST_ESP_OK(storage.eraseItem(2, "foo"));
TEST_ESP_OK(storage.writeItem(1, "foo", 32, purgeAfterErase));
TEST_ESP_OK(storage.writeItem(2, "foo", 64, purgeAfterErase));
TEST_ESP_OK(storage.eraseItem(2, "foo", purgeAfterErase));
int val;
TEST_ESP_OK(storage.readItem(1, "foo", val));
CHECK(val == 32);
TEST_ESP_OK(storage.eraseNamespace(3));
TEST_ESP_OK(storage.eraseNamespace(3, purgeAfterErase));
CHECK(storage.readItem(2, "foo", val) == ESP_ERR_NVS_NOT_FOUND);
CHECK(storage.readItem(3, "key00222", val) == ESP_ERR_NVS_NOT_FOUND);
}
@@ -2335,6 +2347,7 @@ TEST_CASE("Check that orphaned blobs are erased during init", "[nvs]")
// Use NVSPartitionTestHelper to provide nvs::Partition instance for the test
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = true;
// Make sure the partition has enough NVS pages
// Requires 1 namespace entry, 1 + 3 metadata entries for the blob,
@@ -2350,13 +2363,13 @@ TEST_CASE("Check that orphaned blobs are erased during init", "[nvs]")
TEST_ESP_OK(storage.init(0, required_sectors));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::BLOB, "key", blob, sizeof(blob)));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::BLOB, "key", blob, sizeof(blob), purgeAfterErase));
TEST_ESP_OK(storage.init(0, required_sectors));
// Check that multi-page item is still available.
TEST_ESP_OK(storage.readItem(1, nvs::ItemType::BLOB, "key", blob, sizeof(blob)));
TEST_ESP_ERR(storage.writeItem(1, nvs::ItemType::BLOB, "key2", blob, sizeof(blob)), ESP_ERR_NVS_NOT_ENOUGH_SPACE);
TEST_ESP_ERR(storage.writeItem(1, nvs::ItemType::BLOB, "key2", blob, sizeof(blob), purgeAfterErase), ESP_ERR_NVS_NOT_ENOUGH_SPACE);
nvs::Page p;
TEST_ESP_OK(p.load(&h, 3)); // This is where index will be placed.
@@ -2365,7 +2378,7 @@ TEST_CASE("Check that orphaned blobs are erased during init", "[nvs]")
TEST_ESP_OK(storage.init(0, required_sectors));
TEST_ESP_ERR(storage.readItem(1, nvs::ItemType::BLOB, "key", blob, sizeof(blob)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::BLOB, "key3", blob, sizeof(blob)));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::BLOB, "key3", blob, sizeof(blob), purgeAfterErase));
}
TEST_CASE("nvs blob fragmentation test", "[nvs]")
@@ -2434,6 +2447,7 @@ TEST_CASE("nvs code handles errors properly when partition is near to full", "[n
// Initialize NVS partition
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
// This test requires at least 4+1 NVS pages to run.
CHECK(h.get_sectors() >= 5);
@@ -2449,12 +2463,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++) {
snprintf(nvs_key, sizeof(nvs_key), "key:%u", count);
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::BLOB, nvs_key, blob, sizeof(blob)));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::BLOB, nvs_key, blob, sizeof(blob), purgeAfterErase));
}
for (uint8_t count = 13; count <= 20; 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);
TEST_ESP_ERR(storage.writeItem(1, nvs::ItemType::BLOB, nvs_key, blob, sizeof(blob), purgeAfterErase), ESP_ERR_NVS_NOT_ENOUGH_SPACE);
}
}
@@ -2513,6 +2527,7 @@ TEST_CASE("monkey test with old-format blob present", "[nvs][monkey][xxx]")
// Clear partition stats before testing
NVSPartitionTestHelper::clear_stats();
const bool purgeAfterErase = true;
static const size_t smallBlobLen = nvs::Page::CHUNK_MAX_SIZE / 3;
@@ -2531,9 +2546,9 @@ TEST_CASE("monkey test with old-format blob present", "[nvs][monkey][xxx]")
for (uint8_t num = 0; num < sector_count; num++) {
nvs::Page p;
TEST_ESP_OK(p.load(&h, num));
p.eraseItem(1, nvs::ItemType::BLOB, "singlepage", nvs::Item::CHUNK_ANY, nvs::VerOffset::VER_ANY);
p.eraseItem(1, nvs::ItemType::BLOB_IDX, "singlepage", nvs::Item::CHUNK_ANY, nvs::VerOffset::VER_ANY);
p.eraseItem(1, nvs::ItemType::BLOB_DATA, "singlepage", nvs::Item::CHUNK_ANY, nvs::VerOffset::VER_ANY);
p.eraseItem(1, nvs::ItemType::BLOB, "singlepage", purgeAfterErase, nvs::Item::CHUNK_ANY, nvs::VerOffset::VER_ANY);
p.eraseItem(1, nvs::ItemType::BLOB_IDX, "singlepage", purgeAfterErase, nvs::Item::CHUNK_ANY, nvs::VerOffset::VER_ANY);
p.eraseItem(1, nvs::ItemType::BLOB_DATA, "singlepage", purgeAfterErase, nvs::Item::CHUNK_ANY, nvs::VerOffset::VER_ANY);
}
// Now write "singlepage" blob in old format
@@ -3186,6 +3201,7 @@ TEST_CASE("recovery after failure to write data", "[nvs]")
// PartitionTestHelper is used to provide nvs::Partition instance for the test
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
const char str[] = "value 0123456789abcdef012345678value 0123456789abcdef012345678";
@@ -3195,10 +3211,10 @@ TEST_CASE("recovery after failure to write data", "[nvs]")
nvs::Storage storage(&h);
TEST_ESP_OK(storage.init(0, 3));
TEST_ESP_ERR(storage.writeItem(1, nvs::ItemType::SZ, "key", str, strlen(str)), ESP_ERR_FLASH_OP_FAIL);
TEST_ESP_ERR(storage.writeItem(1, nvs::ItemType::SZ, "key", str, strlen(str), purgeAfterErase), ESP_ERR_FLASH_OP_FAIL);
// check that repeated operations cause an error
TEST_ESP_ERR(storage.writeItem(1, nvs::ItemType::SZ, "key", str, strlen(str)), ESP_ERR_NVS_INVALID_STATE);
TEST_ESP_ERR(storage.writeItem(1, nvs::ItemType::SZ, "key", str, strlen(str), purgeAfterErase), ESP_ERR_NVS_INVALID_STATE);
uint8_t val;
TEST_ESP_ERR(storage.readItem(1, nvs::ItemType::U8, "key", &val, sizeof(val)), ESP_ERR_NVS_NOT_FOUND);
@@ -3226,13 +3242,14 @@ TEST_CASE("crc errors in item header are handled", "[nvs]")
// PartitionTestHelper is used to provide nvs::Partition instance for the test
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
// 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)));
TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast<uint8_t>(1), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "value1", static_cast<uint32_t>(1), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "value2", static_cast<uint32_t>(2), purgeAfterErase));
// corrupt item header
uint32_t val = 0;
@@ -3249,7 +3266,7 @@ TEST_CASE("crc errors in item header are handled", "[nvs]")
for (size_t i = 0; i < nvs::Page::ENTRY_COUNT; ++i) {
char item_name[nvs::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)));
TEST_ESP_OK(storage.writeItem(1, item_name, static_cast<uint32_t>(i), purgeAfterErase));
}
// corrupt another item on the full page
@@ -3316,13 +3333,14 @@ TEST_CASE("zero span in item header with correct crc is handled", "[nvs]")
// PartitionTestHelper is used to provide nvs::Partition instance for the test
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
// 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)));
TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast<uint8_t>(1), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "value1", static_cast<uint32_t>(1), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "value2", static_cast<uint32_t>(2), purgeAfterErase));
// damage item header of value1 to introduce span==0 error, recalculate crc
{
@@ -3357,7 +3375,7 @@ TEST_CASE("zero span in item header with correct crc is handled", "[nvs]")
for (size_t i = 0; i < nvs::Page::ENTRY_COUNT; ++i) {
char item_name[nvs::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)));
TEST_ESP_OK(storage.writeItem(1, item_name, static_cast<uint32_t>(i), purgeAfterErase));
}
// damage item header of item_125 to introduce span==0 error, recalculate crc
@@ -3397,18 +3415,19 @@ TEST_CASE("inconsistent fields in item header with correct crc are handled for s
// PartitionTestHelper is used to provide nvs::Partition instance for the test
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
// 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(0, "ns1", static_cast<uint8_t>(1), purgeAfterErase));
const char str[] = "String67890123456789012345678901String67890123456789012345678901String6789012345678901234567890"; // 95 + 1 bytes data occupy 3 entries, overhead 1
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "valuestr1", str, strlen(str)));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "valuestr2", str, strlen(str)));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "valuestr3", str, strlen(str)));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "valuestr4", str, strlen(str)));
TEST_ESP_OK(storage.writeItem(1, "valueu32", static_cast<uint32_t>(2)));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "valuestr1", str, strlen(str), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "valuestr2", str, strlen(str), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "valuestr3", str, strlen(str), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "valuestr4", str, strlen(str), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "valueu32", static_cast<uint32_t>(2), purgeAfterErase));
// read the values back
char read_str[sizeof(str)] = {0};
@@ -3789,14 +3808,15 @@ TEST_CASE("invalid data type in item header with correct crc is handled", "[nvs]
// PartitionTestHelper is used to provide nvs::Partition instance for the test
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
// 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)));
TEST_ESP_OK(storage.writeItem(1, "value3", static_cast<uint32_t>(3)));
TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast<uint8_t>(1), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "value1", static_cast<uint32_t>(1), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "value2", static_cast<uint32_t>(2), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "value3", static_cast<uint32_t>(3), purgeAfterErase));
// damage item header of value2 to introduce data type error, recalculate crc
{
@@ -4745,6 +4765,249 @@ TEST_CASE("nvs find key tests", "[nvs]")
nvs_close(handle_2);
TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME));
}
TEST_CASE("nvs handle purge test", "[nvs]")
{
// TC validating that erased and overwritten entries are purged from NVS storage based on the mode of handle opening.
// The test covers both modes: with implicit purge enabled and disabled.
// To validate the behavior, the test:
// Writes entries of different types to NVS storage. Entries include: integer, string and multi page blob.
// Inf first round, the data contains initial markers to identify them.
// Then it erases / overwrites the entries one by one and after each erase it checks whether the markers are still present in the storage
// SECTIONS:
// 1. Open writable namespace without implicit purge. Erase data. Should find markers.
// 2/ Open writable namespace without implicit purge. Overwrite data. Should find old markers.
// 3. Open writable namespace with implicit purge. Erase data. Should NOT find markers.
// 4. Open writable namespace with implicit purge. Overwrite data. Should NOT find old markers.
const char* part_name = TEST_DEFAULT_PARTITION_NAME;
const char* ns_name = "ns1";
// Markers and lengths used in the test
// String mrkers are fixed strings at the beginning of the string data as well as at the end of the string data
const char str_key[] = "str_key";
const char str_marker_start[] = "STR_START_";;
const char str_marker_end[] = "_STR_END";
const size_t string_len = 3000; // 4000 is max, 3000 fills almost entire page
// Blob markers are fixed patterns at the beginning of the blob data as well as at the end of the blob data
const char blob_key[] = "blob_key";
const uint8_t blob_marker_start[] = {0xBA, 0xAD, 0xF0, 0x0D};
const uint8_t blob_marker_end[] = {0xD0, 0x0F, 0xDA, 0xAB};
const size_t blob_len = 8192; // 8192 bytes to ensure multipage blob
// uint64_t marker is used for integer data
const char uint_key[] = "uint_key";
const uint64_t uint_marker = 0xDEADBEEFFEEBDAED;
// Overwrite patterns
const char str_overwrite_pattern = 'X';
const uint8_t blob_overwrite_pattern = 0x5A;
const uint64_t uint_overwrite_pattern = 0xA5A5A5A5A5A5A5A5;
// nvs_flash_erase_partition to start with clean partition
TEST_ESP_OK(nvs_flash_erase_partition(part_name));
TEST_ESP_OK(nvs_flash_init_partition(part_name));
// init non-purge handle
nvs_handle_t handle;
TEST_ESP_OK(nvs_open_from_partition(part_name, ns_name, NVS_READWRITE, &handle));
// populate with markers
// string
char* str_data = (char*) malloc(string_len);
CHECK(str_data != nullptr);
memset(str_data, 'A', string_len);
memcpy(str_data, str_marker_start, strlen(str_marker_start));
memcpy(str_data + string_len - (strlen(str_marker_end) + 1), str_marker_end, strlen(str_marker_end));
str_data[string_len - 1] = '\0'; // ensure null termination
TEST_ESP_OK(nvs_set_str(handle, str_key, str_data));
free(str_data);
// blob
uint8_t* blob_data = (uint8_t*) malloc(blob_len);
CHECK(blob_data != nullptr);
memset(blob_data, 0xFF, blob_len);
memcpy(blob_data, blob_marker_start, sizeof(blob_marker_start));
memcpy(blob_data + blob_len - sizeof(blob_marker_end), blob_marker_end, sizeof(blob_marker_end));
TEST_ESP_OK(nvs_set_blob(handle, blob_key, blob_data, blob_len));
free(blob_data);
// uint64_t
TEST_ESP_OK(nvs_set_u64(handle, uint_key, uint_marker));
TEST_ESP_OK(nvs_commit(handle));
// make sure markers are written
// check string marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_start, strlen(str_marker_start)));
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_end, strlen(str_marker_end)));
// check blob marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, blob_marker_start, sizeof(blob_marker_start)));
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, blob_marker_end, sizeof(blob_marker_end)));
// check uint64_t marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)&uint_marker, sizeof(uint_marker)));
// deinit
nvs_close(handle);
SECTION("non-purge handle - erase entries")
{
// init non-purge handle
TEST_ESP_OK(nvs_open_from_partition(part_name, ns_name, NVS_READWRITE, &handle));
// erase / check markers
TEST_ESP_OK(nvs_erase_key(handle, str_key));
TEST_ESP_OK(nvs_erase_key(handle, blob_key));
TEST_ESP_OK(nvs_erase_key(handle, uint_key));
TEST_ESP_OK(nvs_commit(handle));
// this is non implicit purge handle, so markers should be found
// check string marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_start, strlen(str_marker_start)));
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_end, strlen(str_marker_end)));
// check blob marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, blob_marker_start, sizeof(blob_marker_start)));
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, blob_marker_end, sizeof(blob_marker_end)));
// check uint64_t marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)&uint_marker, sizeof(uint_marker)));
// explicitly call purge
TEST_ESP_OK(nvs_purge_all(handle));
// after purge, markers should NOT be found
// check string marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_start, strlen(str_marker_start)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_end, strlen(str_marker_end)), ESP_ERR_NVS_NOT_FOUND);
// check blob marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, blob_marker_start, sizeof(blob_marker_start)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, blob_marker_end, sizeof(blob_marker_end)), ESP_ERR_NVS_NOT_FOUND);
// check uint64_t marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)&uint_marker, sizeof(uint_marker)), ESP_ERR_NVS_NOT_FOUND);
}
SECTION("non-purge handle - overwrite entries")
{
// init non-purge handle
TEST_ESP_OK(nvs_open_from_partition(part_name, ns_name, NVS_READWRITE, &handle));
// overwrite / check markers
// string
char* str_data_ov = (char*) malloc(string_len);
CHECK(str_data_ov != nullptr);
memset(str_data_ov, str_overwrite_pattern, string_len);
str_data_ov[string_len - 1] = '\0'; // ensure null termination
TEST_ESP_OK(nvs_set_str(handle, str_key, str_data_ov));
// blob
uint8_t* blob_data_ov = (uint8_t*) malloc(blob_len);
CHECK(blob_data_ov != nullptr);
memset(blob_data_ov, blob_overwrite_pattern, blob_len);
TEST_ESP_OK(nvs_set_blob(handle, blob_key, blob_data_ov, blob_len));
// uint64_t
TEST_ESP_OK(nvs_set_u64(handle, uint_key, uint_overwrite_pattern));
TEST_ESP_OK(nvs_commit(handle));
free(str_data_ov);
free(blob_data_ov);
// this is non implicit purge handle, so old markers should be found
// check string marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_start, strlen(str_marker_start)));
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_end, strlen(str_marker_end)));
// check blob marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, blob_marker_start, sizeof(blob_marker_start)));
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, blob_marker_end, sizeof(blob_marker_end)));
// check uint64_t marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)&uint_marker, sizeof(uint_marker)));
// explicitly call purge
TEST_ESP_OK(nvs_purge_all(handle));
// after purge, markers should NOT be found
// check string marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_start, strlen(str_marker_start)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_end, strlen(str_marker_end)), ESP_ERR_NVS_NOT_FOUND);
// check blob marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, blob_marker_start, sizeof(blob_marker_start)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, blob_marker_end, sizeof(blob_marker_end)), ESP_ERR_NVS_NOT_FOUND);
// check uint64_t marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)&uint_marker, sizeof(uint_marker)), ESP_ERR_NVS_NOT_FOUND);
}
SECTION("purge handle - erase entries")
{
// init purge handle
TEST_ESP_OK(nvs_open_from_partition(part_name, ns_name, NVS_READWRITE_PURGE, &handle));
// erase / check markers
TEST_ESP_OK(nvs_erase_key(handle, str_key));
TEST_ESP_OK(nvs_erase_key(handle, blob_key));
TEST_ESP_OK(nvs_erase_key(handle, uint_key));
TEST_ESP_OK(nvs_commit(handle));
// this is purge handle, so markers should NOT be found
// check string marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_start, strlen(str_marker_start)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_end, strlen(str_marker_end)), ESP_ERR_NVS_NOT_FOUND);
// check blob marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, blob_marker_start, sizeof(blob_marker_start)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, blob_marker_end, sizeof(blob_marker_end)), ESP_ERR_NVS_NOT_FOUND);
// check uint64_t marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)&uint_marker, sizeof(uint_marker)), ESP_ERR_NVS_NOT_FOUND);
}
SECTION("purge handle - overwrite entries")
{
// init purge handle
TEST_ESP_OK(nvs_open_from_partition(part_name, ns_name, NVS_READWRITE_PURGE, &handle));
// overwrite / check markers
// string
char* str_data_ov = (char*) malloc(string_len);
CHECK(str_data_ov != nullptr);
memset(str_data_ov, str_overwrite_pattern, string_len);
str_data_ov[string_len - 1] = '\0'; // ensure null termination
TEST_ESP_OK(nvs_set_str(handle, str_key, str_data_ov));
// blob
uint8_t* blob_data_ov = (uint8_t*) malloc(blob_len);
CHECK(blob_data_ov != nullptr);
memset(blob_data_ov, blob_overwrite_pattern, blob_len);
TEST_ESP_OK(nvs_set_blob(handle, blob_key, blob_data_ov, blob_len));
// uint64_t
TEST_ESP_OK(nvs_set_u64(handle, uint_key, uint_overwrite_pattern));
TEST_ESP_OK(nvs_commit(handle));
free(str_data_ov);
free(blob_data_ov);
// this is purge handle, so old markers should NOT be found
// check string marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_start, strlen(str_marker_start)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_end, strlen(str_marker_end)), ESP_ERR_NVS_NOT_FOUND);
// check blob marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, blob_marker_start, sizeof(blob_marker_start)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, blob_marker_end, sizeof(blob_marker_end)), ESP_ERR_NVS_NOT_FOUND);
// check uint64_t marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)&uint_marker, sizeof(uint_marker)), ESP_ERR_NVS_NOT_FOUND);
}
// deinit
nvs_close(handle);
// clean up partition
TEST_ESP_OK(nvs_flash_erase_partition(part_name));
}
// Add new tests above
// This test has to be the final one
@@ -12,6 +12,7 @@
using namespace std;
#define TEST_DEFAULT_PARTITION_NAME "nvs"
#define TEST_DEFAULT_PURGE_AFTER_ERASE true // erase with purge after erase
TEST_CASE("Storage iterator recognizes blob with VerOffset::VER_1_OFFSET", "[nvs_storage]")
{
@@ -31,11 +32,10 @@ TEST_CASE("Storage iterator recognizes blob with VerOffset::VER_1_OFFSET", "[nvs
REQUIRE(storage != nullptr);
storage->createOrOpenNamespace("test_ns", true, ns_index);
CHECK(storage->writeItem(ns_index, nvs::ItemType::BLOB, "test_blob", blob, sizeof(blob)) == ESP_OK);
CHECK(storage->writeItem(ns_index, nvs::ItemType::BLOB, "test_blob", blob, sizeof(blob), TEST_DEFAULT_PURGE_AFTER_ERASE) == ESP_OK);
// changing provokes a blob with version offset 1 (VerOffset::VER_1_OFFSET)
CHECK(storage->writeItem(ns_index, nvs::ItemType::BLOB, "test_blob", blob_new, sizeof(blob_new)) == ESP_OK);
CHECK(storage->writeItem(ns_index, nvs::ItemType::BLOB, "test_blob", blob_new, sizeof(blob_new), TEST_DEFAULT_PURGE_AFTER_ERASE) == ESP_OK);
nvs_opaque_iterator_t it;
it.storage = storage;
it.type = NVS_TYPE_ANY;
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -26,6 +26,8 @@ NVS_GUARD_SYSVIEW_MACRO_EXPANSION_PUSH();
using namespace std;
using namespace nvs;
#define DEFAULT_PURGE_AFTER_ERASE false
void test_Page_load_reading_header_fails()
{
PartitionEmulationFixture fix;
@@ -661,7 +663,7 @@ void test_Page_eraseItem__uninitialized()
{
Page page;
TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "test_value"));
TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "test_value", DEFAULT_PURGE_AFTER_ERASE));
}
void test_Page_eraseItem__key_not_found()
@@ -672,7 +674,7 @@ void test_Page_eraseItem__key_not_found()
TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, fix.page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "different"));
TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, fix.page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "different", DEFAULT_PURGE_AFTER_ERASE));
TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount());
TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount());
@@ -692,7 +694,7 @@ void test_Page_eraseItem__write_fail()
// simulated write failure should fail the eraseItem call
fix.fail_write_at(1);
TEST_ASSERT_EQUAL(ESP_ERR_FLASH_OP_FAIL, fix.page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "test_value"));
TEST_ASSERT_EQUAL(ESP_ERR_FLASH_OP_FAIL, fix.page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "test_value", DEFAULT_PURGE_AFTER_ERASE));
TEST_ASSERT_EQUAL(1, fix.page.getUsedEntryCount());
TEST_ASSERT_EQUAL(123, fix.page.getErasedEntryCount());
@@ -706,7 +708,7 @@ void test_Page_eraseItem__write_succeed()
TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount());
TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount());
TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
TEST_ASSERT_EQUAL(ESP_OK, fix.page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "test_value"));
TEST_ASSERT_EQUAL(ESP_OK, fix.page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "test_value", DEFAULT_PURGE_AFTER_ERASE));
TEST_ASSERT_EQUAL(1, fix.page.getUsedEntryCount());
TEST_ASSERT_EQUAL(123, fix.page.getErasedEntryCount());
TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+27 -5
View File
@@ -84,8 +84,9 @@ _Pragma("pop_macro(\"I64\")")
* @brief Mode of opening the non-volatile storage
*/
typedef enum {
NVS_READONLY, /*!< Read only */
NVS_READWRITE /*!< Read and write */
NVS_READONLY, /*!< Read only */
NVS_READWRITE, /*!< Read and write */
NVS_READWRITE_PURGE /*!< Read and write */
} nvs_open_mode_t;
/*
@@ -136,9 +137,12 @@ typedef struct nvs_opaque_iterator_t *nvs_iterator_t;
* table.
*
* @param[in] namespace_name Namespace name. Maximum length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty.
* @param[in] open_mode NVS_READWRITE or NVS_READONLY. If NVS_READONLY, will
* open a handle for reading only. All write requests will
* be rejected for this handle.
* @param[in] open_mode NVS_READONLY opens a read only handle
* NVS_READWRITE opens a read/write handle. erase and set operations are allowed.
* previous data is marked as deleted only and new data is written to a new location.
* NVS_READWRITE_PURGE opens a read/write handle. Update and erase operations are allowed.
* previous data is purged from flash memory to ensure that it cannot be recovered.
* New data is written to a new location.
* @param[out] out_handle If successful (return code is zero), handle will be
* returned in this argument.
*
@@ -558,6 +562,24 @@ esp_err_t nvs_erase_key(nvs_handle_t handle, const char* key);
*/
esp_err_t nvs_erase_all(nvs_handle_t handle);
/**
* @brief Purge data of all erased key-value pairs in a namespace
*
* Note that actual storage may not be updated until nvs_commit function is called.
*
* @param[in] handle Storage handle obtained with nvs_open.
* Handles that were opened read only cannot be used.
*
* @return
* - ESP_OK if erase operation was successful
* - ESP_FAIL if there is an internal error; most likely due to corrupted
* NVS partition (only if NVS assertion checks are disabled)
* - ESP_ERR_NVS_INVALID_HANDLE if handle has been closed or is NULL
* - ESP_ERR_NVS_READ_ONLY if handle was opened as read only
* - other error codes from the underlying storage driver
*/
esp_err_t nvs_purge_all(nvs_handle_t handle);
/**
* @brief Write any pending changes to non-volatile storage
*
@@ -210,6 +210,11 @@ public:
*/
virtual esp_err_t erase_all() = 0;
/**
* Purges all erased entries in the scope of this handle. The scope may vary, depending on the implementation.
*/
virtual esp_err_t purge_all() = 0;
/**
* Commits all changes done through this handle so far.
* Currently, NVS writes to storage right after the set and get functions,
+14
View File
@@ -381,6 +381,20 @@ extern "C" esp_err_t nvs_erase_all(nvs_handle_t c_handle)
return handle->erase_all();
}
extern "C" esp_err_t nvs_purge_all(nvs_handle_t c_handle)
{
Lock lock;
ESP_LOGD(TAG, "%s", __func__);
NVSHandleSimple *handle;
auto err = nvs_find_ns_handle(c_handle, &handle);
if (err != ESP_OK) {
return err;
}
return handle->purge_all();
}
template<typename T>
static esp_err_t nvs_set(nvs_handle_t c_handle, const char* key, T value)
{
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -55,6 +55,11 @@ esp_err_t NVSHandleLocked::erase_all() {
return handle->erase_all();
}
esp_err_t NVSHandleLocked::purge_all() {
Lock lock;
return handle->purge_all();
}
esp_err_t NVSHandleLocked::commit() {
Lock lock;
return handle->commit();
@@ -46,6 +46,8 @@ public:
esp_err_t erase_all() override;
esp_err_t purge_all() override;
esp_err_t commit() override;
esp_err_t get_used_entry_count(size_t& usedEntries) override;
+14 -6
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -18,7 +18,7 @@ esp_err_t NVSHandleSimple::set_typed_item(ItemType datatype, const char *key, co
if (!valid) return ESP_ERR_NVS_INVALID_HANDLE;
if (mReadOnly) return ESP_ERR_NVS_READ_ONLY;
return mStoragePtr->writeItem(mNsIndex, datatype, key, data, dataSize);
return mStoragePtr->writeItem(mNsIndex, datatype, key, data, dataSize, mPurgeAfterErase);
}
esp_err_t NVSHandleSimple::get_typed_item(ItemType datatype, const char *key, void* data, size_t dataSize)
@@ -33,7 +33,7 @@ esp_err_t NVSHandleSimple::set_string(const char *key, const char* str)
if (!valid) return ESP_ERR_NVS_INVALID_HANDLE;
if (mReadOnly) return ESP_ERR_NVS_READ_ONLY;
return mStoragePtr->writeItem(mNsIndex, nvs::ItemType::SZ, key, str, strlen(str) + 1);
return mStoragePtr->writeItem(mNsIndex, nvs::ItemType::SZ, key, str, strlen(str) + 1, mPurgeAfterErase);
}
esp_err_t NVSHandleSimple::set_blob(const char *key, const void* blob, size_t len)
@@ -41,7 +41,7 @@ esp_err_t NVSHandleSimple::set_blob(const char *key, const void* blob, size_t le
if (!valid) return ESP_ERR_NVS_INVALID_HANDLE;
if (mReadOnly) return ESP_ERR_NVS_READ_ONLY;
return mStoragePtr->writeItem(mNsIndex, nvs::ItemType::BLOB, key, blob, len);
return mStoragePtr->writeItem(mNsIndex, nvs::ItemType::BLOB, key, blob, len, mPurgeAfterErase);
}
esp_err_t NVSHandleSimple::get_string(const char *key, char* out_str, size_t len)
@@ -87,7 +87,7 @@ esp_err_t NVSHandleSimple::erase_item(const char* key)
if (!valid) return ESP_ERR_NVS_INVALID_HANDLE;
if (mReadOnly) return ESP_ERR_NVS_READ_ONLY;
return mStoragePtr->eraseItem(mNsIndex, key);
return mStoragePtr->eraseItem(mNsIndex, key, mPurgeAfterErase);
}
esp_err_t NVSHandleSimple::erase_all()
@@ -95,7 +95,15 @@ esp_err_t NVSHandleSimple::erase_all()
if (!valid) return ESP_ERR_NVS_INVALID_HANDLE;
if (mReadOnly) return ESP_ERR_NVS_READ_ONLY;
return mStoragePtr->eraseNamespace(mNsIndex);
return mStoragePtr->eraseNamespace(mNsIndex, mPurgeAfterErase);
}
esp_err_t NVSHandleSimple::purge_all()
{
if (!valid) return ESP_ERR_NVS_INVALID_HANDLE;
if (mReadOnly) return ESP_ERR_NVS_READ_ONLY;
return mStoragePtr->purgeNamespace(mNsIndex);
}
esp_err_t NVSHandleSimple::commit()
+10 -2
View File
@@ -27,10 +27,11 @@ class NVSHandleSimple : public intrusive_list_node<NVSHandleSimple>,
public ExceptionlessAllocatable {
friend class NVSPartitionManager;
public:
NVSHandleSimple(bool readOnly, uint8_t nsIndex, Storage *StoragePtr) :
NVSHandleSimple(bool readOnly, uint8_t nsIndex, Storage *StoragePtr, bool purgeAfterErase) :
mStoragePtr(StoragePtr),
mNsIndex(nsIndex),
mReadOnly(readOnly),
mPurgeAfterErase(purgeAfterErase),
valid(1)
{ }
@@ -56,6 +57,8 @@ public:
esp_err_t erase_all() override;
esp_err_t purge_all() override;
esp_err_t commit() override;
esp_err_t get_used_entry_count(size_t &usedEntries) override;
@@ -91,11 +94,16 @@ private:
/**
* Whether this handle is marked as read-only or read-write.
* 0 indicates read-only, any other value read-write.
* 1 indicates read-only, 0 indicates read-write.
*/
uint8_t mReadOnly;
/**
* Whether this handle purges the erased or updated items right after erasing or updating them.
* 0 indicates no purge, any other value indicates purge.
*/
uint8_t mPurgeAfterErase; /**
* Indicates the validity of this handle.
* Upon opening, a handle is valid. It becomes invalid if the underlying storage is de-initialized.
*/
+124 -12
View File
@@ -276,7 +276,7 @@ esp_err_t Page::readVariableLengthItemData(const Item& item, const size_t index,
dst += willCopy;
}
if (Item::calculateCrc32(reinterpret_cast<uint8_t * >(data), item.varLength.dataSize) != item.varLength.dataCrc32) {
rc = eraseEntryAndSpan(index);
rc = eraseEntryAndSpan(index, DEFAULT_PURGE_AFTER_ERASE);
if (rc != ESP_OK) {
return rc;
}
@@ -388,7 +388,7 @@ esp_err_t Page::cmpItem(uint8_t nsIndex, ItemType datatype, const char* key, con
return ESP_OK;
}
esp_err_t Page::eraseItem(uint8_t nsIndex, ItemType datatype, const char* key, uint8_t chunkIdx, VerOffset chunkStart)
esp_err_t Page::eraseItem(uint8_t nsIndex, ItemType datatype, const char* key, const bool purgeAfterErase, uint8_t chunkIdx, VerOffset chunkStart)
{
size_t index = 0;
Item item;
@@ -396,7 +396,60 @@ esp_err_t Page::eraseItem(uint8_t nsIndex, ItemType datatype, const char* key, u
if (rc != ESP_OK) {
return rc;
}
return eraseEntryAndSpan(index);
return eraseEntryAndSpan(index, purgeAfterErase);
}
esp_err_t Page::purgeErasedItems(uint8_t nsIndex)
{
if (mState == PageState::INVALID) {
return ESP_ERR_NVS_INVALID_STATE;
}
size_t index = 0;
esp_err_t err;
EntryState state;
while (index < ENTRY_COUNT) {
err = mEntryTable.get(index, &state);
if (err != ESP_OK) {
return err;
}
if (state == EntryState::ERASED) {
Item item;
err = readEntry(index, item);
if (err != ESP_OK) {
return err;
}
// check item sanity to avoid purging random data
bool headerConsistent = item.checkHeaderConsistency(index);
// if header is not consistent, we cannot rely on nsIndex, so skip
if (!headerConsistent) {
++index;
continue;
}
// if nsIndex does not match, skip as well
if (item.nsIndex != nsIndex) {
++index;
continue;
}
// we have matching nsIndex and a consistent header, span is valid
// purge entries belonging to the item's span range
// we assume that if first entry is erased, the whole span is erased as well
err = purgeEntryRange(index, index + item.span);
if (err != ESP_OK) {
return err;
}
index += item.span;
continue;
}
// next entry
++index;
}
return ESP_OK;
}
esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, uint8_t chunkIdx, VerOffset chunkStart)
@@ -406,7 +459,7 @@ esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, ui
return findItem(nsIndex, datatype, key, index, item, chunkIdx, chunkStart);
}
esp_err_t Page::eraseEntryAndSpan(size_t index)
esp_err_t Page::eraseEntryAndSpan(size_t index, const bool purgeAfterErase)
{
uint32_t seq_num;
getSeqNumber(seq_num);
@@ -432,6 +485,12 @@ esp_err_t Page::eraseEntryAndSpan(size_t index)
if (rc != ESP_OK) {
return rc;
}
if(purgeAfterErase) {
rc = purgeEntry(index);
if (rc != ESP_OK) {
return rc;
}
}
} else {
mHashList.erase(index);
span = item.span;
@@ -453,12 +512,28 @@ esp_err_t Page::eraseEntryAndSpan(size_t index)
if (rc != ESP_OK) {
return rc;
}
if(purgeAfterErase) {
if(span == 1) {
rc = purgeEntry(index);
} else {
rc = purgeEntryRange(index, index + span);
}
}
if (rc != ESP_OK) {
return rc;
}
}
} else {
auto rc = alterEntryState(index, EntryState::ERASED);
if (rc != ESP_OK) {
return rc;
}
if(purgeAfterErase) {
rc = purgeEntry(index);
if (rc != ESP_OK) {
return rc;
}
}
}
if (index == mFirstUsedEntry) {
@@ -635,6 +710,13 @@ esp_err_t Page::mLoadEntryTable()
mState = PageState::INVALID;
return err;
}
if(DEFAULT_PURGE_AFTER_ERASE) {
err = purgeEntry(mNextFreeEntry);
if (err != ESP_OK) {
mState = PageState::INVALID;
return err;
}
}
++mNextFreeEntry;
if (oldState == EntryState::WRITTEN) {
--mUsedEntryCount;
@@ -666,7 +748,7 @@ esp_err_t Page::mLoadEntryTable()
if (state == EntryState::ILLEGAL) {
lastItemIndex = INVALID_ENTRY;
auto err = eraseEntryAndSpan(i);
auto err = eraseEntryAndSpan(i, DEFAULT_PURGE_AFTER_ERASE);
if (err != ESP_OK) {
mState = PageState::INVALID;
return err;
@@ -683,7 +765,7 @@ esp_err_t Page::mLoadEntryTable()
}
if (!item.checkHeaderConsistency(i)) {
err = eraseEntryAndSpan(i);
err = eraseEntryAndSpan(i, DEFAULT_PURGE_AFTER_ERASE);
if (err != ESP_OK) {
mState = PageState::INVALID;
return err;
@@ -715,7 +797,7 @@ esp_err_t Page::mLoadEntryTable()
}
}
if (needErase) {
eraseEntryAndSpan(i);
eraseEntryAndSpan(i, DEFAULT_PURGE_AFTER_ERASE);
continue;
}
}
@@ -725,7 +807,7 @@ esp_err_t Page::mLoadEntryTable()
* for same key on active page. Since datatype is not used in hash calculation,
* old-format blob will be removed.*/
if (duplicateIndex < i) {
eraseEntryAndSpan(duplicateIndex);
eraseEntryAndSpan(duplicateIndex, DEFAULT_PURGE_AFTER_ERASE);
}
}
@@ -735,7 +817,7 @@ esp_err_t Page::mLoadEntryTable()
Item dupItem;
if (findItem(item.nsIndex, item.datatype, item.key, findItemIndex, dupItem) == ESP_OK) {
if (findItemIndex < lastItemIndex) {
auto err = eraseEntryAndSpan(findItemIndex);
auto err = eraseEntryAndSpan(findItemIndex, DEFAULT_PURGE_AFTER_ERASE);
if (err != ESP_OK) {
mState = PageState::INVALID;
return err;
@@ -763,7 +845,7 @@ esp_err_t Page::mLoadEntryTable()
}
if (!item.checkHeaderConsistency(i)) {
err = eraseEntryAndSpan(i);
err = eraseEntryAndSpan(i, DEFAULT_PURGE_AFTER_ERASE);
if (err != ESP_OK) {
mState = PageState::INVALID;
return err;
@@ -788,7 +870,7 @@ esp_err_t Page::mLoadEntryTable()
return err;
}
if (state != EntryState::WRITTEN) {
eraseEntryAndSpan(i);
eraseEntryAndSpan(i, DEFAULT_PURGE_AFTER_ERASE);
break;
}
}
@@ -897,6 +979,36 @@ esp_err_t Page::readEntry(size_t index, Item &dst) const
return ESP_OK;
}
esp_err_t Page::purgeEntry(size_t index){
return purgeEntryRange(index, index + 1);
}
esp_err_t Page::purgeEntryRange(size_t begin, size_t end)
{
NVS_ASSERT_OR_RETURN(end <= ENTRY_COUNT, ESP_FAIL);
NVS_ASSERT_OR_RETURN(end > begin, ESP_FAIL);
esp_err_t err;
uint32_t phyAddr;
uint8_t entryPurgeBuffer[ENTRY_SIZE];
std::fill_n(entryPurgeBuffer, ENTRY_SIZE, 0x00);
for (size_t i = begin; i < end; i++) {
err = getEntryAddress(i, &phyAddr);
if (err != ESP_OK) {
return err;
}
err = mPartition->write_raw(phyAddr, entryPurgeBuffer, ENTRY_SIZE);
if (err != ESP_OK) {
mState = PageState::INVALID;
return err;
}
}
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)
{
if (mState == PageState::CORRUPT || mState == PageState::INVALID || mState == PageState::UNINITIALIZED) {
@@ -950,7 +1062,7 @@ esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, si
}
if (!item.checkHeaderConsistency(i)) {
rc = eraseEntryAndSpan(i);
rc = eraseEntryAndSpan(i, DEFAULT_PURGE_AFTER_ERASE);
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
+12 -4
View File
@@ -18,6 +18,8 @@ namespace nvs
class Page : public intrusive_list_node<Page>, public ExceptionlessAllocatable
{
public:
static const bool DEFAULT_PURGE_AFTER_ERASE = true;
static const uint32_t PSB_INIT = NVS_CONST_PSB_INIT;
static const uint32_t PSB_FULL = NVS_CONST_PSB_FULL;
static const uint32_t PSB_FREEING = NVS_CONST_PSB_FREEING;
@@ -86,13 +88,15 @@ public:
esp_err_t cmpItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY);
esp_err_t eraseItem(uint8_t nsIndex, ItemType datatype, const char* key, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY);
esp_err_t eraseItem(uint8_t nsIndex, ItemType datatype, const char* key, const bool purgeAfterErase, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY);
esp_err_t purgeErasedItems(uint8_t nsIndex);
esp_err_t findItem(uint8_t nsIndex, ItemType datatype, const char* key, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY);
esp_err_t findItem(uint8_t nsIndex, ItemType datatype, const char* key, size_t &itemIndex, Item& item, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY);
esp_err_t eraseEntryAndSpan(size_t index);
esp_err_t eraseEntryAndSpan(size_t index, const bool purgeAfterErase);
template<typename T>
esp_err_t writeItem(uint8_t nsIndex, const char* key, const T& value)
@@ -113,9 +117,9 @@ public:
}
template<typename T>
esp_err_t eraseItem(uint8_t nsIndex, const char* key)
esp_err_t eraseItem(uint8_t nsIndex, const char* key, const bool purgeAfterErase)
{
return eraseItem(nsIndex, itemTypeOf<T>(), key);
return eraseItem(nsIndex, itemTypeOf<T>(), key, purgeAfterErase);
}
size_t getUsedEntryCount() const
@@ -178,6 +182,10 @@ protected:
esp_err_t alterPageState(PageState state);
esp_err_t purgeEntryRange(size_t begin, size_t end);
esp_err_t purgeEntry(size_t index);
esp_err_t readEntry(size_t index, Item& dst) const;
esp_err_t writeEntry(const Item& item);
+2 -2
View File
@@ -75,7 +75,7 @@ esp_err_t PageManager::load(Partition *partition, uint32_t baseSector, uint32_t
for (it = begin(); it != last; ++it) {
if ((it->state() != Page::PageState::FREEING) &&
(it->eraseItem(item.nsIndex, item.datatype, item.key, item.chunkIndex) == ESP_OK)) {
(it->eraseItem(item.nsIndex, item.datatype, item.key, Page::DEFAULT_PURGE_AFTER_ERASE, item.chunkIndex) == ESP_OK)) {
break;
}
}
@@ -85,7 +85,7 @@ esp_err_t PageManager::load(Partition *partition, uint32_t baseSector, uint32_t
for (it = begin(); it != last; ++it) {
if ((it->state() != Page::PageState::FREEING) &&
(it->eraseItem(item.nsIndex, ItemType::BLOB, item.key, item.chunkIndex) == ESP_OK)) {
(it->eraseItem(item.nsIndex, ItemType::BLOB, item.key, Page::DEFAULT_PURGE_AFTER_ERASE, item.chunkIndex) == ESP_OK)) {
break;
}
}
@@ -195,17 +195,19 @@ esp_err_t NVSPartitionManager::open_handle(const char *part_name,
return ESP_ERR_NVS_PART_NOT_FOUND;
}
if (open_mode == NVS_READWRITE && const_cast<Partition*>(sHandle->getPart())->get_readonly()) {
#define WANTS_WRITE_MODE(mode) ((mode) == NVS_READWRITE || (mode) == NVS_READWRITE_PURGE)
if (WANTS_WRITE_MODE(open_mode) && const_cast<Partition*>(sHandle->getPart())->get_readonly()) {
return ESP_ERR_NOT_ALLOWED;
}
esp_err_t err = sHandle->createOrOpenNamespace(ns_name, open_mode == NVS_READWRITE, nsIndex);
esp_err_t err = sHandle->createOrOpenNamespace(ns_name, WANTS_WRITE_MODE(open_mode), nsIndex);
if (err != ESP_OK) {
return err;
}
NVSHandleSimple* new_handle = new (std::nothrow) NVSHandleSimple(open_mode==NVS_READONLY, nsIndex, sHandle);
NVSHandleSimple* new_handle = new (std::nothrow) NVSHandleSimple(open_mode==NVS_READONLY, nsIndex, sHandle, open_mode==NVS_READWRITE_PURGE);
if (new_handle == nullptr) {
return ESP_ERR_NO_MEM;
}
+33 -20
View File
@@ -132,7 +132,7 @@ void Storage::eraseMismatchedBlobIndexes(TBlobIndexList& blobIdxList)
}
Page& p = *it;
if(p.eraseItem(iter->nsIndex, nvs::ItemType::BLOB_IDX, iter->key, 255, iter->chunkStart) == ESP_OK){
if(p.eraseItem(iter->nsIndex, nvs::ItemType::BLOB_IDX, iter->key, Page::DEFAULT_PURGE_AFTER_ERASE, 255, iter->chunkStart) == ESP_OK){
break;
}
}
@@ -172,7 +172,7 @@ void Storage::eraseOrphanDataBlobs(TBlobIndexList& blobIdxList)
&& (item.chunkIndex >= static_cast<uint8_t> (e.chunkStart))
&& (item.chunkIndex < static_cast<uint8_t> (e.chunkStart) + e.chunkCount);});
if(iter == std::end(blobIdxList)) {
p.eraseItem(item.nsIndex, item.datatype, item.key, item.chunkIndex);
p.eraseItem(item.nsIndex, item.datatype, item.key, Page::DEFAULT_PURGE_AFTER_ERASE, item.chunkIndex);
}
itemIndex += item.span;
@@ -270,7 +270,7 @@ esp_err_t Storage::findItem(uint8_t nsIndex, ItemType datatype, const char* key,
return ESP_ERR_NVS_NOT_FOUND;
}
esp_err_t Storage::writeMultiPageBlob(uint8_t nsIndex, const char* key, const void* data, size_t dataSize, VerOffset chunkStart)
esp_err_t Storage::writeMultiPageBlob(uint8_t nsIndex, const char* key, const void* data, size_t dataSize, VerOffset chunkStart, const bool purgeAfterErase)
{
uint8_t chunkCount = 0;
TUsedPageList usedPages;
@@ -368,7 +368,7 @@ esp_err_t Storage::writeMultiPageBlob(uint8_t nsIndex, const char* key, const vo
/* Anything failed, then we should erase all the written chunks*/
int ii=0;
for(auto it = std::begin(usedPages); it != std::end(usedPages); it++) {
it->mPage->eraseItem(nsIndex, ItemType::BLOB_DATA, key, ii++);
it->mPage->eraseItem(nsIndex, ItemType::BLOB_DATA, key, purgeAfterErase, ii++);
}
}
usedPages.clearAndFreeNodes();
@@ -377,7 +377,7 @@ esp_err_t Storage::writeMultiPageBlob(uint8_t nsIndex, const char* key, const vo
// datatype BLOB is written as BLOB_INDEX and BLOB_DATA and is searched for previous value as BLOB_INDEX and/or BLOB
// datatype BLOB_INDEX and BLOB_DATA are not supported as input parameters, the layer above should always use BLOB
esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize)
esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize, const bool purgeAfterErase)
{
if(mState != StorageState::ACTIVE) {
return ESP_ERR_NVS_NOT_INITIALIZED;
@@ -488,7 +488,7 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key
= (prevStart == VerOffset::VER_1_OFFSET) ? VerOffset::VER_0_OFFSET : VerOffset::VER_1_OFFSET;
}
// Write the blob with new version
err = writeMultiPageBlob(nsIndex, key, data, dataSize, nextStart);
err = writeMultiPageBlob(nsIndex, key, data, dataSize, nextStart, purgeAfterErase);
if(err == ESP_ERR_NVS_PAGE_FULL) {
return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
@@ -543,7 +543,7 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key
// If the item found was BLOB_INDEX, the eraseMultiPageBlob is used to erase the whole multi-page blob.
// It is not necessary to check the potential page relocation as the function will find the blob again anyway.
VerOffset prevStart = item.blobIndex.chunkStart;
err = eraseMultiPageBlob(nsIndex, key, prevStart);
err = eraseMultiPageBlob(nsIndex, key, purgeAfterErase, prevStart);
if(err == ESP_ERR_FLASH_OP_FAIL) {
return ESP_ERR_NVS_REMOVE_FAILED;
@@ -583,7 +583,7 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key
}
// Page containing the old value is now refreshed. We can erase the old value.
err = findPage->eraseEntryAndSpan(itemIndex);
err = findPage->eraseEntryAndSpan(itemIndex, purgeAfterErase);
if(err == ESP_ERR_FLASH_OP_FAIL) {
return ESP_ERR_NVS_REMOVE_FAILED;
}
@@ -623,7 +623,7 @@ esp_err_t Storage::createOrOpenNamespace(const char* nsName, bool canCreate, uin
return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
}
auto err = writeItem(Page::NS_INDEX, ItemType::U8, nsName, &ns, sizeof(ns));
auto err = writeItem(Page::NS_INDEX, ItemType::U8, nsName, &ns, sizeof(ns), Page::DEFAULT_PURGE_AFTER_ERASE);
if(err != ESP_OK) {
return err;
}
@@ -696,7 +696,7 @@ esp_err_t Storage::readMultiPageBlob(uint8_t nsIndex, const char* key, void* dat
if(err == ESP_ERR_NVS_NOT_FOUND || err == ESP_ERR_NVS_INVALID_LENGTH) {
// cleanup if a chunk is not found or the size is inconsistent
eraseMultiPageBlob(nsIndex, key);
eraseMultiPageBlob(nsIndex, key, Page::DEFAULT_PURGE_AFTER_ERASE);
}
NVS_ASSERT_OR_RETURN(offset == dataSize, ESP_FAIL);
@@ -783,7 +783,7 @@ esp_err_t Storage::readItem(uint8_t nsIndex, ItemType datatype, const char* key,
}
esp_err_t Storage::eraseMultiPageBlob(uint8_t nsIndex, const char* key, VerOffset chunkStart)
esp_err_t Storage::eraseMultiPageBlob(uint8_t nsIndex, const char* key, const bool purgeAfterErase, VerOffset chunkStart)
{
if(mState != StorageState::ACTIVE) {
return ESP_ERR_NVS_NOT_INITIALIZED;
@@ -801,7 +801,7 @@ esp_err_t Storage::eraseMultiPageBlob(uint8_t nsIndex, const char* key, VerOffse
chunkCount = item.blobIndex.chunkCount;
// Erase the index first and make children blobs orphan
err = findPage->eraseEntryAndSpan(itemIndex);
err = findPage->eraseEntryAndSpan(itemIndex, purgeAfterErase);
if(err != ESP_OK) {
return err;
}
@@ -817,7 +817,7 @@ esp_err_t Storage::eraseMultiPageBlob(uint8_t nsIndex, const char* key, VerOffse
// Ignore potential error if the item is not found
err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item, Page::CHUNK_ANY, chunkStart, &itemIndex);
if(err == ESP_OK) {
err = findPage->eraseEntryAndSpan(itemIndex);
err = findPage->eraseEntryAndSpan(itemIndex, purgeAfterErase);
if(err != ESP_OK) {
return err;
}
@@ -838,7 +838,7 @@ esp_err_t Storage::eraseMultiPageBlob(uint8_t nsIndex, const char* key, VerOffse
if(err == ESP_ERR_NVS_NOT_FOUND) {
break;
} else if(err == ESP_OK) {
err = it->eraseEntryAndSpan(itemIndex);
err = it->eraseEntryAndSpan(itemIndex, purgeAfterErase);
// advance itemIndex to the next potential entry on the page
// findItem checks the consistency of the entry metadata so we can safely assume the span is non-zero
@@ -874,7 +874,7 @@ esp_err_t Storage::eraseMultiPageBlob(uint8_t nsIndex, const char* key, VerOffse
}
// Erase the entry
err = findPage->eraseEntryAndSpan(itemIndex);
err = findPage->eraseEntryAndSpan(itemIndex, purgeAfterErase);
if(err != ESP_OK) {
return err;
}
@@ -884,7 +884,7 @@ esp_err_t Storage::eraseMultiPageBlob(uint8_t nsIndex, const char* key, VerOffse
return ESP_OK;
}
esp_err_t Storage::eraseItem(uint8_t nsIndex, ItemType datatype, const char* key)
esp_err_t Storage::eraseItem(uint8_t nsIndex, ItemType datatype, const char* key, const bool purgeAfterErase)
{
if(mState != StorageState::ACTIVE) {
return ESP_ERR_NVS_NOT_INITIALIZED;
@@ -901,13 +901,13 @@ esp_err_t Storage::eraseItem(uint8_t nsIndex, ItemType datatype, const char* key
}
// If the item found is BLOB_IDX, the eraseMultiPageBlob is used to erase the whole multi-page blob.
if (item.datatype == ItemType::BLOB_IDX) {
return eraseMultiPageBlob(nsIndex, key, item.blobIndex.chunkStart);
return eraseMultiPageBlob(nsIndex, key, purgeAfterErase, item.blobIndex.chunkStart);
}
return findPage->eraseEntryAndSpan(itemIndex);
return findPage->eraseEntryAndSpan(itemIndex, purgeAfterErase);
}
esp_err_t Storage::eraseNamespace(uint8_t nsIndex)
esp_err_t Storage::eraseNamespace(uint8_t nsIndex, const bool purgeAfterErase)
{
if(mState != StorageState::ACTIVE) {
return ESP_ERR_NVS_NOT_INITIALIZED;
@@ -915,7 +915,7 @@ esp_err_t Storage::eraseNamespace(uint8_t nsIndex)
for(auto it = std::begin(mPageManager); it != std::end(mPageManager); ++it) {
while(true) {
auto err = it->eraseItem(nsIndex, ItemType::ANY, nullptr);
auto err = it->eraseItem(nsIndex, ItemType::ANY, nullptr, purgeAfterErase);
if(err == ESP_ERR_NVS_NOT_FOUND) {
break;
}
@@ -925,7 +925,20 @@ esp_err_t Storage::eraseNamespace(uint8_t nsIndex)
}
}
return ESP_OK;
}
esp_err_t Storage::purgeNamespace(uint8_t nsIndex){
if(mState != StorageState::ACTIVE) {
return ESP_ERR_NVS_NOT_INITIALIZED;
}
for(auto it = std::begin(mPageManager); it != std::end(mPageManager); ++it) {
auto err = it->purgeErasedItems(nsIndex);
if(err != ESP_OK) {
return err;
}
}
return ESP_OK;
}
esp_err_t Storage::findKey(const uint8_t nsIndex, const char* key, ItemType* datatype)
+11 -9
View File
@@ -69,7 +69,7 @@ public:
esp_err_t createOrOpenNamespace(const char* nsName, bool canCreate, uint8_t& nsIndex);
esp_err_t writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize);
esp_err_t writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize, const bool purgeAfterErase);
esp_err_t readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize);
@@ -77,12 +77,12 @@ public:
esp_err_t getItemDataSize(uint8_t nsIndex, ItemType datatype, const char* key, size_t& dataSize);
esp_err_t eraseItem(uint8_t nsIndex, ItemType datatype, const char* key);
esp_err_t eraseItem(uint8_t nsIndex, ItemType datatype, const char* key, const bool purgeAfterErase);
template<typename T>
esp_err_t writeItem(uint8_t nsIndex, const char* key, const T& value)
esp_err_t writeItem(uint8_t nsIndex, const char* key, const T& value, const bool purgeAfterErase)
{
return writeItem(nsIndex, itemTypeOf(value), key, &value, sizeof(value));
return writeItem(nsIndex, itemTypeOf(value), key, &value, sizeof(value), purgeAfterErase);
}
template<typename T>
@@ -91,12 +91,14 @@ public:
return readItem(nsIndex, itemTypeOf(value), key, &value, sizeof(value));
}
esp_err_t eraseItem(uint8_t nsIndex, const char* key)
esp_err_t eraseItem(uint8_t nsIndex, const char* key, const bool purgeAfterErase)
{
return eraseItem(nsIndex, ItemType::ANY, key);
return eraseItem(nsIndex, ItemType::ANY, key, purgeAfterErase);
}
esp_err_t eraseNamespace(uint8_t nsIndex);
esp_err_t eraseNamespace(uint8_t nsIndex, const bool purgeAfterErase);
esp_err_t purgeNamespace(uint8_t nsIndex);
const Partition *getPart() const
{
@@ -113,13 +115,13 @@ public:
return mPageManager.getBaseSector();
}
esp_err_t writeMultiPageBlob(uint8_t nsIndex, const char* key, const void* data, size_t dataSize, VerOffset chunkStart);
esp_err_t writeMultiPageBlob(uint8_t nsIndex, const char* key, const void* data, size_t dataSize, VerOffset chunkStart, const bool purgeAfterErase);
esp_err_t readMultiPageBlob(uint8_t nsIndex, const char* key, void* data, size_t dataSize);
esp_err_t cmpMultiPageBlob(uint8_t nsIndex, const char* key, const void* data, size_t dataSize);
esp_err_t eraseMultiPageBlob(uint8_t nsIndex, const char* key, VerOffset chunkStart = VerOffset::VER_ANY);
esp_err_t eraseMultiPageBlob(uint8_t nsIndex, const char* key, const bool purgeAfterErase, VerOffset chunkStart = VerOffset::VER_ANY);
void debugDump();