esp_nvs_partition_tool/lib.rs
1//! ESP-IDF compatible NVS (Non-Volatile Storage) partition table parser and
2//! generator.
3
4pub mod error;
5pub mod partition;
6
7mod csv;
8
9pub use error::Error;
10pub use partition::{
11 DataValue,
12 EntryContent,
13 FileEncoding,
14 MAX_KEY_LENGTH,
15 NvsEntry,
16};
17
18/// A collection of NVS key-value entries, optionally spanning multiple
19/// namespaces.
20///
21/// This is the primary in-memory representation used by the CSV and binary
22/// parsers/generators.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct NvsPartition {
25 /// The ordered list of entries in this partition.
26 pub entries: Vec<NvsEntry>,
27}
28
29impl NvsPartition {
30 /// Attempt to parse either a binary or CSV NVS partition from the given
31 /// input.
32 ///
33 /// Binary partitions are detected by checking whether the first byte
34 /// looks like an NVS page-state value (≥ 0x80). If so the data is
35 /// parsed as binary; otherwise it is interpreted as CSV text.
36 pub fn try_from<D>(data: D) -> Result<Self, Error>
37 where
38 D: Into<Vec<u8>>,
39 {
40 let input: Vec<u8> = data.into();
41
42 // NVS binary partitions start with the page state (u32 LE).
43 // Valid page states (Active = 0xFE, Full = 0xFC, Freeing = 0xF8, etc.)
44 // all have their first byte well above 0x80, while CSV text is always
45 // valid ASCII (< 0x80). We use 0x80 as the threshold to reliably
46 // distinguish the two formats.
47 if input.first().is_some_and(|&b| b >= 0x80) {
48 Self::try_from_bytes(input)
49 } else {
50 Self::try_from_str(
51 String::from_utf8(input).map_err(|e| Error::InvalidValue(format!("input is not valid UTF-8: {e}")))?,
52 )
53 }
54 }
55
56 /// Attempt to parse a CSV NVS partition from the given string.
57 ///
58 /// File-type entries store the path exactly as written in the CSV.
59 pub fn try_from_str<S>(string: S) -> Result<Self, Error>
60 where
61 S: Into<String>,
62 {
63 csv::parser::parse_csv(&string.into())
64 }
65
66 /// Attempt to parse a binary NVS partition from the given bytes.
67 pub fn try_from_bytes<B>(bytes: B) -> Result<Self, Error>
68 where
69 B: Into<Vec<u8>>,
70 {
71 partition::parser::parse_binary_data(&bytes.into())
72 }
73
74 /// Serialize this partition to CSV and return the content as a `String`.
75 ///
76 /// Entries are written in their original insertion order. A namespace
77 /// header row is emitted whenever the namespace changes between
78 /// consecutive entries. `Encoding::Binary` values are serialized as
79 /// base64, matching the ESP-IDF `nvs_partition_tool` convention.
80 pub fn to_csv(self) -> Result<String, Error> {
81 csv::writer::write_csv_content(self)
82 }
83
84 /// Generate an NVS partition binary in memory.
85 ///
86 /// `size` must be a multiple of 4096 (the ESP-IDF flash sector size).
87 pub fn generate_partition(&self, size: usize) -> Result<Vec<u8>, Error> {
88 partition::generator::generate_partition_data(self, size)
89 }
90
91 /// Find an entry with the given name in the NVS partition.
92 pub fn find(&self, name: &str) -> Option<&NvsEntry> {
93 self.entries.iter().find(|e| e.key == name)
94 }
95
96 /// Find an entry by key, returning a mutable reference.
97 pub fn find_mut(&mut self, name: &str) -> Option<&mut NvsEntry> {
98 self.entries.iter_mut().find(|e| e.key == name)
99 }
100}