Skip to main content

esp_nvs_partition_tool/
partition.rs

1pub(crate) mod generator;
2pub(crate) mod parser;
3
4use std::path::PathBuf;
5
6pub use esp_nvs::MAX_KEY_LENGTH;
7
8use crate::error::Error;
9
10/// A single NVS key-value entry belonging to a namespace.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct NvsEntry {
13    /// The namespace this entry belongs to (max 15 bytes).
14    pub namespace: String,
15    /// The key identifying this entry within its namespace (max 15 bytes).
16    pub key: String,
17    /// The payload — either inline data or a reference to an external file.
18    pub content: EntryContent,
19}
20
21/// The content of an NVS entry — either inline data or a file reference.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum EntryContent {
24    /// Inline data whose encoding is determined by the [`DataValue`] variant.
25    Data(DataValue),
26    /// A reference to a file whose content will be read at generation time.
27    File {
28        /// How the file content is interpreted.
29        encoding: FileEncoding,
30        /// Path to the file (resolved relative to the CSV location).
31        file_path: PathBuf,
32    },
33}
34
35/// The encoding used to interpret file content for NVS file entries.
36///
37/// `String` reads the file as UTF-8 text. `Hex2Bin` decodes hex-encoded
38/// content. `Base64` decodes base64-encoded content. `Binary` uses the
39/// raw bytes directly.
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum FileEncoding {
42    /// UTF-8 text.
43    String,
44    /// Hex-encoded binary data.
45    Hex2Bin,
46    /// Base64-encoded binary data.
47    Base64,
48    /// Raw binary data.
49    Binary,
50}
51
52impl std::str::FromStr for FileEncoding {
53    type Err = Error;
54
55    fn from_str(s: &str) -> Result<Self, Self::Err> {
56        match s {
57            "string" => Ok(Self::String),
58            "hex2bin" => Ok(Self::Hex2Bin),
59            "base64" => Ok(Self::Base64),
60            "binary" => Ok(Self::Binary),
61            _ => Err(Error::InvalidEncoding(s.to_string())),
62        }
63    }
64}
65
66impl FileEncoding {
67    /// Return the encoding name as a static string slice.
68    pub fn as_str(&self) -> &'static str {
69        match self {
70            Self::String => "string",
71            Self::Hex2Bin => "hex2bin",
72            Self::Base64 => "base64",
73            Self::Binary => "binary",
74        }
75    }
76}
77
78impl std::fmt::Display for FileEncoding {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        f.write_str(self.as_str())
81    }
82}
83
84/// A concrete data value stored in an NVS entry.
85#[derive(Debug, Clone, PartialEq, Eq)]
86pub enum DataValue {
87    /// Unsigned 8-bit integer.
88    U8(u8),
89    /// Signed 8-bit integer.
90    I8(i8),
91    /// Unsigned 16-bit integer.
92    U16(u16),
93    /// Signed 16-bit integer.
94    I16(i16),
95    /// Unsigned 32-bit integer.
96    U32(u32),
97    /// Signed 32-bit integer.
98    I32(i32),
99    /// Unsigned 64-bit integer.
100    U64(u64),
101    /// Signed 64-bit integer.
102    I64(i64),
103    /// UTF-8 string (without null terminator).
104    String(String),
105    /// Opaque byte blob.
106    Binary(Vec<u8>),
107}
108
109impl DataValue {
110    /// Return the CSV encoding column string for this value.
111    ///
112    /// `Binary` maps to `"base64"` because blobs parsed from a binary
113    /// partition have no original CSV encoding, and the ESP-IDF convention
114    /// is base64.
115    pub fn encoding_str(&self) -> &'static str {
116        match self {
117            Self::U8(_) => "u8",
118            Self::I8(_) => "i8",
119            Self::U16(_) => "u16",
120            Self::I16(_) => "i16",
121            Self::U32(_) => "u32",
122            Self::I32(_) => "i32",
123            Self::U64(_) => "u64",
124            Self::I64(_) => "i64",
125            Self::String(_) => "string",
126            Self::Binary(_) => "base64",
127        }
128    }
129}
130
131impl std::fmt::Display for DataValue {
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        match self {
134            Self::U8(v) => write!(f, "{v}"),
135            Self::I8(v) => write!(f, "{v}"),
136            Self::U16(v) => write!(f, "{v}"),
137            Self::I16(v) => write!(f, "{v}"),
138            Self::U32(v) => write!(f, "{v}"),
139            Self::I32(v) => write!(f, "{v}"),
140            Self::U64(v) => write!(f, "{v}"),
141            Self::I64(v) => write!(f, "{v}"),
142            Self::String(s) => f.write_str(s),
143            Self::Binary(b) => {
144                use base64::Engine;
145                f.write_str(&base64::engine::general_purpose::STANDARD.encode(b))
146            }
147        }
148    }
149}
150
151impl NvsEntry {
152    /// Create a new entry with inline data.
153    ///
154    /// The encoding is derived automatically from the [`DataValue`] variant.
155    pub fn new_data(namespace: String, key: String, value: DataValue) -> Self {
156        Self {
157            namespace,
158            key,
159            content: EntryContent::Data(value),
160        }
161    }
162
163    /// Create a new entry that references an external file.
164    ///
165    /// The file content will be read and converted according to `encoding`
166    /// at partition generation time.
167    pub fn new_file(namespace: String, key: String, encoding: FileEncoding, file_path: PathBuf) -> Self {
168        Self {
169            namespace,
170            key,
171            content: EntryContent::File { encoding, file_path },
172        }
173    }
174
175    /// Replace the entry's content.
176    pub fn set_content(&mut self, content: EntryContent) {
177        self.content = content;
178    }
179
180    /// Set the content to inline data.
181    ///
182    /// Convenience method for `set_content(EntryContent::Data(value))`.
183    pub fn set_data(&mut self, value: DataValue) {
184        self.content = EntryContent::Data(value);
185    }
186
187    /// Set the content to a file reference.
188    ///
189    /// Convenience method for `set_content(EntryContent::File { .. })`.
190    pub fn set_file(&mut self, encoding: FileEncoding, file_path: PathBuf) {
191        self.content = EntryContent::File { encoding, file_path };
192    }
193}
194
195/// Validate that `key` is non-empty and within the NVS maximum key length.
196pub(crate) fn validate_key(key: &str) -> Result<(), Error> {
197    if key.is_empty() {
198        return Err(Error::InvalidKey("key must not be empty".to_string()));
199    }
200    if key.len() > MAX_KEY_LENGTH {
201        return Err(Error::InvalidKey(format!(
202            "key '{}' is too long (max {} characters)",
203            key, MAX_KEY_LENGTH
204        )));
205    }
206    Ok(())
207}