Skip to content

fix(nvs): std::string reads no longer include the zero terminator in size() (#854) - #855

Merged
finger563 merged 3 commits into
mainfrom
fix/nvs-string-trailing-nul
Oct 22, 2026
Merged

finger563 merged 3 commits into
mainfrom
fix/nvs-string-trailing-nul

Conversation

@finger563

@finger563 finger563 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #854. NvsHandle::get() for std::string sized the string with get_item_size(), which for strings counts the zero terminator (IDF documents this on get_item_size), and get_string() then wrote that terminator into the string. A stored "ABCDEFG" came back with size() == 8 and a trailing '\0', so == / compare() failed and serialised output carried an embedded NUL, exactly as reported.

The change is confined to the two std::string read overloads in nvs_handle_espp.hpp (get(key, std::string&, ec) and get(key, std::string&, default, ec), which Nvs::get_var / get_or_set_var dispatch to): after a successful get_string() the string is resized to len - 1, so size() is the stored string's length. The generic / bool / float paths and the string write path are untouched; the default-value branch still assigns the default unchanged. The doc comments say what size() now means, and the example prints the size next to each string it reads back.

Example validation section

The nvs example now ends with a self-check section (//! [nvs validation]) that exercises the NVS operations with explicit pass / fail checks and prints a summary (NVS validation: 18/18 checks passed - PASS, failing checks listed after it). It runs in its own validate namespace that is erased before and after, so every boot runs the same sequence:

  • integer / bool / float round trips through get_or_set_var (missing key stores and returns the default) and set_var / get_var
  • strings: default on a missing key, stored value on an existing key, round trip with exact size() and last character, shorter overwrite into a longer destination, empty string, 64-character string, strlen(c_str()) == size(), no embedded '\0'
  • erase key (key gone, others survive), read of a missing key fails and leaves the value alone, erase namespace (all keys gone)

The string checks are the regression test for #854: before the fix, size() is one too large and the equality checks fail.

Test plan

  • components/nvs/example builds for esp32 (IDF 6.1) and esp32p4
  • On hardware: the example prints String = 'default' (7 bytes) on first boot and String = 'hello' (5 bytes) after the set; the reporter's test_NVS() prints size=7 for both reads
  • On hardware: the validation section prints NVS validation: 18/18 checks passed - PASS

🤖 Generated with Claude Code

https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU

…size()

NvsHandle::get() for std::string sized the string with get_item_size(), which
for strings counts the zero terminator (IDF documents this), and get_string()
then wrote that terminator into the string: a stored "ABCDEFG" came back with
size() 8 and a trailing '\0', so comparisons failed and serialised output
carried an embedded NUL. Both std::string read overloads (plain, and with a
default value) now drop the terminator after a successful read, so size() is
the stored string's length. Nothing else changes: the generic / bool / float
paths and the string write path are untouched.

The example prints the size next to each string it reads back.

Fixes #854

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

✅Static analysis result - no issues found! ✅

finger563 and others added 2 commits October 21, 2026 07:25
…mmary

Append a validation section to the nvs example that exercises integer, bool,
float and string round trips through get_or_set_var / set_var / get_var plus
key and namespace erase, with explicit pass / fail checks and a final
"N/M checks passed" summary. The string checks assert that size() is the
stored string's length (no zero terminator in the value, #854). The checks
run in their own namespace that is erased before and after, so every boot
runs the same sequence. Documented in the example README.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
… validation

`s == "default"` already compares the size, so the explicit `s.size() == N`
next to it is always true once the equality holds (knownConditionTrueFalse).
The 64-character and `strlen(c_str()) == size()` checks still assert the
#854 property directly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
@finger563
finger563 merged commit 20bf105 into main Oct 22, 2026
173 of 175 checks passed
@finger563
finger563 deleted the fix/nvs-string-trailing-nul branch October 22, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

espp::Nvs::get_var / get_or_set_var returns std::string with a trailing '\0' included in size()

1 participant