Skip to main content

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}