Skip to content

Commit

Permalink
Merge branch 'bugfix/nvs_set_datatype' into 'master'
Browse files Browse the repository at this point in the history
bugfix(nvs_flash) : fixed nvs_set functions behaviour when called sequentially with same key and different data type(s)

Closes IDFGH-9727

See merge request espressif/esp-idf!25581
  • Loading branch information
pacucha42 committed Oct 2, 2023
2 parents 5cd6284 + 4f659b7 commit c50dfa2
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 34 deletions.
3 changes: 3 additions & 0 deletions components/nvs_flash/.build-test-rules.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
components/nvs_flash/host_test:
enable:
- if: IDF_TARGET == "linux"
10 changes: 10 additions & 0 deletions components/nvs_flash/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,14 @@ menu "NVS"
default n
help
This option switches error checking type between assertions (y) or return codes (n).

config NVS_LEGACY_DUP_KEYS_COMPATIBILITY
bool "Enable legacy nvs_set function behavior when same key is reused with different data types"
default n
help
Enabling this option will switch the nvs_set() family of functions to the legacy mode:
when called repeatedly with the same key but different data type, the existing value
in the NVS remains active and the new value is just stored, actually not accessible through
corresponding nvs_get() call for the key given. Use this option only when your application
relies on such NVS API behaviour.
endmenu
65 changes: 65 additions & 0 deletions components/nvs_flash/host_test/nvs_host_test/main/test_nvs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3255,6 +3255,71 @@ TEST_CASE("check and read data from partition generated via manufacturing utilit
}
}

TEST_CASE("nvs multiple write with same key but different types", "[nvs][xxx]")
{
PartitionEmulationFixture f(0, 10);

nvs_handle_t handle_1;
const uint32_t NVS_FLASH_SECTOR = 6;
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);)

for (uint16_t j = NVS_FLASH_SECTOR; j < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; ++j) {
f.erase(j);
}
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(),
NVS_FLASH_SECTOR,
NVS_FLASH_SECTOR_COUNT_MIN));

TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle_1));

nvs_erase_all(handle_1);

int32_t v32;
int8_t v8;

TEST_ESP_OK(nvs_set_i32(handle_1, "foo", (int32_t)12345678));
TEST_ESP_OK(nvs_set_i8(handle_1, "foo", (int8_t)12));
TEST_ESP_OK(nvs_set_i8(handle_1, "foo", (int8_t)34));

#ifdef CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY
// Legacy behavior
// First use of key hooks data type until removed by nvs_erase_key. Alternative re-use of same key with different
// data type is written to the storage as hidden active value. It is returned by nvs_get function after nvs_erase_key is called.
// Mixing more than 2 data types brings undefined behavior. It is not tested here.

TEST_ESP_ERR(nvs_get_i8(handle_1, "foo", &v8), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_OK(nvs_get_i32(handle_1, "foo", &v32));
TEST_ESP_OK(nvs_erase_key(handle_1, "foo"));

TEST_ESP_OK(nvs_get_i8(handle_1, "foo", &v8));
TEST_ESP_ERR(nvs_get_i32(handle_1, "foo", &v32), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_OK(nvs_erase_key(handle_1, "foo"));

TEST_ESP_OK(nvs_get_i8(handle_1, "foo", &v8));
TEST_ESP_ERR(nvs_get_i32(handle_1, "foo", &v32), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_OK(nvs_erase_key(handle_1, "foo"));

TEST_ESP_ERR(nvs_erase_key(handle_1, "foo"), ESP_ERR_NVS_NOT_FOUND);
#else
// New behavior
// Latest nvs_set call replaces any existing value. Only one active value under the key exists in storage

TEST_ESP_OK(nvs_get_i8(handle_1, "foo", &v8));
TEST_ESP_ERR(nvs_get_i32(handle_1, "foo", &v32), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_OK(nvs_erase_key(handle_1, "foo"));

TEST_ESP_ERR(nvs_get_i8(handle_1, "foo", &v8), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(nvs_get_i32(handle_1, "foo", &v32), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(nvs_erase_key(handle_1, "foo"), ESP_ERR_NVS_NOT_FOUND);
#endif

nvs_close(handle_1);

TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME));
}


/* Add new tests above */
/* This test has to be the final one */

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY=n
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY=y
28 changes: 10 additions & 18 deletions components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
// 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-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "unity.h"
#include "test_fixtures.hpp"
Expand Down Expand Up @@ -863,7 +855,7 @@ void test_Page_calcEntries__uninit()
NVSPageFixture fix;
TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state());

nvs_stats_t nvsStats = {0, 0, 0, 0};
nvs_stats_t nvsStats = {0, 0, 0, 0, 0};

TEST_ASSERT_EQUAL(ESP_OK, fix.page.calcEntries(nvsStats));
TEST_ASSERT_EQUAL(0, nvsStats.used_entries);
Expand All @@ -888,7 +880,7 @@ void test_Page_calcEntries__corrupt()

TEST_ASSERT_EQUAL(Page::PageState::CORRUPT, page.state());

nvs_stats_t nvsStats = {0, 0, 0, 0};
nvs_stats_t nvsStats = {0, 0, 0, 0, 0};

TEST_ASSERT_EQUAL(ESP_OK, page.calcEntries(nvsStats));
TEST_ASSERT_EQUAL(0, nvsStats.used_entries);
Expand All @@ -901,7 +893,7 @@ void test_Page_calcEntries__active_wo_blob()
{
NVSValidPageFixture fix;

nvs_stats_t nvsStats = {0, 0, 0, 0};
nvs_stats_t nvsStats = {0, 0, 0, 0, 0};

TEST_ASSERT_EQUAL(ESP_OK, fix.page.calcEntries(nvsStats));
TEST_ASSERT_EQUAL(2, nvsStats.used_entries);
Expand All @@ -914,7 +906,7 @@ void test_Page_calcEntries__active_with_blob()
{
NVSValidBlobPageFixture fix;

nvs_stats_t nvsStats = {0, 0, 0, 0};
nvs_stats_t nvsStats = {0, 0, 0, 0, 0};

TEST_ASSERT_EQUAL(ESP_OK, fix.page.calcEntries(nvsStats));
TEST_ASSERT_EQUAL(4, nvsStats.used_entries);
Expand All @@ -927,7 +919,7 @@ void test_Page_calcEntries__invalid()
{
Page page;

nvs_stats_t nvsStats = {0, 0, 0, 0};
nvs_stats_t nvsStats = {0, 0, 0, 0, 0};

TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state());

Expand Down
2 changes: 1 addition & 1 deletion components/nvs_flash/src/nvs_page.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -878,7 +878,7 @@ esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, si
end = ENTRY_COUNT;
}

if (nsIndex != NS_ANY && datatype != ItemType::ANY && key != NULL) {
if (nsIndex != NS_ANY && key != NULL) {
size_t cachedIndex = mHashList.find(start, Item(nsIndex, datatype, 0, key, chunkIdx));
if (cachedIndex < ENTRY_COUNT) {
start = cachedIndex;
Expand Down
30 changes: 26 additions & 4 deletions components/nvs_flash/src/nvs_storage.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -285,13 +285,27 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key
}

Page* findPage = nullptr;
bool matchedTypePageFound = false;
Item item;

esp_err_t err;
if (datatype == ItemType::BLOB) {
err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item);
if(err == ESP_OK) {
matchedTypePageFound = true;
}
} else {
#ifdef CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY
err = findItem(nsIndex, datatype, key, findPage, item);
if(err == ESP_OK && findPage != nullptr) {
matchedTypePageFound = true;
}
#else
err = findItem(nsIndex, ItemType::ANY, key, findPage, item);
if(err == ESP_OK && datatype == item.datatype) {
matchedTypePageFound = true;
}
#endif
}

if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
Expand All @@ -301,7 +315,7 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key
if (datatype == ItemType::BLOB) {
VerOffset prevStart, nextStart;
prevStart = nextStart = VerOffset::VER_0_OFFSET;
if (findPage) {
if (matchedTypePageFound) {
// Do a sanity check that the item in question is actually being modified.
// If it isn't, it is cheaper to purposefully not write out new data.
// since it may invoke an erasure of flash.
Expand Down Expand Up @@ -335,7 +349,7 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key
return err;
}

if (findPage) {
if (matchedTypePageFound) {
/* Erase the blob with earlier version*/
err = eraseMultiPageBlob(nsIndex, key, prevStart);

Expand All @@ -358,7 +372,7 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key
// Do a sanity check that the item in question is actually being modified.
// If it isn't, it is cheaper to purposefully not write out new data.
// since it may invoke an erasure of flash.
if (findPage != nullptr &&
if (matchedTypePageFound &&
findPage->cmpItem(nsIndex, datatype, key, data, dataSize) == ESP_OK) {
return ESP_OK;
}
Expand Down Expand Up @@ -392,12 +406,20 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key
if (findPage) {
if (findPage->state() == Page::PageState::UNINITIALIZED ||
findPage->state() == Page::PageState::INVALID) {
#ifdef CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY
err = findItem(nsIndex, datatype, key, findPage, item);
#else
err = findItem(nsIndex, ItemType::ANY, key, findPage, item);
#endif
if (err != ESP_OK) {
return err;
}
}
#ifdef CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY
err = findPage->eraseItem(nsIndex, datatype, key);
#else
err = findPage->eraseItem(nsIndex, ItemType::ANY, key);
#endif
if (err == ESP_ERR_FLASH_OP_FAIL) {
return ESP_ERR_NVS_REMOVE_FAILED;
}
Expand Down
7 changes: 2 additions & 5 deletions docs/en/api-reference/storage/nvs_flash.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,9 @@ NVS operates on key-value pairs. Keys are ASCII strings; the maximum key length

Additional types, such as ``float`` and ``double`` might be added later.

Keys are required to be unique. Assigning a new value to an existing key works as follows:
Keys are required to be unique. Assigning a new value to an existing key replaces the old value and data type with the value and data type specified by a write operation.

- If the new value is of the same type as the old one, value is updated.
- If the new value has a different data type, an error is returned.

Data type check is also performed when reading a value. An error is returned if the data type of the read operation does not match the data type of the value.
A data type check is performed when reading a value. An error is returned if the data type expected by read operation does not match the data type of entry found for the key provided.


Namespaces
Expand Down
7 changes: 2 additions & 5 deletions docs/zh_CN/api-reference/storage/nvs_flash.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,9 @@ NVS 的操作对象为键值对,其中键是 ASCII 字符串,当前支持的

后续可能会增加对 ``float`` 和 ``double`` 等其他类型数据的支持。

键必须唯一。为现有的键写入新的值可能产生如下结果:
键必须唯一。为现有的键写入新值时,会将旧的值及数据类型更新为写入操作指定的值和数据类型。

- 如果新旧值数据类型相同,则更新值;
- 如果新旧值数据类型不同,则返回错误。

读取值时也会执行数据类型检查。如果读取操作的数据类型与该值的数据类型不匹配,则返回错误。
读取值时会执行数据类型检查。如果读取操作预期的数据类型与对应键的数据类型不匹配,则返回错误。


命名空间
Expand Down
1 change: 0 additions & 1 deletion tools/ci/check_copyright_ignore.txt
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,6 @@ components/mbedtls/port/include/sha512_alt.h
components/mbedtls/port/sha/dma/include/esp_sha_dma_priv.h
components/mbedtls/port/sha/dma/sha.c
components/mbedtls/port/sha/parallel_engine/sha.c
components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp
components/nvs_flash/include/nvs_handle.hpp
components/nvs_flash/src/nvs_cxx_api.cpp
components/nvs_flash/src/nvs_encrypted_partition.hpp
Expand Down

0 comments on commit c50dfa2

Please sign in to comment.