Skip to content

Commit c50dfa2

Browse files
committed
Merge branch 'bugfix/nvs_set_datatype' into 'master'
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
2 parents 5cd6284 + 4f659b7 commit c50dfa2

File tree

11 files changed

+121
-34
lines changed

11 files changed

+121
-34
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
components/nvs_flash/host_test:
2+
enable:
3+
- if: IDF_TARGET == "linux"

components/nvs_flash/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,14 @@ menu "NVS"
2525
default n
2626
help
2727
This option switches error checking type between assertions (y) or return codes (n).
28+
29+
config NVS_LEGACY_DUP_KEYS_COMPATIBILITY
30+
bool "Enable legacy nvs_set function behavior when same key is reused with different data types"
31+
default n
32+
help
33+
Enabling this option will switch the nvs_set() family of functions to the legacy mode:
34+
when called repeatedly with the same key but different data type, the existing value
35+
in the NVS remains active and the new value is just stored, actually not accessible through
36+
corresponding nvs_get() call for the key given. Use this option only when your application
37+
relies on such NVS API behaviour.
2838
endmenu

components/nvs_flash/host_test/nvs_host_test/main/test_nvs.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3255,6 +3255,71 @@ TEST_CASE("check and read data from partition generated via manufacturing utilit
32553255
}
32563256
}
32573257

3258+
TEST_CASE("nvs multiple write with same key but different types", "[nvs][xxx]")
3259+
{
3260+
PartitionEmulationFixture f(0, 10);
3261+
3262+
nvs_handle_t handle_1;
3263+
const uint32_t NVS_FLASH_SECTOR = 6;
3264+
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3;
3265+
TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);)
3266+
3267+
for (uint16_t j = NVS_FLASH_SECTOR; j < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; ++j) {
3268+
f.erase(j);
3269+
}
3270+
TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(),
3271+
NVS_FLASH_SECTOR,
3272+
NVS_FLASH_SECTOR_COUNT_MIN));
3273+
3274+
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle_1));
3275+
3276+
nvs_erase_all(handle_1);
3277+
3278+
int32_t v32;
3279+
int8_t v8;
3280+
3281+
TEST_ESP_OK(nvs_set_i32(handle_1, "foo", (int32_t)12345678));
3282+
TEST_ESP_OK(nvs_set_i8(handle_1, "foo", (int8_t)12));
3283+
TEST_ESP_OK(nvs_set_i8(handle_1, "foo", (int8_t)34));
3284+
3285+
#ifdef CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY
3286+
// Legacy behavior
3287+
// First use of key hooks data type until removed by nvs_erase_key. Alternative re-use of same key with different
3288+
// data type is written to the storage as hidden active value. It is returned by nvs_get function after nvs_erase_key is called.
3289+
// Mixing more than 2 data types brings undefined behavior. It is not tested here.
3290+
3291+
TEST_ESP_ERR(nvs_get_i8(handle_1, "foo", &v8), ESP_ERR_NVS_NOT_FOUND);
3292+
TEST_ESP_OK(nvs_get_i32(handle_1, "foo", &v32));
3293+
TEST_ESP_OK(nvs_erase_key(handle_1, "foo"));
3294+
3295+
TEST_ESP_OK(nvs_get_i8(handle_1, "foo", &v8));
3296+
TEST_ESP_ERR(nvs_get_i32(handle_1, "foo", &v32), ESP_ERR_NVS_NOT_FOUND);
3297+
TEST_ESP_OK(nvs_erase_key(handle_1, "foo"));
3298+
3299+
TEST_ESP_OK(nvs_get_i8(handle_1, "foo", &v8));
3300+
TEST_ESP_ERR(nvs_get_i32(handle_1, "foo", &v32), ESP_ERR_NVS_NOT_FOUND);
3301+
TEST_ESP_OK(nvs_erase_key(handle_1, "foo"));
3302+
3303+
TEST_ESP_ERR(nvs_erase_key(handle_1, "foo"), ESP_ERR_NVS_NOT_FOUND);
3304+
#else
3305+
// New behavior
3306+
// Latest nvs_set call replaces any existing value. Only one active value under the key exists in storage
3307+
3308+
TEST_ESP_OK(nvs_get_i8(handle_1, "foo", &v8));
3309+
TEST_ESP_ERR(nvs_get_i32(handle_1, "foo", &v32), ESP_ERR_NVS_NOT_FOUND);
3310+
TEST_ESP_OK(nvs_erase_key(handle_1, "foo"));
3311+
3312+
TEST_ESP_ERR(nvs_get_i8(handle_1, "foo", &v8), ESP_ERR_NVS_NOT_FOUND);
3313+
TEST_ESP_ERR(nvs_get_i32(handle_1, "foo", &v32), ESP_ERR_NVS_NOT_FOUND);
3314+
TEST_ESP_ERR(nvs_erase_key(handle_1, "foo"), ESP_ERR_NVS_NOT_FOUND);
3315+
#endif
3316+
3317+
nvs_close(handle_1);
3318+
3319+
TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME));
3320+
}
3321+
3322+
32583323
/* Add new tests above */
32593324
/* This test has to be the final one */
32603325

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY=n
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY=y

components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
1-
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
2-
//
3-
// Licensed under the Apache License, Version 2.0 (the "License");
4-
// you may not use this file except in compliance with the License.
5-
// You may obtain a copy of the License at
6-
7-
// http://www.apache.org/licenses/LICENSE-2.0
8-
//
9-
// Unless required by applicable law or agreed to in writing, software
10-
// distributed under the License is distributed on an "AS IS" BASIS,
11-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12-
// See the License for the specific language governing permissions and
13-
// limitations under the License.
1+
/*
2+
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
146
#include <stdio.h>
157
#include "unity.h"
168
#include "test_fixtures.hpp"
@@ -863,7 +855,7 @@ void test_Page_calcEntries__uninit()
863855
NVSPageFixture fix;
864856
TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state());
865857

866-
nvs_stats_t nvsStats = {0, 0, 0, 0};
858+
nvs_stats_t nvsStats = {0, 0, 0, 0, 0};
867859

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

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

891-
nvs_stats_t nvsStats = {0, 0, 0, 0};
883+
nvs_stats_t nvsStats = {0, 0, 0, 0, 0};
892884

893885
TEST_ASSERT_EQUAL(ESP_OK, page.calcEntries(nvsStats));
894886
TEST_ASSERT_EQUAL(0, nvsStats.used_entries);
@@ -901,7 +893,7 @@ void test_Page_calcEntries__active_wo_blob()
901893
{
902894
NVSValidPageFixture fix;
903895

904-
nvs_stats_t nvsStats = {0, 0, 0, 0};
896+
nvs_stats_t nvsStats = {0, 0, 0, 0, 0};
905897

906898
TEST_ASSERT_EQUAL(ESP_OK, fix.page.calcEntries(nvsStats));
907899
TEST_ASSERT_EQUAL(2, nvsStats.used_entries);
@@ -914,7 +906,7 @@ void test_Page_calcEntries__active_with_blob()
914906
{
915907
NVSValidBlobPageFixture fix;
916908

917-
nvs_stats_t nvsStats = {0, 0, 0, 0};
909+
nvs_stats_t nvsStats = {0, 0, 0, 0, 0};
918910

919911
TEST_ASSERT_EQUAL(ESP_OK, fix.page.calcEntries(nvsStats));
920912
TEST_ASSERT_EQUAL(4, nvsStats.used_entries);
@@ -927,7 +919,7 @@ void test_Page_calcEntries__invalid()
927919
{
928920
Page page;
929921

930-
nvs_stats_t nvsStats = {0, 0, 0, 0};
922+
nvs_stats_t nvsStats = {0, 0, 0, 0, 0};
931923

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

components/nvs_flash/src/nvs_page.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -878,7 +878,7 @@ esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, si
878878
end = ENTRY_COUNT;
879879
}
880880

881-
if (nsIndex != NS_ANY && datatype != ItemType::ANY && key != NULL) {
881+
if (nsIndex != NS_ANY && key != NULL) {
882882
size_t cachedIndex = mHashList.find(start, Item(nsIndex, datatype, 0, key, chunkIdx));
883883
if (cachedIndex < ENTRY_COUNT) {
884884
start = cachedIndex;

components/nvs_flash/src/nvs_storage.cpp

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -285,13 +285,27 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key
285285
}
286286

287287
Page* findPage = nullptr;
288+
bool matchedTypePageFound = false;
288289
Item item;
289290

290291
esp_err_t err;
291292
if (datatype == ItemType::BLOB) {
292293
err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item);
294+
if(err == ESP_OK) {
295+
matchedTypePageFound = true;
296+
}
293297
} else {
298+
#ifdef CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY
294299
err = findItem(nsIndex, datatype, key, findPage, item);
300+
if(err == ESP_OK && findPage != nullptr) {
301+
matchedTypePageFound = true;
302+
}
303+
#else
304+
err = findItem(nsIndex, ItemType::ANY, key, findPage, item);
305+
if(err == ESP_OK && datatype == item.datatype) {
306+
matchedTypePageFound = true;
307+
}
308+
#endif
295309
}
296310

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

338-
if (findPage) {
352+
if (matchedTypePageFound) {
339353
/* Erase the blob with earlier version*/
340354
err = eraseMultiPageBlob(nsIndex, key, prevStart);
341355

@@ -358,7 +372,7 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key
358372
// Do a sanity check that the item in question is actually being modified.
359373
// If it isn't, it is cheaper to purposefully not write out new data.
360374
// since it may invoke an erasure of flash.
361-
if (findPage != nullptr &&
375+
if (matchedTypePageFound &&
362376
findPage->cmpItem(nsIndex, datatype, key, data, dataSize) == ESP_OK) {
363377
return ESP_OK;
364378
}
@@ -392,12 +406,20 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key
392406
if (findPage) {
393407
if (findPage->state() == Page::PageState::UNINITIALIZED ||
394408
findPage->state() == Page::PageState::INVALID) {
409+
#ifdef CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY
395410
err = findItem(nsIndex, datatype, key, findPage, item);
411+
#else
412+
err = findItem(nsIndex, ItemType::ANY, key, findPage, item);
413+
#endif
396414
if (err != ESP_OK) {
397415
return err;
398416
}
399417
}
418+
#ifdef CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY
400419
err = findPage->eraseItem(nsIndex, datatype, key);
420+
#else
421+
err = findPage->eraseItem(nsIndex, ItemType::ANY, key);
422+
#endif
401423
if (err == ESP_ERR_FLASH_OP_FAIL) {
402424
return ESP_ERR_NVS_REMOVE_FAILED;
403425
}

docs/en/api-reference/storage/nvs_flash.rst

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,9 @@ NVS operates on key-value pairs. Keys are ASCII strings; the maximum key length
3535

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

38-
Keys are required to be unique. Assigning a new value to an existing key works as follows:
38+
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.
3939

40-
- If the new value is of the same type as the old one, value is updated.
41-
- If the new value has a different data type, an error is returned.
42-
43-
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.
40+
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.
4441

4542

4643
Namespaces

docs/zh_CN/api-reference/storage/nvs_flash.rst

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,9 @@ NVS 的操作对象为键值对,其中键是 ASCII 字符串,当前支持的
3535

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

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

40-
- 如果新旧值数据类型相同,则更新值;
41-
- 如果新旧值数据类型不同,则返回错误。
42-
43-
读取值时也会执行数据类型检查。如果读取操作的数据类型与该值的数据类型不匹配,则返回错误。
40+
读取值时会执行数据类型检查。如果读取操作预期的数据类型与对应键的数据类型不匹配,则返回错误。
4441

4542

4643
命名空间

tools/ci/check_copyright_ignore.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,6 @@ components/mbedtls/port/include/sha512_alt.h
586586
components/mbedtls/port/sha/dma/include/esp_sha_dma_priv.h
587587
components/mbedtls/port/sha/dma/sha.c
588588
components/mbedtls/port/sha/parallel_engine/sha.c
589-
components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp
590589
components/nvs_flash/include/nvs_handle.hpp
591590
components/nvs_flash/src/nvs_cxx_api.cpp
592591
components/nvs_flash/src/nvs_encrypted_partition.hpp

0 commit comments

Comments
 (0)