ESP-IDF-SVC NVS contains() Bug Report - String Values Not Detected
Bug description
The contains() method in EspNvs returns false for string values that actually exist in NVS partitions, even though the same strings can be successfully retrieved using get_str(). This creates inconsistent behavior where data exists in flash but appears missing to the application.
Root Cause: The contains() method internally calls len(), which only checks for u64 and blob data types via nvs_get_u64() and nvs_get_blob(). It never calls nvs_get_str() to check for string values stored with nvs_set_str().
Would you like to work on a fix? Yes, we have identified the exact issue and can provide a complete solution.
To Reproduce
- Create an NVS partition in your ESP32 project (default or custom)
- Initialize an
EspNvs or EspCustomNvs instance with a namespace
- Store a string value using
set_str(key, "some_string")
- Call
contains(key) - it returns false
- Call
get_str(key, buffer) - it successfully retrieves the string
- Reboot the device and repeat steps 4-5 - same inconsistent behavior
Minimal reproduction code:
use esp_idf_svc::nvs::{EspDefaultNvsPartition, EspNvs};
// or for custom partitions: use esp_idf_svc::nvs::{EspCustomNvsPartition, EspCustomNvs};
fn reproduce_bug() -> Result<(), Box<dyn std::error::Error>> {
// Using default partition (bug affects all partition types)
let partition = EspDefaultNvsPartition::take()?;
let mut nvs = EspNvs::new(partition, "test_ns", true)?;
// Alternative: Custom partition
// let partition = EspCustomNvsPartition::take("custom_partition")?;
// let mut nvs = EspCustomNvs::new(partition, "test_ns", true)?;
// Set a string value
nvs.set_str("test_key", "test_value")?;
// BUG: contains() returns false for string values
let contains_result = nvs.contains("test_key")?;
println!("contains(): {}", contains_result); // Prints: false
// But get_str() works correctly
let mut buffer = [0u8; 64];
let get_result = nvs.get_str("test_key", &mut buffer)?;
println!("get_str(): {:?}", get_result); // Prints: Some("test_value")
// Inconsistent behavior: data exists but contains() can't find it
assert!(contains_result != get_result.is_some()); // This assertion passes, proving the bug
Ok(())
}
Expected behavior
contains(key) should return true for any existing NVS entry regardless of data type (u8, u16, u32, u64, i8, i16, i32, i64, string, blob). The method should be consistent with other NVS operations like get_str(), get_u32(), etc.
Expected behavior:
nvs.set_str("key", "value") - stores string successfully ✅
nvs.contains("key") - returns true ✅ (currently returns false ❌)
nvs.get_str("key", buffer) - retrieves string successfully ✅
Root Cause Analysis
The bug is in esp-idf-svc/src/nvs.rs:
pub fn contains(&self, name: &str) -> Result<bool, EspError> {
self.len(name).map(|v| v.is_some()) // ← calls len()
}
fn len(&self, name: &str) -> Result<Option<usize>, EspError> {
let c_key = to_cstring_arg(name)?;
let mut value: u_int64_t = 0;
// check for u64 value
match unsafe { nvs_get_u64(self.1, c_key.as_ptr(), &mut value as *mut _) } {
ESP_ERR_NVS_NOT_FOUND => {
// check for blob value, by getting blob length
let mut len = 0;
match unsafe {
nvs_get_blob(self.1, c_key.as_ptr(), ptr::null_mut(), &mut len as *mut _)
} {
ESP_ERR_NVS_NOT_FOUND => Ok(None), // ← STOPS HERE, never checks strings!
// ... only handles blob case
}
}
// ... only handles u64 case
}
}
Problem: The len() method only checks nvs_get_u64() and nvs_get_blob(). It never calls nvs_get_str() for string values.
Proposed Solution
Option 1: Fix the len() method to check string values
fn len(&self, name: &str) -> Result<Option<usize>, EspError> {
let c_key = to_cstring_arg(name)?;
// Check for u64 value
let mut u64_value: u_int64_t = 0;
match unsafe { nvs_get_u64(self.1, c_key.as_ptr(), &mut u64_value as *mut _) } {
ESP_ERR_NVS_NOT_FOUND => {
// Check for blob value
let mut blob_len = 0;
match unsafe { nvs_get_blob(self.1, c_key.as_ptr(), ptr::null_mut(), &mut blob_len as *mut _) } {
ESP_ERR_NVS_NOT_FOUND => {
// ← ADD THIS: Check for string value
let mut str_len = 0;
match unsafe { nvs_get_str(self.1, c_key.as_ptr(), ptr::null_mut(), &mut str_len as *mut _) } {
ESP_ERR_NVS_NOT_FOUND => Ok(None),
err => {
esp!(err)?;
Ok(Some(str_len))
}
}
}
err => {
esp!(err)?;
Ok(Some(blob_len))
}
}
}
err => {
esp!(err)?;
Ok(Some(8)) // u64 is always 8 bytes
}
}
}
Option 2: Use nvs_find_key() in contains() method (Recommended)
pub fn contains(&self, name: &str) -> Result<bool, EspError> {
let c_key = to_cstring_arg(name)?;
let mut entry_type: nvs_type_t = nvs_type_t_NVS_TYPE_ANY;
let result = unsafe {
nvs_find_key(self.1, c_key.as_ptr(), &mut entry_type as *mut _)
};
match result {
ESP_OK => Ok(true),
ESP_ERR_NVS_NOT_FOUND => Ok(false),
err => {
esp!(err)?;
Ok(false) // This line should never be reached due to esp!() macro
}
}
}
Option 3: Enhanced find_key() method that returns data type (Additional Feature)
The nvs_find_key function can also return the data type of the found entry. Consider adding this as a new method:
pub fn find_key(&self, name: &str) -> Result<Option<nvs_type_t>, EspError> {
let c_key = to_cstring_arg(name)?;
let mut entry_type: nvs_type_t = nvs_type_t_NVS_TYPE_ANY;
let result = unsafe {
nvs_find_key(self.1, c_key.as_ptr(), &mut entry_type as *mut _)
};
match result {
ESP_OK => Ok(Some(entry_type)),
ESP_ERR_NVS_NOT_FOUND => Ok(None),
err => {
esp!(err)?;
Ok(None) // This line should never be reached due to esp!() macro
}
}
}
This would allow applications to:
// Check existence and get type in one call
if let Some(data_type) = nvs.find_key("my_key")? {
match data_type {
nvs_type_t_NVS_TYPE_STR => { /* handle string */ },
nvs_type_t_NVS_TYPE_U32 => { /* handle u32 */ },
nvs_type_t_NVS_TYPE_BLOB => { /* handle blob */ },
_ => { /* handle other types */ },
}
}
Options 2 and 3 are recommended because:
nvs_find_key() is designed specifically to check for key existence regardless of data type
- It's more efficient than trying multiple
nvs_get_*() calls
- It's more robust and handles all current and future NVS data types
- It matches the semantic expectation of a
contains() method
- Uses the exact pattern from ESP-IDF commit 8740888 that introduced
nvs_find_key() on Dec 8, 2023
- Option 3 additionally provides data type information, which can be useful for applications
Note: The nvs_find_key function was added to ESP-IDF specifically for this use case - checking key existence regardless of data type. The implementation above follows the exact error handling pattern used in the official ESP-IDF test suite.
Environment
- Crate (esp-idf-svc) version: 0.51.0
- ESP-IDF branch or tag: release/v5.2
- Target device (MCU): esp32
- OS: Windows 11
Impact
This bug affects anyone using string values in NVS partitions with esp-idf-svc, making it a critical issue for production applications. It causes:
- Logic errors where applications think data doesn't exist when it actually does
- Unnecessary re-initialization of configuration values
- Data inconsistency between different NVS operation results
- Debugging confusion where data appears in partition dumps but
contains() returns false
Additional Notes
- The bug affects all NVS partitions (default and custom) and string values
get_str(), set_str(), and other string operations work correctly
- Only the
contains() method is affected
- Binary partition dumps show the string entries exist in flash
- This suggests the bug is in the esp-idf-svc wrapper, not the underlying ESP-IDF NVS implementation
ESP-IDF-SVC NVS contains() Bug Report - String Values Not Detected
Bug description
The
contains()method inEspNvsreturnsfalsefor string values that actually exist in NVS partitions, even though the same strings can be successfully retrieved usingget_str(). This creates inconsistent behavior where data exists in flash but appears missing to the application.Root Cause: The
contains()method internally callslen(), which only checks foru64andblobdata types vianvs_get_u64()andnvs_get_blob(). It never callsnvs_get_str()to check for string values stored withnvs_set_str().Would you like to work on a fix? Yes, we have identified the exact issue and can provide a complete solution.
To Reproduce
EspNvsorEspCustomNvsinstance with a namespaceset_str(key, "some_string")contains(key)- it returnsfalseget_str(key, buffer)- it successfully retrieves the stringMinimal reproduction code:
Expected behavior
contains(key)should returntruefor any existing NVS entry regardless of data type (u8, u16, u32, u64, i8, i16, i32, i64, string, blob). The method should be consistent with other NVS operations likeget_str(),get_u32(), etc.Expected behavior:
nvs.set_str("key", "value")- stores string successfully ✅nvs.contains("key")- returnstrue✅ (currently returnsfalse❌)nvs.get_str("key", buffer)- retrieves string successfully ✅Root Cause Analysis
The bug is in
esp-idf-svc/src/nvs.rs:Problem: The
len()method only checksnvs_get_u64()andnvs_get_blob(). It never callsnvs_get_str()for string values.Proposed Solution
Option 1: Fix the
len()method to check string valuesOption 2: Use
nvs_find_key()incontains()method (Recommended)Option 3: Enhanced
find_key()method that returns data type (Additional Feature)The
nvs_find_keyfunction can also return the data type of the found entry. Consider adding this as a new method:This would allow applications to:
Options 2 and 3 are recommended because:
nvs_find_key()is designed specifically to check for key existence regardless of data typenvs_get_*()callscontains()methodnvs_find_key()on Dec 8, 2023Note: The
nvs_find_keyfunction was added to ESP-IDF specifically for this use case - checking key existence regardless of data type. The implementation above follows the exact error handling pattern used in the official ESP-IDF test suite.Environment
Impact
This bug affects anyone using string values in NVS partitions with esp-idf-svc, making it a critical issue for production applications. It causes:
contains()returns falseAdditional Notes
get_str(),set_str(), and other string operations work correctlycontains()method is affected