diff --git a/components/nvs_flash/host_test/nvs_host_test/main/test_nvs.cpp b/components/nvs_flash/host_test/nvs_host_test/main/test_nvs.cpp index 4dcb315c45..796cd75bf2 100644 --- a/components/nvs_flash/host_test/nvs_host_test/main/test_nvs.cpp +++ b/components/nvs_flash/host_test/nvs_host_test/main/test_nvs.cpp @@ -549,8 +549,8 @@ TEST_CASE("readonly handle fails on writing", "[nvs]") TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); // first, creating namespace... TEST_ESP_OK(nvs_open("ro_ns", NVS_READWRITE, &handle_1)); @@ -577,14 +577,13 @@ TEST_CASE("nvs api tests", "[nvs]") const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) - TEST_ESP_ERR(nvs_open("namespace1", NVS_READWRITE, &handle_1), ESP_ERR_NVS_NOT_INITIALIZED); for (uint16_t i = NVS_FLASH_SECTOR; i < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; ++i) { f.erase(i); } TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); TEST_ESP_ERR(nvs_open("namespace1", NVS_READONLY, &handle_1), ESP_ERR_NVS_NOT_FOUND); @@ -645,11 +644,11 @@ TEST_CASE("deinit partition doesn't affect other partition's open handles", "[nv TEMPORARILY_DISABLED(f_other.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f_other.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); TEST_ESP_OK(nvs_open_from_partition(OTHER_PARTITION_NAME, "ns", NVS_READWRITE, &handle_1)); @@ -717,8 +716,8 @@ TEST_CASE("nvs iterators tests", "[nvs]") f.erase(i); } TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); nvs_iterator_t it; nvs_entry_info_t info; @@ -750,8 +749,7 @@ TEST_CASE("nvs iterators tests", "[nvs]") int count = 0; nvs_iterator_t it = nullptr; esp_err_t res = nvs_entry_find(part, name, type, &it); - for (count = 0; res == ESP_OK; count++) - { + for (count = 0; res == ESP_OK; count++) { res = nvs_entry_next(&it); } CHECK(res == ESP_ERR_NVS_NOT_FOUND); // after finishing the loop or if no entry was found to begin with, @@ -765,8 +763,7 @@ TEST_CASE("nvs iterators tests", "[nvs]") int count = 0; nvs_iterator_t it = nullptr; esp_err_t res = nvs_entry_find_in_handle(handle, type, &it); - for (count = 0; res == ESP_OK; count++) - { + for (count = 0; res == ESP_OK; count++) { res = nvs_entry_next(&it); } CHECK(res == ESP_ERR_NVS_NOT_FOUND); // after finishing the loop or if no entry was found to begin with, @@ -820,7 +817,6 @@ TEST_CASE("nvs iterators tests", "[nvs]") CHECK(entry_count(NVS_DEFAULT_PART_NAME, NULL, NVS_TYPE_U64) == 1); } - SECTION("Number of entries found for specified handle and type is correct") { CHECK(entry_count_handle(handle_1, NVS_TYPE_ANY) == 11); CHECK(entry_count_handle(handle_1, NVS_TYPE_I32) == 3); @@ -884,14 +880,13 @@ TEST_CASE("nvs iterators tests", "[nvs]") nvs_release_iterator(it); } - SECTION("Iterating over multiple pages works correctly") { nvs_handle_t handle_3; const char *name_3 = "namespace3"; const int entries_created = 250; TEST_ESP_OK(nvs_open(name_3, NVS_READWRITE, &handle_3)); - for (size_t i = 0; i < entries_created; i++) { + for (size_t i = 0; i < entries_created; i++) { TEST_ESP_OK(nvs_set_u8(handle_3, to_string(i).c_str(), 123)); } @@ -948,8 +943,8 @@ TEST_CASE("Iterator with not matching type iterates correctly", "[nvs]") f.erase(i); } TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); // writing string to namespace (a type which spans multiple entries) TEST_ESP_OK(nvs_open(NAMESPACE, NVS_READWRITE, &my_handle)); @@ -962,8 +957,8 @@ TEST_CASE("Iterator with not matching type iterates correctly", "[nvs]") // re-init to trigger cleaning up of broken items -> a corrupted string will be erased TEST_ESP_OK(nvs_flash_deinit()); TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); TEST_ESP_OK(nvs_entry_find(NVS_DEFAULT_PART_NAME, NAMESPACE, NVS_TYPE_STR, &it)); nvs_release_iterator(it); @@ -981,8 +976,8 @@ TEST_CASE("wifi test", "[nvs]") const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); nvs_handle_t misc_handle; TEST_ESP_OK(nvs_open("nvs.net80211", NVS_READWRITE, &misc_handle)); @@ -1234,8 +1229,7 @@ public: static_assert(nKeys == sizeof(future_values) / sizeof(future_values[0]), ""); auto randomRead = [&](size_t index) -> esp_err_t { - switch (types[index]) - { + switch (types[index]) { case nvs::ItemType::I32: { int32_t val; auto err = nvs_get_i32(handle, keys[index], &val); @@ -1282,8 +1276,7 @@ public: }; auto randomWrite = [&](size_t index) -> esp_err_t { - switch (types[index]) - { + switch (types[index]) { case nvs::ItemType::I32: { int32_t val = static_cast(gen()); @@ -1421,7 +1414,7 @@ public: return ESP_OK; } - esp_err_t handleExternalWriteAtIndex(uint8_t index, const void *value, const size_t len ) + esp_err_t handleExternalWriteAtIndex(uint8_t index, const void *value, const size_t len) { if (index == 9) { /* This is only done for small-page blobs for now*/ if (len > smallBlobLen) { @@ -1452,8 +1445,8 @@ TEST_CASE("monkey test", "[nvs][monkey]") TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); nvs_handle_t handle; TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); @@ -1473,8 +1466,8 @@ TEST_CASE("test for memory leaks in open/set", "[leaks]") const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); for (int i = 0; i < 100000; ++i) { nvs_handle_t light_handle = 0; @@ -1527,22 +1520,22 @@ TEST_CASE("nvs_flash_init checks for an empty page", "[nvs]") const size_t blob_size = nvs::Page::CHUNK_MAX_SIZE; uint8_t blob[blob_size] = {0}; PartitionEmulationFixture f(0, 8); - TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5) ); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5)); nvs_handle_t handle; - TEST_ESP_OK( nvs_open("test", NVS_READWRITE, &handle) ); + TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); // Fill first page - TEST_ESP_OK( nvs_set_blob(handle, "1a", blob, blob_size) ); + TEST_ESP_OK(nvs_set_blob(handle, "1a", blob, blob_size)); // Fill second page - TEST_ESP_OK( nvs_set_blob(handle, "2a", blob, blob_size) ); + TEST_ESP_OK(nvs_set_blob(handle, "2a", blob, blob_size)); // Fill third page - TEST_ESP_OK( nvs_set_blob(handle, "3a", blob, blob_size) ); - TEST_ESP_OK( nvs_commit(handle) ); + TEST_ESP_OK(nvs_set_blob(handle, "3a", blob, blob_size)); + TEST_ESP_OK(nvs_commit(handle)); nvs_close(handle); TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); // first two pages are now full, third one is writable, last two are empty // init should fail - TEST_ESP_ERR( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3), - ESP_ERR_NVS_NO_FREE_PAGES ); + TEST_ESP_ERR(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3), + ESP_ERR_NVS_NO_FREE_PAGES); // in case this test fails, to not affect other tests nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME); @@ -1553,19 +1546,19 @@ TEST_CASE("nvs page selection takes into account free entries also not just eras const size_t blob_size = nvs::Page::CHUNK_MAX_SIZE / 2; uint8_t blob[blob_size] = {0}; PartitionEmulationFixture f(0, 3); - TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3) ); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); nvs_handle_t handle; - TEST_ESP_OK( nvs_open("test", NVS_READWRITE, &handle) ); + TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); // Fill first page - TEST_ESP_OK( nvs_set_blob(handle, "1a", blob, blob_size / 3) ); - TEST_ESP_OK( nvs_set_blob(handle, "1b", blob, blob_size) ); + TEST_ESP_OK(nvs_set_blob(handle, "1a", blob, blob_size / 3)); + TEST_ESP_OK(nvs_set_blob(handle, "1b", blob, blob_size)); // Fill second page - TEST_ESP_OK( nvs_set_blob(handle, "2a", blob, blob_size) ); - TEST_ESP_OK( nvs_set_blob(handle, "2b", blob, blob_size) ); + TEST_ESP_OK(nvs_set_blob(handle, "2a", blob, blob_size)); + TEST_ESP_OK(nvs_set_blob(handle, "2b", blob, blob_size)); // The item below should be able to fit the first page. - TEST_ESP_OK( nvs_set_blob(handle, "3a", blob, 4) ); - TEST_ESP_OK( nvs_commit(handle) ); + TEST_ESP_OK(nvs_set_blob(handle, "3a", blob, 4)); + TEST_ESP_OK(nvs_commit(handle)); nvs_close(handle); TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); @@ -1813,21 +1806,21 @@ TEST_CASE("Modification of values for Multi-page blobs are supported", "[nvs]") uint8_t blob4[blob_size] = { 0x33}; size_t read_size = blob_size; PartitionEmulationFixture f(0, 6); - TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 6) ); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 6)); nvs_handle_t handle; memset(blob, 0x11, blob_size); memset(blob2, 0x22, blob_size); memset(blob3, 0x33, blob_size); memset(blob4, 0x44, blob_size); memset(blob_read, 0xff, blob_size); - TEST_ESP_OK( nvs_open("test", NVS_READWRITE, &handle) ); - TEST_ESP_OK( nvs_set_blob(handle, "abc", blob, blob_size) ); - TEST_ESP_OK( nvs_set_blob(handle, "abc", blob2, blob_size) ); - TEST_ESP_OK( nvs_set_blob(handle, "abc", blob3, blob_size) ); - TEST_ESP_OK( nvs_set_blob(handle, "abc", blob4, blob_size) ); - TEST_ESP_OK( nvs_get_blob(handle, "abc", blob_read, &read_size)); + TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size)); + TEST_ESP_OK(nvs_set_blob(handle, "abc", blob2, blob_size)); + TEST_ESP_OK(nvs_set_blob(handle, "abc", blob3, blob_size)); + TEST_ESP_OK(nvs_set_blob(handle, "abc", blob4, blob_size)); + TEST_ESP_OK(nvs_get_blob(handle, "abc", blob_read, &read_size)); CHECK(memcmp(blob4, blob_read, blob_size) == 0); - TEST_ESP_OK( nvs_commit(handle) ); + TEST_ESP_OK(nvs_commit(handle)); nvs_close(handle); TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); @@ -1840,14 +1833,14 @@ TEST_CASE("Modification from single page blob to multi-page", "[nvs]") uint8_t blob_read[blob_size] = {0xff}; size_t read_size = blob_size; PartitionEmulationFixture f(0, 5); - TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5) ); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5)); nvs_handle_t handle; - TEST_ESP_OK(nvs_open("Test", NVS_READWRITE, &handle) ); + TEST_ESP_OK(nvs_open("Test", NVS_READWRITE, &handle)); TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, nvs::Page::CHUNK_MAX_SIZE / 2)); TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size)); TEST_ESP_OK(nvs_get_blob(handle, "abc", blob_read, &read_size)); CHECK(memcmp(blob, blob_read, blob_size) == 0); - TEST_ESP_OK(nvs_commit(handle) ); + TEST_ESP_OK(nvs_commit(handle)); nvs_close(handle); TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); @@ -1860,15 +1853,15 @@ TEST_CASE("Modification from multi-page to single page", "[nvs]") uint8_t blob_read[blob_size] = {0xff}; size_t read_size = blob_size; PartitionEmulationFixture f(0, 5); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5) ); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5)); nvs_handle_t handle; - TEST_ESP_OK(nvs_open("Test", NVS_READWRITE, &handle) ); + TEST_ESP_OK(nvs_open("Test", NVS_READWRITE, &handle)); TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size)); TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, nvs::Page::CHUNK_MAX_SIZE / 2)); TEST_ESP_OK(nvs_set_blob(handle, "abc2", blob, blob_size)); TEST_ESP_OK(nvs_get_blob(handle, "abc", blob_read, &read_size)); CHECK(memcmp(blob, blob_read, nvs::Page::CHUNK_MAX_SIZE) == 0); - TEST_ESP_OK(nvs_commit(handle) ); + TEST_ESP_OK(nvs_commit(handle)); nvs_close(handle); TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); @@ -1905,7 +1898,6 @@ TEST_CASE("Check that orphaned blobs are erased during init", "[nvs]") TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::BLOB, "key", blob, sizeof(blob))); - TEST_ESP_OK(storage.init(0, 5)); /* Check that multi-page item is still available.**/ TEST_ESP_OK(storage.readItem(1, nvs::ItemType::BLOB, "key", blob, sizeof(blob))); @@ -1925,21 +1917,21 @@ TEST_CASE("Check that orphaned blobs are erased during init", "[nvs]") TEST_CASE("nvs blob fragmentation test", "[nvs]") { PartitionEmulationFixture f(0, 4); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 4) ); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 4)); const size_t BLOB_SIZE = 3500; uint8_t *blob = (uint8_t *) malloc(BLOB_SIZE); CHECK(blob != NULL); memset(blob, 0xEE, BLOB_SIZE); const uint32_t magic = 0xff33eaeb; nvs_handle_t h; - TEST_ESP_OK( nvs_open("blob_tests", NVS_READWRITE, &h) ); + TEST_ESP_OK(nvs_open("blob_tests", NVS_READWRITE, &h)); for (int i = 0; i < 128; i++) { INFO("Iteration " << i << "...\n"); - TEST_ESP_OK( nvs_set_u32(h, "magic", magic) ); - TEST_ESP_OK( nvs_set_blob(h, "blob", blob, BLOB_SIZE) ); + TEST_ESP_OK(nvs_set_u32(h, "magic", magic)); + TEST_ESP_OK(nvs_set_blob(h, "blob", blob, BLOB_SIZE)); char seq_buf[16]; snprintf(seq_buf, sizeof(seq_buf), "seq%d", i); - TEST_ESP_OK( nvs_set_u32(h, seq_buf, i) ); + TEST_ESP_OK(nvs_set_u32(h, seq_buf, i)); } free(blob); @@ -2004,14 +1996,14 @@ TEST_CASE("monkey test with old-format blob present", "[nvs][monkey]") TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); nvs_handle_t handle; TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); RandomTest test; - for ( uint8_t it = 0; it < 10; it++) { + for (uint8_t it = 0; it < 10; it++) { size_t count = 200; /* Erase index and chunks for the blob with "singlepage" key, do not care about errorcodes */ @@ -2050,8 +2042,8 @@ TEST_CASE("monkey test with old-format blob present", "[nvs][monkey]") TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); /* Initialize again */ TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); /* Perform random things */ @@ -2127,7 +2119,7 @@ TEST_CASE("Recovery from power-off during modification of blob present in old-fo TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); - TEST_ESP_OK( nvs_get_blob(handle, "singlepage", buf, &buflen)); + TEST_ESP_OK(nvs_get_blob(handle, "singlepage", buf, &buflen)); CHECK(memcmp(buf, hexdata, buflen) == 0); nvs::Page p2; @@ -2157,7 +2149,6 @@ TEST_CASE("Recovery from power-off during modification of blob present in old-fo size_t buflen = sizeof(hexdata); uint8_t buf[nvs::Page::CHUNK_MAX_SIZE]; - /* Power-off when blob was being written on the different page where its old version in old format * was present*/ nvs::Page p; @@ -2186,7 +2177,7 @@ TEST_CASE("Recovery from power-off during modification of blob present in old-fo TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); - TEST_ESP_OK( nvs_get_blob(handle, "singlepage", buf, &buflen)); + TEST_ESP_OK(nvs_get_blob(handle, "singlepage", buf, &buflen)); CHECK(memcmp(buf, hexdata, buflen) == 0); nvs::Page p3; @@ -2229,9 +2220,9 @@ TEST_CASE("namespace name is deep copy", "[nvs]") TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) - TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); TEST_ESP_OK(nvs_open("const_name", NVS_READWRITE, &handle_1)); strcpy(ns_name, "just_kidding"); @@ -2261,22 +2252,22 @@ TEST_CASE("multiple partitions access check", "[nvs]") NVS_FLASH_PARTITION2 ); - TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR_BEGIN1, - NVS_FLASH_SECTOR_SIZE1)); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), + NVS_FLASH_SECTOR_BEGIN1, + NVS_FLASH_SECTOR_SIZE1)); - TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part2(), - NVS_FLASH_SECTOR_BEGIN2, - NVS_FLASH_SECTOR_SIZE2)); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part2(), + NVS_FLASH_SECTOR_BEGIN2, + NVS_FLASH_SECTOR_SIZE2)); nvs_handle_t handle1, handle2; - TEST_ESP_OK( nvs_open_from_partition("nvs1", "test", NVS_READWRITE, &handle1) ); - TEST_ESP_OK( nvs_open_from_partition("nvs2", "test", NVS_READWRITE, &handle2) ); - TEST_ESP_OK( nvs_set_i32(handle1, "foo", 0xdeadbeef)); - TEST_ESP_OK( nvs_set_i32(handle2, "foo", 0xcafebabe)); + TEST_ESP_OK(nvs_open_from_partition("nvs1", "test", NVS_READWRITE, &handle1)); + TEST_ESP_OK(nvs_open_from_partition("nvs2", "test", NVS_READWRITE, &handle2)); + TEST_ESP_OK(nvs_set_i32(handle1, "foo", 0xdeadbeef)); + TEST_ESP_OK(nvs_set_i32(handle2, "foo", 0xcafebabe)); int32_t v1, v2; - TEST_ESP_OK( nvs_get_i32(handle1, "foo", &v1)); - TEST_ESP_OK( nvs_get_i32(handle2, "foo", &v2)); + TEST_ESP_OK(nvs_get_i32(handle1, "foo", &v1)); + TEST_ESP_OK(nvs_get_i32(handle2, "foo", &v2)); CHECK(v1 == 0xdeadbeef); CHECK(v2 == 0xcafebabe); TEST_ESP_OK(nvs_flash_deinit_partition(NVS_FLASH_PARTITION1)); @@ -2291,8 +2282,8 @@ TEST_CASE("writing the identical content does not write or erase", "[nvs]") const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 10; TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); nvs_handle_t misc_handle; TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &misc_handle)); @@ -2350,8 +2341,8 @@ TEST_CASE("can init storage from flash with random contents", "[nvs]") const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); TEST_ESP_OK(nvs_open("nvs.net80211", NVS_READWRITE, &handle)); @@ -2368,7 +2359,7 @@ TEST_CASE("nvs api tests, starting with random data in flash", "[nvs][long][.]") const size_t testIters = 3000; int lastPercent = -1; for (size_t count = 0; count < testIters; ++count) { - int percentDone = (int) (count * 100 / testIters); + int percentDone = (int)(count * 100 / testIters); if (percentDone != lastPercent) { lastPercent = percentDone; printf("%d%%\n", percentDone); @@ -2381,8 +2372,8 @@ TEST_CASE("nvs api tests, starting with random data in flash", "[nvs][long][.]") TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); nvs_handle_t handle_1; TEST_ESP_ERR(nvs_open("namespace1", NVS_READONLY, &handle_1), ESP_ERR_NVS_NOT_FOUND); @@ -2458,8 +2449,8 @@ TEST_CASE("test recovery from sudden poweroff", "[long][nvs][recovery][monkey][. size_t count = iter_count; if (nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN) == ESP_OK) { + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN) == ESP_OK) { esp_err_t res = ESP_ERR_FLASH_OP_FAIL; if (nvs_open("namespace1", NVS_READWRITE, &handle) == ESP_OK) { res = test.doRandomThings(handle, gen, count, errDelay); @@ -2475,8 +2466,8 @@ TEST_CASE("test recovery from sudden poweroff", "[long][nvs][recovery][monkey][. } TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); esp_err_t res; @@ -2634,6 +2625,499 @@ TEST_CASE("crc error in variable length item is handled", "[nvs]") } } +// handle damaged item header's span=0 even if crc is correct +TEST_CASE("zero span in item header with correct crc is handled", "[nvs]") +{ + PartitionEmulationFixture f(0, 3); + nvs::Storage storage(f.part()); + // prepare some data + TEST_ESP_OK(storage.init(0, 3)); + TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast(1))); + TEST_ESP_OK(storage.writeItem(1, "value1", static_cast(1))); + TEST_ESP_OK(storage.writeItem(1, "value2", static_cast(2))); + + // damage item header of value1 to introduce span==0 error, recalculate crc + { + uint8_t new_span = 0; + size_t entry_offset = 32 * 3; // 2x page header + 1x ns1 + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + + nvs::Item* item = (nvs::Item*)buff; + + // set span to 0 + item->span = new_span; + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + + // allow overwriting smaller portion of flash than whole 4k page + ((esp_partition_t*) f.get_esp_partition())->erase_size = 1; + TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); + TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + } + + // check that storage can recover + uint32_t val = 0; + + TEST_ESP_OK(storage.init(0, 3)); + TEST_ESP_OK(storage.readItem(1, "value2", val)); + CHECK(val == 2); + // check that the damaged item is no longer present + TEST_ESP_ERR(ESP_ERR_NVS_NOT_FOUND, storage.readItem(1, "value1", val)); + + // add more items named "item_0" till "item_125" to make the page full + 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(i))); + } + + // damage item header of item_125 to introduce span==0 error, recalculate crc + { + uint8_t new_span = 0; + size_t entry_offset = 32 * (2 + 1 + 1 + 128) ; // 2x page header + 1x ns1 + 1x value1 + whole page + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + + nvs::Item* item = (nvs::Item*)buff; + // set span to 0 + item->span = new_span; + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + + // allow overwriting smaller portion of flash than whole 4k page + ((esp_partition_t*) f.get_esp_partition())->erase_size = 1; + TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); + TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + } + + // check that storage can recover + TEST_ESP_OK(storage.init(0, 3)); + // check that the damaged item is no longer present + TEST_ESP_ERR(ESP_ERR_NVS_NOT_FOUND, storage.readItem(1, "item_125", val)); +} + +// test case for damaged item header with correct crc using string. +// first sub-case, span goes over the remaining entries in the page +// second sub-case, span goes over the number of entries required to store the string +// third sub-case, span goes below the number of entries required to store the string +// fourth sub-case, indicated variable data length goes over the remaining space in the page +TEST_CASE("inconsistent fields in item header with correct crc are handled for string", "[nvs]") +{ + PartitionEmulationFixture f(0, 3); + nvs::Storage storage(f.part()); + // prepare some data + TEST_ESP_OK(storage.init(0, 3)); + TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast(1))); + 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(2))); + + // read the values back + char read_str[sizeof(str)] = {0}; + size_t read_str_size = sizeof(read_str); + uint32_t val = 0; + + TEST_ESP_OK(storage.readItem(1, nvs::ItemType::SZ, "valuestr1", (void*)read_str, read_str_size)); + CHECK(strcmp(read_str, str) == 0); + TEST_ESP_OK(storage.readItem(1, nvs::ItemType::SZ, "valuestr2", (void*)read_str, read_str_size)); + CHECK(strcmp(read_str, str) == 0); + TEST_ESP_OK(storage.readItem(1, nvs::ItemType::SZ, "valuestr3", (void*)read_str, read_str_size)); + CHECK(strcmp(read_str, str) == 0); + TEST_ESP_OK(storage.readItem(1, nvs::ItemType::SZ, "valuestr4", (void*)read_str, read_str_size)); + CHECK(strcmp(read_str, str) == 0); + + // default values for re-check after sections of the test + esp_err_t exp_err_str1 = ESP_OK; + esp_err_t exp_err_str2 = ESP_OK; + esp_err_t exp_err_str3 = ESP_OK; + esp_err_t exp_err_str4 = ESP_OK; + + TEST_ESP_OK(storage.readItem(1, "valueu32", val)); + CHECK(val == 2); + + // allow overwriting smaller portion of flash than whole 4k page + ((esp_partition_t*) f.get_esp_partition())->erase_size = 1; + + SECTION("damage item header of valuestr1 to introduce span exceeding remaining page size error") { + // span of the valstr1 is 4, there are 126 - 1 = 125 entries left in the page, set damaged span to 126 + uint8_t new_span = 126; + + const uint8_t entry_index = 3; // 2x page header + 1x ns1 + + size_t entry_offset = 32 * entry_index; + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + + nvs::Item* item = (nvs::Item*)buff; + + item->span = new_span; + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + + TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); + TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + + // expect error when trying to read the item + exp_err_str1 = ESP_ERR_NVS_NOT_FOUND; + } + + SECTION("damage item header of valuestr2 to introduce span exceeding the number of entries required to store the string") { + // span of the valstr2 is 4, there are 126 - (1 + 4) = 121 entries left in the page, set damaged span to value below 121 and above 4 + uint8_t new_span = 10; + + const uint8_t entry_index = 7; // 2x page header + 1x ns1 + 4x valuestr1 + + size_t entry_offset = 32 * entry_index; + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + + nvs::Item* item = (nvs::Item*)buff; + + item->span = new_span; + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + + TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); + TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + + // expect error when trying to read the item + exp_err_str2 = ESP_ERR_NVS_NOT_FOUND; + } + + SECTION("damage item header of valuestr3 to introduce span lower than the number of entries required to store the string") { + // span of the valstr3 is 4, set to 3 + uint8_t new_span = 3; + + const uint8_t entry_index = 11; // 2x page header + 1x ns1 + 4x valuestr1 + 4x valuestr2 + + size_t entry_offset = 32 * entry_index; + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + + nvs::Item* item = (nvs::Item*)buff; + + item->span = new_span; + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + + TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); + TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + + // expect error when trying to read the item + exp_err_str3 = ESP_ERR_NVS_NOT_FOUND; + } + + SECTION("damage item header of valuestr4 to introduce indicated variable data length going over the remaining space in the page") { + const uint8_t entry_index = 15; // 2x page header + 1x ns1 + 4x valuestr1 + 4x valuestr2 + 4x valuestr3 + + size_t entry_offset = 32 * entry_index; + // we are at entry 15, there are 126 - 15 = 111 entries left in the page, set data size to 112 * 32 bytes + uint16_t new_size = 112 * 32; + + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + + nvs::Item* item = (nvs::Item*)buff; + + item->varLength.dataSize = new_size; + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + + TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); + TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + + // expect error when trying to read the item + exp_err_str4 = ESP_ERR_NVS_NOT_FOUND; + } + + // check that storage can recover + + TEST_ESP_OK(storage.init(0, 3)); + TEST_ESP_OK(storage.readItem(1, "valueu32", val)); + CHECK(val == 2); + + // check that the damaged items are no longer present + char read_buff[sizeof(str)]; + TEST_ESP_ERR(exp_err_str1, storage.readItem(1, nvs::ItemType::SZ, "valuestr1", read_buff, sizeof(read_buff))); + TEST_ESP_ERR(exp_err_str2, storage.readItem(1, nvs::ItemType::SZ, "valuestr2", read_buff, sizeof(read_buff))); + TEST_ESP_ERR(exp_err_str3, storage.readItem(1, nvs::ItemType::SZ, "valuestr3", read_buff, sizeof(read_buff))); + TEST_ESP_ERR(exp_err_str4, storage.readItem(1, nvs::ItemType::SZ, "valuestr4", read_buff, sizeof(read_buff))); +} + +// Inconsistent fields in item header with correct crc are handled for blobs + +// Before each sub case damaging the blob with key = "valueblob1", following scheme will be used to store the blob and do the test +// Page 1 +// 1x namespace entry +// 1x U32 entry, key = "valueu32_1" before data +// 124x BLOB_DATA entries, key = "valueblob1", chunk index = 0 containing 123*32 bytes of data + +// Page 2 +// 3x BLOB_DATA entries, key = "valueblob1", chunk index = 1 containing 2*32 bytes of data +// 1x BLOB_INDEX entry key = "valueblob1", chunkVersion = 0, chunkCount = 2 +// 3x BLOB_DATA entries, key = "valueblob2", chunk index = 0 containing 2*32 bytes of data +// 1x BLOB_INDEX entry key = "valueblob2", chunkVersion = 0, chunkCount = 1 +// 1x U32 entry key = "valueu32_2" after data +TEST_CASE("inconsistent fields in item header with correct crc are handled for multi page blob", "[nvs]") +{ + PartitionEmulationFixture f(0, 3); + + // keys + char ukey1[] = "valueu32_1"; + char ukey2[] = "valueu32_2"; + char blob_key1[] = "valueblob1"; + char blob_key2[] = "valueblob2"; + + // planned chunk lengths + size_t blob_key1_chunk_0_len = 123 * 32; + size_t blob_key1_chunk_1_len = 2 * 32; + size_t blob_key2_chunk_len = 2 * 32; + + // initial values + uint32_t uval1 = 1; + uint32_t uval2 = 2; + uint8_t blob_data1[blob_key1_chunk_0_len + blob_key1_chunk_1_len]; + uint8_t blob_data2[blob_key2_chunk_len]; + + // value buffers + uint32_t read_u32_1; + uint32_t read_u32_2; + uint8_t read_blob1[sizeof(blob_data1)]; + uint8_t read_blob2[sizeof(blob_data2)]; + size_t read_blob1_size = sizeof(read_blob1); + size_t read_blob2_size = sizeof(read_blob2); + + // Skip one page, 2 entries of page header and 3 entries of valueblob1's BLOB_DATA entries + const size_t blob_index_offset = 4096 + 32 * 2 + 32 * 3; + + // Skip 2 entries of page header 1 entry of namespace and 1 entry of valueu32_1 + const size_t blob_data_chunk0_offset = 32 * 2 + 32 * 1 + 32 * 1; + + // returns length of BLOB_DATA of blob_key1 on page 1 within the partition + // Skip 2 entries of page header 1 entry of namespace and 1 entry of valueu32_1 + const uint16_t blob_data_chunk0_len = blob_key1_chunk_0_len; + + // returns length of BLOB_DATA of blob_key1 on page 1 within the partition + // Skip 2 entries of page header 1 entry of namespace and 1 entry of valueu32_1 + const uint16_t blob_data_chunk1_len = blob_key1_chunk_1_len; + + // returns offset of BLOB_DATA of blob_key1 on page 1 within the partition + // Skip one page, 2 entries of page header + const size_t blob_data_chunk1_offset = 4096 + 32 * 2; + + // initialize buffers + read_u32_1 = 0; + read_u32_2 = 0; + memset(blob_data1, 0x55, sizeof(blob_data1)); + memset(blob_data2, 0xaa, sizeof(blob_data2)); + memset(read_blob1, 0, sizeof(read_blob1)); + memset(read_blob2, 0, sizeof(read_blob2)); + + // Write initial data to the nvs partition + { + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); + + nvs_handle_t handle; + TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_set_u32(handle, ukey1, uval1)); + TEST_ESP_OK(nvs_set_blob(handle, blob_key1, blob_data1, sizeof(blob_data1))); + TEST_ESP_OK(nvs_set_blob(handle, blob_key2, blob_data2, sizeof(blob_data2))); + TEST_ESP_OK(nvs_set_u32(handle, ukey2, uval2)); + nvs_close(handle); + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + } + + // during the test, we will make changes of the data in more granular way than the whole 4k page + // allow overwriting smaller portion of flash than whole 4k page + ((esp_partition_t*) f.get_esp_partition())->erase_size = 1; + + // Sub test cases + SECTION("damage BLOB_IDX - span set to 0") { + // Damage BLOB_IDX of valueblob1, modify span == 1 to 0 + size_t entry_offset = blob_index_offset; + uint8_t expected_span = 1; + uint8_t new_span = 0; + + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + nvs::Item* item = (nvs::Item*)buff; + + CHECK(item->span == expected_span); + item->span = new_span; + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); + TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + } + + SECTION("damage BLOB_IDX - chunkIndex set to 111 instead of CHUNK_ANY") { + // Damage BLOB_IDX of valueblob1, modify chunkIndex == 111 + size_t entry_offset = blob_index_offset; + uint8_t expected_chunk_index = nvs::Item::CHUNK_ANY; + uint8_t new_chunk_index = 111; + + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + nvs::Item* item = (nvs::Item*)buff; + + CHECK(item->chunkIndex == expected_chunk_index); + item->chunkIndex = new_chunk_index; + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); + TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + } + + SECTION("damage BLOB_IDX - blobIndex.dataSize over theoretical limit of 32*125*127 (entry size * max payload entries in page * max # of chunks)") { + // Damage BLOB_IDX of valueblob1, modify blobIndex.dataSize + size_t entry_offset = blob_index_offset; + uint32_t expected_data_size = blob_data_chunk0_len + blob_data_chunk1_len; + uint32_t new_data_size = (32 * 125 * 127) + 1; + + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + nvs::Item* item = (nvs::Item*)buff; + + CHECK(item->blobIndex.dataSize == expected_data_size); + item->chunkIndex = new_data_size; + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); + TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + } + + SECTION("damage BLOB_DATA - chunkIndex set to invalid value of CHUNK_ANY") { + // Damage BLOB_DATA of valueblob1, modify chunkIndex to CHUNK_ANY + size_t entry_offset = blob_data_chunk0_offset; + uint8_t unexpected_chunk_index = nvs::Item::CHUNK_ANY; + uint8_t new_chunk_index = nvs::Item::CHUNK_ANY; + + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + nvs::Item* item = (nvs::Item*)buff; + + CHECK(item->chunkIndex != unexpected_chunk_index); + item->chunkIndex = new_chunk_index; + + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); + TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + } + + SECTION("damage BLOB_DATA - varLength.dataSize is exceeding the maximum capacity available till the end of the page") { + // Damage BLOB_DATA of valueblob1, set new data size to be beyond remaining capacity in the page. + // Even if all entries are used for payload, there will be one overhead entry required + size_t entry_offset = blob_data_chunk1_offset; // let's take chunk1 for this test + uint16_t expected_data_size = blob_data_chunk1_len; + uint16_t new_data_size = 32 * 126; + + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + nvs::Item* item = (nvs::Item*)buff; + + CHECK(item->varLength.dataSize == expected_data_size); + item->varLength.dataSize = new_data_size; + + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); + TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + } + + SECTION("damage BLOB_DATA - span > maxAvailablePageSpan") { + // Damage BLOB_DATA of valueblob1, set span to the value larger than the rest of entries in the page but smaller than maximum of entries . + // page can accommodate + // chunk0 starts at entry index 2 so max span is 126 - 2 = 124. Use 125 + size_t entry_offset = blob_data_chunk0_offset; + uint8_t expected_span = (blob_data_chunk0_len + 31) / 32 + 1; + uint8_t new_span = 125; + + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + nvs::Item* item = (nvs::Item*)buff; + + CHECK(item->span == expected_span); + item->span = new_span; + + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); + TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + } + + // check that storage can recover and validate the remaining data + { + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); + + nvs_handle_t handle; + TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_get_u32(handle, ukey1, &read_u32_1)); + TEST_ESP_ERR(nvs_get_blob(handle, blob_key1, &read_blob1, &read_blob1_size), ESP_ERR_NVS_NOT_FOUND); + TEST_ESP_OK(nvs_get_blob(handle, blob_key2, &read_blob2, &read_blob2_size)); + TEST_ESP_OK(nvs_get_u32(handle, ukey2, &read_u32_2)); + nvs_close(handle); + TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + + // validate the data + CHECK(read_u32_1 == uval1); + CHECK(read_u32_2 == uval2); + CHECK(memcmp(read_blob2, blob_data2, sizeof(blob_data2)) == 0); + } +} + +// header entry of u32 entry will be damaged in a way that data type will be invalid +// while crc will be correct +TEST_CASE("invalid data type in item header with correct crc is handled", "[nvs]") +{ + PartitionEmulationFixture f(0, 3); + nvs::Storage storage(f.part()); + // prepare some data + TEST_ESP_OK(storage.init(0, 3)); + TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast(1))); + TEST_ESP_OK(storage.writeItem(1, "value1", static_cast(1))); + TEST_ESP_OK(storage.writeItem(1, "value2", static_cast(2))); + TEST_ESP_OK(storage.writeItem(1, "value3", static_cast(3))); + + // damage item header of value2 to introduce data type error, recalculate crc + { + nvs::ItemType new_data_type = (nvs::ItemType) 0xfe; + size_t entry_offset = 32 * 4; // 2x page header + 1x ns1 + 1x value1 + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + + nvs::Item* item = (nvs::Item*)buff; + + // set span to U8 + item->datatype = new_data_type; + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + + // allow overwriting smaller portion of flash than whole 4k page + ((esp_partition_t*) f.get_esp_partition())->erase_size = 1; + TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); + TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + } + + // check that storage can recover + uint32_t val = 0; + + TEST_ESP_OK(storage.init(0, 3)); + TEST_ESP_OK(storage.readItem(1, "value1", val)); + CHECK(val == 1); + // check that the damaged item is no longer present + TEST_ESP_ERR(ESP_ERR_NVS_NOT_FOUND, storage.readItem(1, "value2", val)); + + TEST_ESP_OK(storage.readItem(1, "value3", val)); + CHECK(val == 3); +} + // leaks memory TEST_CASE("Recovery from power-off when the entry being erased is not on active page", "[nvs]") { @@ -2641,24 +3125,24 @@ TEST_CASE("Recovery from power-off when the entry being erased is not on active size_t read_size = blob_size; uint8_t blob[blob_size] = {0x11}; PartitionEmulationFixture f(0, 3); - TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3) ); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); nvs_handle_t handle; - TEST_ESP_OK( nvs_open("test", NVS_READWRITE, &handle) ); + TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); esp_partition_clear_stats(); esp_partition_fail_after(nvs::Page::CHUNK_MAX_SIZE / 4 + 75, ESP_PARTITION_FAIL_AFTER_MODE_BOTH); - TEST_ESP_OK( nvs_set_blob(handle, "1a", blob, blob_size) ); - TEST_ESP_OK( nvs_set_blob(handle, "1b", blob, blob_size) ); + TEST_ESP_OK(nvs_set_blob(handle, "1a", blob, blob_size)); + TEST_ESP_OK(nvs_set_blob(handle, "1b", blob, blob_size)); - TEST_ESP_ERR( nvs_erase_key(handle, "1a"), ESP_ERR_FLASH_OP_FAIL ); + TEST_ESP_ERR(nvs_erase_key(handle, "1a"), ESP_ERR_FLASH_OP_FAIL); - TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3) ); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); // Check 1a is erased fully - TEST_ESP_ERR( nvs_get_blob(handle, "1a", blob, &read_size), ESP_ERR_NVS_NOT_FOUND); + TEST_ESP_ERR(nvs_get_blob(handle, "1a", blob, &read_size), ESP_ERR_NVS_NOT_FOUND); // Check 2b is still accessible - TEST_ESP_OK( nvs_get_blob(handle, "1b", blob, &read_size)); + TEST_ESP_OK(nvs_get_blob(handle, "1b", blob, &read_size)); nvs_close(handle); @@ -2672,7 +3156,7 @@ TEST_CASE("Recovery from power-off when page is being freed", "[nvs]") size_t read_size = blob_size / 2; uint8_t blob[blob_size] = {0}; PartitionEmulationFixture f(0, 3); - TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); nvs_handle_t handle; TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); // Fill first page @@ -2689,19 +3173,19 @@ TEST_CASE("Recovery from power-off when page is being freed", "[nvs]") esp_partition_fail_after(6 * nvs::Page::ENTRY_COUNT, ESP_PARTITION_FAIL_AFTER_MODE_BOTH); TEST_ESP_ERR(nvs_set_blob(handle, "1d", blob, blob_size / 4), ESP_ERR_FLASH_OP_FAIL); - TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); read_size = blob_size / 3; - TEST_ESP_OK( nvs_get_blob(handle, "1a", blob, &read_size)); - TEST_ESP_OK( nvs_get_blob(handle, "1b", blob, &read_size)); + TEST_ESP_OK(nvs_get_blob(handle, "1a", blob, &read_size)); + TEST_ESP_OK(nvs_get_blob(handle, "1b", blob, &read_size)); read_size = blob_size / 4; - TEST_ESP_ERR( nvs_get_blob(handle, "1c", blob, &read_size), ESP_ERR_NVS_NOT_FOUND); - TEST_ESP_ERR( nvs_get_blob(handle, "1d", blob, &read_size), ESP_ERR_NVS_NOT_FOUND); + TEST_ESP_ERR(nvs_get_blob(handle, "1c", blob, &read_size), ESP_ERR_NVS_NOT_FOUND); + TEST_ESP_ERR(nvs_get_blob(handle, "1d", blob, &read_size), ESP_ERR_NVS_NOT_FOUND); read_size = blob_size / 2; - TEST_ESP_OK( nvs_get_blob(handle, "2a", blob, &read_size)); - TEST_ESP_OK( nvs_get_blob(handle, "2b", blob, &read_size)); + TEST_ESP_OK(nvs_get_blob(handle, "2a", blob, &read_size)); + TEST_ESP_OK(nvs_get_blob(handle, "2b", blob, &read_size)); TEST_ESP_OK(nvs_commit(handle)); nvs_close(handle); @@ -2714,7 +3198,7 @@ TEST_CASE("Check that NVS supports old blob format without blob index", "[nvs]") // initialize the fixture with nvs binary loaded PartitionEmulationFixture f(0, 2, "nvs", WD_PREFIX "../../nvs_partition_generator/part_old_blob_format.bin"); - TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 2)); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 2)); nvs_handle_t handle; TEST_ESP_OK(nvs_open_from_partition("nvs", "dummyNamespace", NVS_READWRITE, &handle)); @@ -2775,23 +3259,23 @@ static void check_nvs_part_gen_args(char const *flash_binary_filename, // n // initialize the fixture with nvs binary loaded PartitionEmulationFixture f(0, size, part_name, flash_binary_filename); - TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, size) ); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, size)); - TEST_ESP_OK( nvs_open_from_partition(part_name, "dummyNamespace", NVS_READONLY, &handle)); + TEST_ESP_OK(nvs_open_from_partition(part_name, "dummyNamespace", NVS_READONLY, &handle)); uint8_t u8v; - TEST_ESP_OK( nvs_get_u8(handle, "dummyU8Key", &u8v)); + TEST_ESP_OK(nvs_get_u8(handle, "dummyU8Key", &u8v)); CHECK(u8v == 127); int8_t i8v; - TEST_ESP_OK( nvs_get_i8(handle, "dummyI8Key", &i8v)); + TEST_ESP_OK(nvs_get_i8(handle, "dummyI8Key", &i8v)); CHECK(i8v == -128); uint16_t u16v; - TEST_ESP_OK( nvs_get_u16(handle, "dummyU16Key", &u16v)); + TEST_ESP_OK(nvs_get_u16(handle, "dummyU16Key", &u16v)); CHECK(u16v == 32768); uint32_t u32v; - TEST_ESP_OK( nvs_get_u32(handle, "dummyU32Key", &u32v)); + TEST_ESP_OK(nvs_get_u32(handle, "dummyU32Key", &u32v)); CHECK(u32v == 4294967295); int32_t i32v; - TEST_ESP_OK( nvs_get_i32(handle, "dummyI32Key", &i32v)); + TEST_ESP_OK(nvs_get_i32(handle, "dummyI32Key", &i32v)); CHECK(i32v == -2147483648); char string_buf[256]; @@ -2801,28 +3285,27 @@ static void check_nvs_part_gen_args(char const *flash_binary_filename, // n "Pellentesque rhoncus dictum sodales.\n" "In justo erat, viverra at interdum eget, interdum vel dui."; size_t str_len = sizeof(test_str); - TEST_ESP_OK( nvs_get_str(handle, "dummyStringKey", string_buf, &str_len)); + TEST_ESP_OK(nvs_get_str(handle, "dummyStringKey", string_buf, &str_len)); CHECK(strncmp(string_buf, test_str, str_len) == 0); char buf[64] = {0}; uint8_t hexdata[] = {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef}; size_t buflen = 64; - int j; - TEST_ESP_OK( nvs_get_blob(handle, "dummyHex2BinKey", buf, &buflen)); + TEST_ESP_OK(nvs_get_blob(handle, "dummyHex2BinKey", buf, &buflen)); CHECK(memcmp(buf, hexdata, buflen) == 0); uint8_t base64data[] = {'1', '2', '3', 'a', 'b', 'c'}; - TEST_ESP_OK( nvs_get_blob(handle, "dummyBase64Key", buf, &buflen)); + TEST_ESP_OK(nvs_get_blob(handle, "dummyBase64Key", buf, &buflen)); CHECK(memcmp(buf, base64data, buflen) == 0); buflen = 64; uint8_t hexfiledata[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}; - TEST_ESP_OK( nvs_get_blob(handle, "hexFileKey", buf, &buflen)); + TEST_ESP_OK(nvs_get_blob(handle, "hexFileKey", buf, &buflen)); CHECK(memcmp(buf, hexfiledata, buflen) == 0); buflen = 64; uint8_t strfiledata[64] = "abcdefghijklmnopqrstuvwxyz\0"; - TEST_ESP_OK( nvs_get_str(handle, "stringFileKey", buf, &buflen)); + TEST_ESP_OK(nvs_get_str(handle, "stringFileKey", buf, &buflen)); CHECK(memcmp(buf, strfiledata, buflen) == 0); char bin_data[5200]; @@ -2831,7 +3314,7 @@ static void check_nvs_part_gen_args(char const *flash_binary_filename, // n ifstream file; file.open(filename); file.read(binfiledata, 5200); - TEST_ESP_OK( nvs_get_blob(handle, "binFileKey", bin_data, &bin_len)); + TEST_ESP_OK(nvs_get_blob(handle, "binFileKey", bin_data, &bin_len)); CHECK(memcmp(bin_data, binfiledata, bin_len) == 0); file.close(); @@ -2850,48 +3333,47 @@ static void check_nvs_part_gen_args_mfg(char const *flash_binary_filename, // n // initialize the fixture with nvs binary loaded PartitionEmulationFixture f(0, size, part_name, flash_binary_filename); - TEST_ESP_OK( nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, size) ); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, size)); - TEST_ESP_OK( nvs_open_from_partition(part_name, "dummyNamespace", NVS_READONLY, &handle)); + TEST_ESP_OK(nvs_open_from_partition(part_name, "dummyNamespace", NVS_READONLY, &handle)); uint8_t u8v; - TEST_ESP_OK( nvs_get_u8(handle, "dummyU8Key", &u8v)); + TEST_ESP_OK(nvs_get_u8(handle, "dummyU8Key", &u8v)); CHECK(u8v == 127); int8_t i8v; - TEST_ESP_OK( nvs_get_i8(handle, "dummyI8Key", &i8v)); + TEST_ESP_OK(nvs_get_i8(handle, "dummyI8Key", &i8v)); CHECK(i8v == -128); uint16_t u16v; - TEST_ESP_OK( nvs_get_u16(handle, "dummyU16Key", &u16v)); + TEST_ESP_OK(nvs_get_u16(handle, "dummyU16Key", &u16v)); CHECK(u16v == 32768); uint32_t u32v; - TEST_ESP_OK( nvs_get_u32(handle, "dummyU32Key", &u32v)); + TEST_ESP_OK(nvs_get_u32(handle, "dummyU32Key", &u32v)); CHECK(u32v == 4294967295); int32_t i32v; - TEST_ESP_OK( nvs_get_i32(handle, "dummyI32Key", &i32v)); + TEST_ESP_OK(nvs_get_i32(handle, "dummyI32Key", &i32v)); CHECK(i32v == -2147483648); char buf[64] = {0}; size_t buflen = 64; - TEST_ESP_OK( nvs_get_str(handle, "dummyStringKey", buf, &buflen)); + TEST_ESP_OK(nvs_get_str(handle, "dummyStringKey", buf, &buflen)); CHECK(strncmp(buf, "0A:0B:0C:0D:0E:0F", buflen) == 0); uint8_t hexdata[] = {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef}; buflen = 64; - int j; - TEST_ESP_OK( nvs_get_blob(handle, "dummyHex2BinKey", buf, &buflen)); + TEST_ESP_OK(nvs_get_blob(handle, "dummyHex2BinKey", buf, &buflen)); CHECK(memcmp(buf, hexdata, buflen) == 0); uint8_t base64data[] = {'1', '2', '3', 'a', 'b', 'c'}; - TEST_ESP_OK( nvs_get_blob(handle, "dummyBase64Key", buf, &buflen)); + TEST_ESP_OK(nvs_get_blob(handle, "dummyBase64Key", buf, &buflen)); CHECK(memcmp(buf, base64data, buflen) == 0); buflen = 64; uint8_t hexfiledata[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}; - TEST_ESP_OK( nvs_get_blob(handle, "hexFileKey", buf, &buflen)); + TEST_ESP_OK(nvs_get_blob(handle, "hexFileKey", buf, &buflen)); CHECK(memcmp(buf, hexfiledata, buflen) == 0); buflen = 64; uint8_t strfiledata[64] = "abcdefghijklmnopqrstuvwxyz\0"; - TEST_ESP_OK( nvs_get_str(handle, "stringFileKey", buf, &buflen)); + TEST_ESP_OK(nvs_get_str(handle, "stringFileKey", buf, &buflen)); CHECK(memcmp(buf, strfiledata, buflen) == 0); char bin_data[5200]; @@ -2900,7 +3382,7 @@ static void check_nvs_part_gen_args_mfg(char const *flash_binary_filename, // n ifstream file; file.open(filename); file.read(binfiledata, 5200); - TEST_ESP_OK( nvs_get_blob(handle, "binFileKey", bin_data, &bin_len)); + TEST_ESP_OK(nvs_get_blob(handle, "binFileKey", bin_data, &bin_len)); CHECK(memcmp(bin_data, binfiledata, bin_len) == 0); file.close(); @@ -2918,7 +3400,7 @@ TEST_CASE("check and read data from partition generated via partition generation exit(execlp("cp", " cp", "-rf", WD_PREFIX "../../nvs_partition_generator/testdata", - "." , + ".", NULL)); } else { CHECK(childpid > 0); @@ -3082,7 +3564,6 @@ TEST_CASE("check and read data from partition generated via manufacturing utilit 3, "testdata/sample_singlepage_blob.bin"); - childpid = fork(); if (childpid == 0) { exit(execlp("bash", " bash", @@ -3268,8 +3749,8 @@ TEST_CASE("nvs multiple write with same key but different types", "[nvs][xxx]") f.erase(j); } TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle_1)); @@ -3332,14 +3813,13 @@ TEST_CASE("nvs find key tests", "[nvs]") const uint32_t NVS_FLASH_SECTOR = 6; const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 13; - TEST_ESP_ERR(nvs_open("namespace1", NVS_READWRITE, &handle_1), ESP_ERR_NVS_NOT_INITIALIZED); for (uint16_t i = NVS_FLASH_SECTOR; i < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; ++i) { f.erase(i); } TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); nvs_type_t datatype_found; // datatype of entry found @@ -3372,11 +3852,15 @@ TEST_CASE("nvs find key tests", "[nvs]") uint8_t *p_buff = (uint8_t *) malloc(buff_len); CHECK(p_buff != nullptr); TEST_ESP_ERR(nvs_find_key(handle_1, "foo2", &datatype_found), ESP_ERR_NVS_NOT_FOUND); - for(size_t i=0; i #include "nvs_internal.h" -namespace nvs -{ +namespace nvs { Page::Page() : mPartition(nullptr) { } uint32_t Page::Header::calculateCrc32() { return esp_rom_crc32_le(0xffffffff, - reinterpret_cast(this) + offsetof(Header, mSeqNumber), - offsetof(Header, mCrc32) - offsetof(Header, mSeqNumber)); + reinterpret_cast(this) + offsetof(Header, mSeqNumber), + offsetof(Header, mCrc32) - offsetof(Header, mSeqNumber)); } esp_err_t Page::load(Partition *partition, uint32_t sectorNumber) @@ -46,7 +45,9 @@ esp_err_t Page::load(Partition *partition, uint32_t sectorNumber) const int BLOCK_SIZE = 128; uint32_t* block = new (std::nothrow) uint32_t[BLOCK_SIZE]; - if (!block) return ESP_ERR_NO_MEM; + if (!block) { + return ESP_ERR_NO_MEM; + } for (uint32_t i = 0; i < SPI_FLASH_SEC_SIZE; i += 4 * BLOCK_SIZE) { rc = mPartition->read_raw(mBaseAddress + i, block, 4 * BLOCK_SIZE); @@ -67,7 +68,7 @@ esp_err_t Page::load(Partition *partition, uint32_t sectorNumber) } else { mState = header.mState; mSeqNumber = header.mSeqNumber; - if(header.mVersion < NVS_VERSION) { + if (header.mVersion < NVS_VERSION) { return ESP_ERR_NVS_NEW_VERSION_FOUND; } else { mVersion = header.mVersion; @@ -92,7 +93,7 @@ esp_err_t Page::load(Partition *partition, uint32_t sectorNumber) return ESP_OK; } -esp_err_t Page::writeEntry(const Item& item) +esp_err_t Page::writeEntry(const Item &item) { uint32_t phyAddr; esp_err_t err = getEntryAddress(mNextFreeEntry, &phyAddr); @@ -101,7 +102,6 @@ esp_err_t Page::writeEntry(const Item& item) } err = mPartition->write(phyAddr, &item, sizeof(item)); - if (err != ESP_OK) { mState = PageState::INVALID; return err; @@ -190,7 +190,7 @@ esp_err_t Page::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, c // primitive types should fit into one entry NVS_ASSERT_OR_RETURN(totalSize == ENTRY_SIZE || - isVariableLengthType(datatype), ESP_ERR_NVS_VALUE_TOO_LONG); + isVariableLengthType(datatype), ESP_ERR_NVS_VALUE_TOO_LONG); if (mNextFreeEntry == INVALID_ENTRY || mNextFreeEntry + entriesCount > ENTRY_COUNT) { // page will not fit this amount of data @@ -283,12 +283,12 @@ esp_err_t Page::readItem(uint8_t nsIndex, ItemType datatype, const char* key, vo return rc; } size_t willCopy = ENTRY_SIZE; - willCopy = (left < willCopy)?left:willCopy; + willCopy = (left < willCopy) ? left : willCopy; memcpy(dst, ditem.rawData, willCopy); left -= willCopy; dst += willCopy; } - if (Item::calculateCrc32(reinterpret_cast(data), item.varLength.dataSize) != item.varLength.dataCrc32) { + if (Item::calculateCrc32(reinterpret_cast(data), item.varLength.dataSize) != item.varLength.dataCrc32) { rc = eraseEntryAndSpan(index); if (rc != ESP_OK) { return rc; @@ -336,14 +336,14 @@ esp_err_t Page::cmpItem(uint8_t nsIndex, ItemType datatype, const char* key, con return rc; } size_t willCopy = ENTRY_SIZE; - willCopy = (left < willCopy)?left:willCopy; + willCopy = (left < willCopy) ? left : willCopy; if (memcmp(dst, ditem.rawData, willCopy)) { return ESP_ERR_NVS_CONTENT_DIFFERS; } left -= willCopy; dst += willCopy; } - if (Item::calculateCrc32(reinterpret_cast(data), item.varLength.dataSize) != item.varLength.dataCrc32) { + if (Item::calculateCrc32(reinterpret_cast(data), item.varLength.dataSize) != item.varLength.dataCrc32) { return ESP_ERR_NVS_NOT_FOUND; } @@ -386,7 +386,7 @@ esp_err_t Page::eraseEntryAndSpan(size_t index) if (rc != ESP_OK) { return rc; } - if (item.calculateCrc32() != item.crc32) { + if (!item.checkHeaderConsistency(index)) { mHashList.erase(index); rc = alterEntryState(index, EntryState::ERASED); --mUsedEntryCount; @@ -460,7 +460,7 @@ esp_err_t Page::updateFirstUsedEntry(size_t index, size_t span) return ESP_OK; } -esp_err_t Page::copyItems(Page& other) +esp_err_t Page::copyItems(Page &other) { if (mFirstUsedEntry == INVALID_ENTRY) { return ESP_ERR_NVS_NOT_FOUND; @@ -508,7 +508,10 @@ esp_err_t Page::copyItems(Page& other) NVS_ASSERT_OR_RETURN(end <= ENTRY_COUNT, ESP_FAIL); for (size_t i = readEntryIndex + 1; i < end; ++i) { - readEntry(i, entry); + err = readEntry(i, entry); + if (err != ESP_OK) { + return err; + } err = other.writeEntry(entry); if (err != ESP_OK) { return err; @@ -527,7 +530,7 @@ esp_err_t Page::mLoadEntryTable() mState == PageState::FULL || mState == PageState::FREEING) { auto rc = mPartition->read_raw(mBaseAddress + ENTRY_TABLE_OFFSET, mEntryTable.data(), - mEntryTable.byteSize()); + mEntryTable.byteSize()); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -599,8 +602,7 @@ esp_err_t Page::mLoadEntryTable() --mUsedEntryCount; } ++mErasedEntryCount; - } - else { + } else { break; } } @@ -642,7 +644,7 @@ esp_err_t Page::mLoadEntryTable() return err; } - if (item.crc32 != item.calculateCrc32()) { + if (!item.checkHeaderConsistency(i)) { err = eraseEntryAndSpan(i); if (err != ESP_OK) { mState = PageState::INVALID; @@ -722,7 +724,7 @@ esp_err_t Page::mLoadEntryTable() return err; } - if (item.crc32 != item.calculateCrc32()) { + if (!item.checkHeaderConsistency(i)) { err = eraseEntryAndSpan(i); if (err != ESP_OK) { mState = PageState::INVALID; @@ -762,7 +764,6 @@ esp_err_t Page::mLoadEntryTable() return ESP_OK; } - esp_err_t Page::initialize() { NVS_ASSERT_OR_RETURN(mState == PageState::UNINITIALIZED, ESP_FAIL); @@ -794,7 +795,7 @@ esp_err_t Page::alterEntryState(size_t index, EntryState state) size_t wordToWrite = mEntryTable.getWordIndex(index); uint32_t word = mEntryTable.data()[wordToWrite]; err = mPartition->write_raw(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast(wordToWrite) * 4, - &word, sizeof(word)); + &word, sizeof(word)); if (err != ESP_OK) { mState = PageState::INVALID; return err; @@ -810,7 +811,7 @@ esp_err_t Page::alterEntryRangeState(size_t begin, size_t end, EntryState state) esp_err_t err; for (ptrdiff_t i = end - 1; i >= static_cast(begin); --i) { err = mEntryTable.set(i, state); - if (err != ESP_OK){ + if (err != ESP_OK) { return err; } size_t nextWordIndex; @@ -822,7 +823,7 @@ esp_err_t Page::alterEntryRangeState(size_t begin, size_t end, EntryState state) if (nextWordIndex != wordIndex) { uint32_t word = mEntryTable.data()[wordIndex]; auto rc = mPartition->write_raw(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast(wordIndex) * 4, - &word, 4); + &word, 4); if (rc != ESP_OK) { return rc; } @@ -844,7 +845,7 @@ esp_err_t Page::alterPageState(PageState state) return ESP_OK; } -esp_err_t Page::readEntry(size_t index, Item& dst) const +esp_err_t Page::readEntry(size_t index, Item &dst) const { uint32_t phyAddr; esp_err_t rc = getEntryAddress(index, &phyAddr); @@ -858,7 +859,7 @@ esp_err_t Page::readEntry(size_t index, Item& dst) const return ESP_OK; } -esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, size_t &itemIndex, Item& item, uint8_t chunkIdx, VerOffset chunkStart) +esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, size_t &itemIndex, Item &item, uint8_t chunkIdx, VerOffset chunkStart) { if (mState == PageState::CORRUPT || mState == PageState::INVALID || mState == PageState::UNINITIALIZED) { return ESP_ERR_NVS_NOT_FOUND; @@ -910,8 +911,7 @@ esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, si return rc; } - auto crc32 = item.calculateCrc32(); - if (item.crc32 != crc32) { + if (!item.checkHeaderConsistency(i)) { rc = eraseEntryAndSpan(i); if (rc != ESP_OK) { mState = PageState::INVALID; @@ -975,7 +975,6 @@ esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, si continue; } - if (datatype != ItemType::ANY && item.datatype != datatype) { if (key == nullptr && nsIndex == NS_ANY && chunkIdx == CHUNK_ANY) { continue; // continue for bruteforce search on blob indices. @@ -992,7 +991,7 @@ esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, si return ESP_ERR_NVS_NOT_FOUND; } -esp_err_t Page::getSeqNumber(uint32_t& seqNumber) const +esp_err_t Page::getSeqNumber(uint32_t &seqNumber) const { if (mState != PageState::UNINITIALIZED && mState != PageState::INVALID && mState != PageState::CORRUPT) { seqNumber = mSeqNumber; @@ -1001,7 +1000,6 @@ esp_err_t Page::getSeqNumber(uint32_t& seqNumber) const return ESP_ERR_NVS_NOT_INITIALIZED; } - esp_err_t Page::setSeqNumber(uint32_t seqNumber) { if (mState != PageState::UNINITIALIZED) { @@ -1059,41 +1057,41 @@ size_t Page::getVarDataTailroom() const } else if (mState == PageState::FULL) { return 0; } - /* Skip one entry for blob data item precessing the data */ - return ((mNextFreeEntry < (ENTRY_COUNT-1)) ? ((ENTRY_COUNT - mNextFreeEntry - 1) * ENTRY_SIZE): 0); + /* Skip one entry for blob data item processing the data */ + return ((mNextFreeEntry < (ENTRY_COUNT - 1)) ? ((ENTRY_COUNT - mNextFreeEntry - 1) * ENTRY_SIZE) : 0); } const char* Page::pageStateToName(PageState ps) { switch (ps) { - case PageState::CORRUPT: - return "CORRUPT"; + case PageState::CORRUPT: + return "CORRUPT"; - case PageState::ACTIVE: - return "ACTIVE"; + case PageState::ACTIVE: + return "ACTIVE"; - case PageState::FREEING: - return "FREEING"; + case PageState::FREEING: + return "FREEING"; - case PageState::FULL: - return "FULL"; + case PageState::FULL: + return "FULL"; - case PageState::INVALID: - return "INVALID"; + case PageState::INVALID: + return "INVALID"; - case PageState::UNINITIALIZED: - return "UNINITIALIZED"; + case PageState::UNINITIALIZED: + return "UNINITIALIZED"; - default: - assert(0 && "invalid state value"); - return ""; + default: + assert(0 && "invalid state value"); + return ""; } } void Page::debugDump() const { printf("state=%" PRIx32 " (%s) addr=%" PRIx32 " seq=%" PRIu32 "\nfirstUsed=%" PRIu32 " nextFree=%" PRIu32 " used=%" PRIu16 " erased=%" PRIu16 "\n", - static_cast(mState), pageStateToName(mState), mBaseAddress, mSeqNumber, static_cast(mFirstUsedEntry), static_cast(mNextFreeEntry), mUsedEntryCount, mErasedEntryCount); + static_cast(mState), pageStateToName(mState), mBaseAddress, mSeqNumber, static_cast(mFirstUsedEntry), static_cast(mNextFreeEntry), mUsedEntryCount, mErasedEntryCount); size_t skip = 0; for (size_t i = 0; i < ENTRY_COUNT; ++i) { printf("%3d: ", static_cast(i)); @@ -1111,7 +1109,7 @@ void Page::debugDump() const readEntry(i, item); if (skip == 0) { printf("W ns=%2" PRIu8 " type=%2" PRIu8 " span=%3" PRIu8 " key=\"%s\" chunkIdx=%" PRIu8 " len=%" PRIi32 "\n", - item.nsIndex, static_cast(item.datatype), item.span, item.key, item.chunkIndex, (item.span != 1)?(static_cast(item.varLength.dataSize)):(-1)); + item.nsIndex, static_cast(item.datatype), item.span, item.key, item.chunkIndex, (item.span != 1) ? (static_cast(item.varLength.dataSize)) : (-1)); if (item.span > 0 && item.span <= ENTRY_COUNT - i) { skip = item.span - 1; } else { @@ -1132,24 +1130,24 @@ esp_err_t Page::calcEntries(nvs_stats_t &nvsStats) nvsStats.total_entries += ENTRY_COUNT; switch (mState) { - case PageState::UNINITIALIZED: - case PageState::CORRUPT: - nvsStats.free_entries += ENTRY_COUNT; - break; + case PageState::UNINITIALIZED: + case PageState::CORRUPT: + nvsStats.free_entries += ENTRY_COUNT; + break; - case PageState::FULL: - case PageState::ACTIVE: - nvsStats.used_entries += mUsedEntryCount; - nvsStats.free_entries += ENTRY_COUNT - mUsedEntryCount; // it's equivalent free + erase entries. - break; + case PageState::FULL: + case PageState::ACTIVE: + nvsStats.used_entries += mUsedEntryCount; + nvsStats.free_entries += ENTRY_COUNT - mUsedEntryCount; // it's equivalent free + erase entries. + break; - case PageState::INVALID: - return ESP_ERR_INVALID_STATE; - break; + case PageState::INVALID: + return ESP_ERR_INVALID_STATE; + break; - default: - assert(false && "Unhandled state"); - break; + default: + assert(false && "Unhandled state"); + break; } return ESP_OK; } diff --git a/components/nvs_flash/src/nvs_types.cpp b/components/nvs_flash/src/nvs_types.cpp index a9369a4e27..24073fc306 100644 --- a/components/nvs_flash/src/nvs_types.cpp +++ b/components/nvs_flash/src/nvs_types.cpp @@ -1,28 +1,22 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include "nvs_types.hpp" - +#include "nvs_page.hpp" +#include "esp_log.h" #include "esp_rom_crc.h" -namespace nvs -{ +#define TAG "nvs" + +namespace nvs { uint32_t Item::calculateCrc32() const { uint32_t result = 0xffffffff; const uint8_t* p = reinterpret_cast(this); result = esp_rom_crc32_le(result, p + offsetof(Item, nsIndex), - offsetof(Item, crc32) - offsetof(Item, nsIndex)); + offsetof(Item, crc32) - offsetof(Item, nsIndex)); result = esp_rom_crc32_le(result, p + offsetof(Item, key), sizeof(key)); result = esp_rom_crc32_le(result, p + offsetof(Item, data), sizeof(data)); return result; @@ -33,7 +27,7 @@ uint32_t Item::calculateCrc32WithoutValue() const uint32_t result = 0xffffffff; const uint8_t* p = reinterpret_cast(this); result = esp_rom_crc32_le(result, p + offsetof(Item, nsIndex), - offsetof(Item, datatype) - offsetof(Item, nsIndex)); + offsetof(Item, datatype) - offsetof(Item, nsIndex)); result = esp_rom_crc32_le(result, p + offsetof(Item, key), sizeof(key)); result = esp_rom_crc32_le(result, p + offsetof(Item, chunkIndex), sizeof(chunkIndex)); return result; @@ -46,4 +40,114 @@ uint32_t Item::calculateCrc32(const uint8_t* data, size_t size) return result; } +bool Item::checkHeaderConsistency(const uint8_t entryIndex) const +{ + // calculate and check the crc32 + if (crc32 != calculateCrc32()) { + ESP_LOGD(TAG, "CRC32 mismatch for entry %d", entryIndex); + return false; + } + + // validate the datatype and check the rest of the header fields + switch (datatype) { + // Entries occupying just one entry + case ItemType::U8: + case ItemType::I8: + case ItemType::U16: + case ItemType::I16: + case ItemType::U32: + case ItemType::I32: + case ItemType::U64: + case ItemType::I64: { + if (span != 1) { + ESP_LOGD(TAG, "Invalid span %u for datatype %#04x", (unsigned int)span, (unsigned int)datatype); + return false; + } + break; + } + + // Special case for BLOB_IDX + case ItemType::BLOB_IDX: { + // span must be 1 + if (span != 1) { + ESP_LOGD(TAG, "Invalid span %u for BLOB_IDX", (unsigned int)span); + return false; + } + + // chunkIndex must be CHUNK_ANY + if (chunkIndex != CHUNK_ANY) { + ESP_LOGD(TAG, "Invalid chunk index %u for BLOB_IDX", (unsigned int)chunkIndex); + return false; + } + + // check maximum data length + // the maximal data length is determined by: + // maximum number of chunks. Chunks are stored in uin8_t, but are logically divided into two "VerOffset" ranges of values (0 based and 128 based) + // maximum theoretical number of entries in the chunk (Page::ENTRY_COUNT - 1) and the number of bytes entry can store (Page::ENTRY_SIZE) + const uint32_t maxDataSize = (uint32_t)((UINT8_MAX / 2) * (Page::ENTRY_COUNT - 1) * Page::ENTRY_SIZE); + if (blobIndex.dataSize > maxDataSize) { + ESP_LOGD(TAG, "Data size %u bytes exceeds maximum possible size %u bytes for BLOB_IDX", (unsigned int)blobIndex.dataSize, (unsigned int)maxDataSize); + return false; + } + break; + } + + // Entries with variable length data + case ItemType::SZ: + case ItemType::BLOB: + case ItemType::BLOB_DATA: { + uint16_t maxAvailableVDataSize; + uint8_t maxAvailablePageSpan; + uint8_t spanCalcFromLen; + + // for BLOB_DATA, chunkIndex must NOT be CHUNK_ANY as this value is used to search ALL chunks in findItem + if (datatype == ItemType::BLOB_DATA) { + // chunkIndex must not be CHUNK_ANY + if (chunkIndex == CHUNK_ANY) { + ESP_LOGD(TAG, "Invalid chunk index %u for BLOB_DATA", (unsigned int)chunkIndex); + return false; + } + } + + // variable length and span checks + + // based on the entryIndex determine the maximum variable length capacity in bytes to the end of the page + maxAvailableVDataSize = ((Page::ENTRY_COUNT - entryIndex) - 1) * Page::ENTRY_SIZE; + + // check if the variable data length is not exceeding the maximum capacity available till the end of the page + if (varLength.dataSize > maxAvailableVDataSize) { + ESP_LOGD(TAG, "Variable data length %u bytes exceeds page boundary. Maximum calculated from the current entry position within page is %u bytes for datatype %#04x ", (unsigned int)varLength.dataSize, (unsigned int)maxAvailableVDataSize, (unsigned int)datatype); + return false; + } + + // based on the entryIndex determine the maximum possible span up to the end of the page + maxAvailablePageSpan = Page::ENTRY_COUNT - entryIndex; + + // this check ensures no data is read beyond the end of the page + if (span > maxAvailablePageSpan) { + ESP_LOGD(TAG, "Span %u exceeds page boundary. Maximum calculated from the current entry position within page is %u for datatype %#04x ", (unsigned int)span, (unsigned int)maxAvailablePageSpan, (unsigned int)datatype); + return false; + } + + // here we have both span and varLength.dataSize within the page boundary. Check if these values are consistent + spanCalcFromLen = (uint8_t)(((size_t) varLength.dataSize + Page::ENTRY_SIZE - 1) / Page::ENTRY_SIZE); + spanCalcFromLen ++; // add overhead entry + + // this check ensures that the span is equal to the number of entries required to store the data plus the overhead entry + if (span != spanCalcFromLen) { + ESP_LOGD(TAG, "Span %i does not match span %u calculated from variable data length %u bytes for datatype %#04x", (unsigned int)span, (unsigned int)spanCalcFromLen, (unsigned int)varLength.dataSize, (unsigned int)datatype); + return false; + } + break; + } + + // Invalid datatype + default: { + ESP_LOGD(TAG, "Invalid datatype %#04x", (unsigned int)datatype); + return false; + } + } + return true; +} + } // namespace nvs diff --git a/components/nvs_flash/src/nvs_types.hpp b/components/nvs_flash/src/nvs_types.hpp index 53d3555693..5eda9abcd9 100644 --- a/components/nvs_flash/src/nvs_types.hpp +++ b/components/nvs_flash/src/nvs_types.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -108,6 +108,14 @@ public: dst = *reinterpret_cast(data); return ESP_OK; } + + // Returns true if item's header: + // crc32 matches the calculated crc32 + // and datatype is one of the supported types + // and span is within the allowed range for the datatype and below the maximum calculated from the entryIndex + // + // Parameter entryIndex is used to calculate the maximum span for the given entry + bool checkHeaderConsistency(const uint8_t entryIndex) const; }; } // namespace nvs diff --git a/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index 9d9e619fe0..328f0ffcff 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -584,7 +584,6 @@ components/nvs_flash/src/nvs_pagemanager.hpp components/nvs_flash/src/nvs_partition_lookup.cpp components/nvs_flash/src/nvs_partition_lookup.hpp components/nvs_flash/src/nvs_test_api.h -components/nvs_flash/src/nvs_types.cpp components/nvs_flash/test_nvs_host/main.cpp components/nvs_flash/test_nvs_host/sdkconfig.h components/nvs_flash/test_nvs_host/test_intrusive_list.cpp