mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
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:
@@ -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());
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -71,7 +71,34 @@ A data type check is performed when reading a value. An error is returned if the
|
||||
Namespaces
|
||||
^^^^^^^^^^
|
||||
|
||||
To mitigate potential conflicts in key names between different components, NVS assigns each key-value pair to one of namespaces. Namespace names follow the same rules as key names, i.e., the maximum length is 15 characters. Furthermore, there can be no more than 254 different namespaces in one NVS partition. Namespace name is specified in the :cpp:func:`nvs_open` or :cpp:type:`nvs_open_from_partition` call. This call returns an opaque handle, which is used in subsequent calls to the ``nvs_get_*``, ``nvs_set_*``, and :cpp:func:`nvs_commit` functions. This way, a handle is associated with a namespace, and key names will not collide with same names in other namespaces. Please note that the namespaces with the same name in different NVS partitions are considered as separate namespaces.
|
||||
To mitigate potential conflicts in key names between different components, NVS assigns each key-value pair to one of namespaces. Namespace names follow the same rules as key names, i.e., the maximum length is 15 characters. Furthermore, there can be no more than 254 different namespaces in one NVS partition. Namespace name is specified in the :cpp:func:`nvs_open` or :cpp:type:`nvs_open_from_partition` call.
|
||||
|
||||
The open mode parameter controls the access level and security behavior:
|
||||
|
||||
- ``NVS_READONLY``: Read-only access. All write operations will be rejected.
|
||||
- ``NVS_READWRITE``: Standard read-write access. Erased data is marked as deleted but remains in flash.
|
||||
- ``NVS_READWRITE_PURGE``: Secure read-write access. Erased data is physically purged from flash memory.
|
||||
|
||||
This call returns an opaque handle, which is used in subsequent calls to the ``nvs_get_*``, ``nvs_set_*``, and :cpp:func:`nvs_commit` functions. This way, a handle is associated with a namespace, and key names will not collide with same names in other namespaces. Please note that the namespaces with the same name in different NVS partitions are considered as separate namespaces.
|
||||
|
||||
.. _data_purging_security:
|
||||
|
||||
Data Purging and Security
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
By default, when NVS updates or erases key-value pairs, the old data is only marked as erased but remains physically present in flash memory. This approach optimizes write performance and flash wear leveling, but the erased data could potentially be recovered with physical access to the flash chip.
|
||||
|
||||
For applications requiring enhanced security where sensitive data must be physically removed from flash memory, NVS provides two mechanisms:
|
||||
|
||||
**Purging Mode**
|
||||
When opening a namespace with ``NVS_READWRITE_PURGE`` mode, all write and erase operations automatically purge (physically wipe) the previously stored data in addition to writing new values. This ensures that old data cannot be recovered from flash memory.
|
||||
|
||||
**Manual Purging**
|
||||
The :cpp:func:`nvs_purge_all` function allows applications to explicitly purge all erased items within a namespace at any time. This can be used with handles opened in either ``NVS_READWRITE`` or ``NVS_READWRITE_PURGE`` mode.
|
||||
|
||||
.. note::
|
||||
|
||||
Purging operations require additional flash write cycles compared to standard erase operations. Applications should balance security requirements with flash wear considerations when deciding whether to use purging features.
|
||||
|
||||
NVS Iterators
|
||||
^^^^^^^^^^^^^
|
||||
@@ -102,6 +129,8 @@ Security, Tampering, and Robustness
|
||||
|
||||
If NVS encryption is not used, it is possible for anyone with physical access to the flash chip to alter, erase, or add key-value pairs. With NVS encryption enabled, it is not possible to alter or add a key-value pair and get recognized as a valid pair without knowing corresponding NVS encryption keys. However, there is no tamper-resistance against the erase operation.
|
||||
|
||||
**Data Recovery Prevention**: By default, when key-value pairs are updated or erased, the old data remains physically present in flash memory and is only marked as invalid. For sensitive applications where old data must not be recoverable, use the ``NVS_READWRITE_PURGE`` mode when opening namespaces, or call :cpp:func:`nvs_purge_all` to explicitly purge erased data. See the :ref:`Data Purging and Security <data_purging_security>` section for details.
|
||||
|
||||
The library does try to recover from conditions when flash memory is in an inconsistent state. In particular, one should be able to power off the device at any point and time and then power it back on. This should not result in loss of data, except for the new key-value pair if it was being written at the moment of powering off. The library should also be able to initialize properly with any random data present in flash memory.
|
||||
|
||||
|
||||
@@ -201,6 +230,10 @@ Log of Key-Value Pairs
|
||||
|
||||
NVS stores key-value pairs sequentially, with new key-value pairs being added at the end. When a value of any given key has to be updated, a new key-value pair is added at the end of the log and the old key-value pair is marked as erased.
|
||||
|
||||
**Standard Erase Behavior**: By default, erased entries remain physically present in flash memory with only their state bits changed to indicate they are no longer valid. This optimizes performance and reduces flash wear.
|
||||
|
||||
**Purge Behavior**: When using ``NVS_READWRITE_PURGE`` mode or calling :cpp:func:`nvs_purge_all`, the library physically overwrites the content of erased entries to prevent data recovery. This operation incurs additional flash write cycles but provides enhanced security for sensitive data.
|
||||
|
||||
Pages and Entries
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
@@ -69,9 +69,36 @@ NVS 的操作对象为键值对,其中键是 ASCII 字符串,当前支持的
|
||||
|
||||
|
||||
命名空间
|
||||
^^^^^^^^^^
|
||||
^^^^^^^^
|
||||
|
||||
为了减少不同组件之间键名的潜在冲突,NVS 将每个键值对分配给一个命名空间。命名空间的命名规则遵循键名的命名规则,例如,最多可占 15 个字符。此外,单个 NVS 分区最多只能容纳 254 个不同的命名空间。命名空间的名称在调用 :cpp:func:`nvs_open` 或 :cpp:type:`nvs_open_from_partition` 中指定,调用后将返回一个不透明句柄,用于后续调用 ``nvs_get_*``、``nvs_set_*`` 和 ``nvs_commit`` 函数。这样,一个句柄关联一个命名空间,键名便不会与其他命名空间中相同键名冲突。请注意,不同 NVS 分区中具有相同名称的命名空间将被视为不同的命名空间。
|
||||
为缓解不同组件间可能出现的键名冲突问题,NVS 将每个键值对分配到某个命名空间。命名空间的命名规则与键名相同,即最大长度为 15 个字符。此外,单个 NVS 分区中,最多支持 254 个不同的命名空间。调用 :cpp:func:`nvs_open` 或 :cpp:type:`nvs_open_from_partition` 可以指定命名空间名称。
|
||||
|
||||
open mode 参数控制访问级别和安全行为:
|
||||
|
||||
- ``NVS_READONLY``:只读访问权限。所有写操作将被拒绝。
|
||||
- ``NVS_READWRITE``:标准读写访问权限。被擦除的数据会被标记为已删除,但仍保留在 flash 中。
|
||||
- ``NVS_READWRITE_PURGE``:安全的读写访问权限。已擦除的数据将从 flash 中物理移除。
|
||||
|
||||
此调用返回一个不透明句柄,用于后续对 ``nvs_get_*``、``nvs_set_*`` 和 :cpp:func:`nvs_commit` 函数的调用。这样,句柄与某个命名空间关联,键名不会与其他命名空间中的同名键发生冲突。请注意,不同 NVS 分区中同名的命名空间被视为相互独立的命名空间。
|
||||
|
||||
.. _data_purging_security:
|
||||
|
||||
数据清除与安全
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
默认情况下,当 NVS 更新或擦除键值对时,旧数据被标记为已擦除,但实际仍存在于 flash 中。这种做法可以提升写入性能,并且有助于减轻 flash 芯片的磨损。但如果能够物理访问 flash 芯片,被擦除的数据仍可能被恢复。
|
||||
|
||||
对于需要更高安全性、必须将敏感数据从 flash 中物理删除的应用,NVS 提供了两种机制:
|
||||
|
||||
**清除模式**
|
||||
以 ``NVS_READWRITE_PURGE`` 模式打开命名空间时,所有写入和擦除操作在写入新值的同时会自动清除(物理擦除)先前存储的数据,确保旧数据无法从 flash 中恢复。
|
||||
|
||||
**手动清除**
|
||||
调用 :cpp:func:`nvs_purge_all` 函数,应用程序可以随时显式清理某个命名空间内所有已擦除的条目。可用于以 ``NVS_READWRITE`` 或 ``NVS_READWRITE_PURGE`` 模式打开的句柄。
|
||||
|
||||
.. note::
|
||||
|
||||
相较于标准擦除操作,清除操作会增加 flash 擦写次数。应用程序在决定是否使用清除功能时,应在安全需求与 flash 磨损之间进行权衡。
|
||||
|
||||
NVS 迭代器
|
||||
^^^^^^^^^^^^^
|
||||
@@ -102,6 +129,8 @@ NVS 迭代器
|
||||
|
||||
如果未启用 NVS 加密,任何对 flash 芯片有物理访问权限的用户都可以修改、擦除或添加键值对。NVS 加密启用后,如果不知道相应的 NVS 加密密钥,则无法修改或添加键值对并将其识别为有效键值对。但是,针对擦除操作没有相应的防篡改功能。
|
||||
|
||||
**防止数据恢复**:默认情况下,当键值对被更新或擦除时,旧数据实际仍然存在于 flash 中,仅被标记为无效。对于禁止恢复旧数据的敏感应用,打开命名空间时应使用 ``NVS_READWRITE_PURGE`` 模式,或调用 :cpp:func:`nvs_purge_all` 以显式清除已擦除的数据。详情参见 :ref:`数据清除与安全 <data_purging_security>` 部分。
|
||||
|
||||
当 flash 处于不一致状态时,NVS 库会尝试恢复。在任何时间点关闭设备电源,然后重新打开电源,不会导致数据丢失;但如果关闭设备电源时正在写入新的键值对,这一键值对可能会丢失。该库还应该能够在 flash 中存在任何随机数据的情况下正常初始化。
|
||||
|
||||
|
||||
@@ -201,6 +230,10 @@ ESP-IDF :example:`storage/nvs` 目录下提供了数个代码示例:
|
||||
|
||||
NVS 按顺序存储键值对,新的键值对添加在最后。因此,如需更新某一键值对,实际是在日志最后增加一对新的键值对,同时将旧的键值对标记为已擦除。
|
||||
|
||||
**标准擦除行为**:默认情况下,被擦除的条目实际仍存在于 flash 中,仅修改其状态位以表明不再有效。这样可以优化性能并减少 flash 磨损。
|
||||
|
||||
**清除行为**:使用 ``NVS_READWRITE_PURGE`` 模式或调用 :cpp:func:`nvs_purge_all` 时,NVS 库会对已擦除的条目内容进行物理覆盖,防止数据被恢复。此操作会增加 flash 擦写次数,但能为敏感数据提供更高的安全性。
|
||||
|
||||
页面和条目
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
Reference in New Issue
Block a user