Skip to main content

esp_nvs/
lib.rs

1#![doc = include_str ! ("../README.md")]
2#![cfg_attr(not(target_arch = "x86_64"), no_std)]
3
4pub mod error;
5mod get;
6mod internal;
7pub mod platform;
8mod raw;
9mod set;
10mod u24;
11
12/// Maximum Key length is 15 bytes + 1 byte for the null terminator.
13const MAX_KEY_LENGTH: usize = 15;
14const MAX_KEY_NUL_TERMINATED_LENGTH: usize = MAX_KEY_LENGTH + 1;
15
16/// A 16-byte key used for NVS storage (15 characters + null terminator)
17#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub struct Key([u8; MAX_KEY_NUL_TERMINATED_LENGTH]);
19
20impl Key {
21    /// Creates a 16 byte, null-padded byte array used as key for values and namespaces.
22    ///
23    /// Usage: `Key::from_array(b"my_key")`
24    ///
25    /// Tip: use a const context if possible to ensure that the key is transformed at compile time:
26    ///   `let my_key = const { Key::from_array(b"my_key") };`
27    pub const fn from_array<const M: usize>(src: &[u8; M]) -> Self {
28        assert!(M <= MAX_KEY_LENGTH);
29        let mut dst = [0u8; MAX_KEY_NUL_TERMINATED_LENGTH];
30        let mut i = 0;
31        while i < M {
32            dst[i] = src[i];
33            i += 1;
34        }
35        Self(dst)
36    }
37
38    /// Creates a 16 byte, null-padded byte array used as key for values and namespaces.
39    ///
40    /// Usage: `Key::from_slice(b"my_key")`
41    ///
42    /// Tip: use a const context if possible to ensure that the key is transformed at compile time:
43    ///   `let my_key = const { Key::from_slice("my_key".as_bytes()) };`
44    pub const fn from_slice(src: &[u8]) -> Self {
45        assert!(src.len() <= MAX_KEY_LENGTH);
46        let mut dst = [0u8; MAX_KEY_NUL_TERMINATED_LENGTH];
47        let mut i = 0;
48        while i < src.len() {
49            dst[i] = src[i];
50            i += 1;
51        }
52        Self(dst)
53    }
54
55    /// Creates a 16 byte, null-padded byte array used as key for values and namespaces.
56    ///
57    /// Usage: `Key::from_str("my_key")`
58    ///
59    /// Tip: use a const context if possible to ensure that the key is transformed at compile time:
60    ///   `let my_key = const { Key::from_str("my_key") };`
61    pub const fn from_str(s: &str) -> Self {
62        let bytes = s.as_bytes();
63        Self::from_slice(bytes)
64    }
65
66    /// Converts a key to a byte array.
67    pub const fn as_bytes(&self) -> &[u8; MAX_KEY_NUL_TERMINATED_LENGTH] {
68        &self.0
69    }
70}
71
72impl fmt::Debug for Key {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        // for debug representation, print as binary string
75        write!(f, "Key(b\"")?;
76
77        // skip the null terminator at the end, which is always null,
78        // and might be confusing in the output if you passed a 15-byte key,
79        // and it shows a \0 at the end.
80        for &byte in &self.0[..self.0.len() - 1] {
81            // escape_default would escape 0 as \x00, but \0 is more readable
82            if byte == 0 {
83                write!(f, "\\0")?;
84                continue;
85            }
86
87            write!(f, "{}", core::ascii::escape_default(byte))?;
88        }
89
90        write!(f, "\")")
91    }
92}
93
94#[cfg(feature = "defmt")]
95impl defmt::Format for Key {
96    fn format(&self, f: defmt::Formatter) {
97        // for defmt representation, print as binary string
98        defmt::write!(f, "Key(b\"");
99
100        // skip the null terminator at the end, which is always null,
101        // and might be confusing in the output if you passed a 15-byte key,
102        // and it shows a \0 at the end. We can't use core::ascii::escape_default
103        // for defmt so some characters are manually escaped.
104        for &byte in &self.0[..self.0.len() - 1] {
105            match byte {
106                b'\t' => defmt::write!(f, "\\t"),
107                b'\n' => defmt::write!(f, "\\n"),
108                b'\r' => defmt::write!(f, "\\r"),
109                b'\\' => defmt::write!(f, "\\\\"),
110                b'"' => defmt::write!(f, "\\\""),
111                0x20..=0x7e => defmt::write!(f, "{}", byte as char),
112                _ => defmt::write!(f, "\\x{:02x}", byte),
113            }
114        }
115
116        defmt::write!(f, "\")");
117    }
118}
119
120impl AsRef<[u8]> for Key {
121    fn as_ref(&self) -> &[u8] {
122        self.as_bytes()
123    }
124}
125
126pub use get::Get;
127pub use set::Set;
128
129extern crate alloc;
130
131use crate::error::Error;
132use crate::internal::{ChunkIndex, IterPageItems, ThinPage, VersionOffset};
133use crate::platform::Platform;
134use crate::raw::{ENTRIES_PER_PAGE, FLASH_SECTOR_SIZE, Item, ItemType};
135use alloc::collections::{BTreeMap, BinaryHeap};
136use alloc::vec::Vec;
137use core::fmt;
138
139#[derive(Debug, Clone, PartialEq)]
140pub struct NvsStatistics {
141    pub pages: PageStatistics,
142    pub entries_per_page: Vec<EntryStatistics>,
143    pub entries_overall: EntryStatistics,
144}
145
146#[derive(Debug, Clone, PartialEq)]
147pub struct PageStatistics {
148    pub empty: u16,
149    pub active: u16,
150    pub full: u16,
151    pub erasing: u16,
152    pub corrupted: u16,
153}
154
155#[derive(Debug, Clone, PartialEq)]
156pub struct EntryStatistics {
157    pub empty: u32,
158    pub written: u32,
159    pub erased: u32,
160    pub illegal: u32,
161}
162
163/// The Nvs struct keeps information about all pages in memory. Increases in size with
164/// the numer of pages in the partition.
165pub struct Nvs<T: Platform> {
166    pub(crate) hal: T,
167    pub(crate) base_address: usize,
168    pub(crate) sectors: u16,
169    pub(crate) faulted: bool,
170
171    // set after calling self.load_sectors
172    pub(crate) namespaces: BTreeMap<Key, u8>,
173    pub(crate) free_pages: BinaryHeap<ThinPage>,
174    pub(crate) pages: Vec<ThinPage>,
175}
176
177impl<T: Platform> Nvs<T> {
178    /// Mimics the original C++ driver behavior and reads all sectors of the given partition to
179    /// 1. Resolve all existing namespaces
180    /// 2. Create a hashed key cache per page for quicker lookups
181    /// 3. Cleanup duplicate entries
182    /// 4. Cleanup of duplicated blobs or orphaned blob data
183    ///
184    /// Pages or entries with invalid CRC32 values are marked as corrupt and are erased when necessary
185    pub fn new(partition_offset: usize, partition_size: usize, hal: T) -> Result<Nvs<T>, Error> {
186        if !partition_offset.is_multiple_of(FLASH_SECTOR_SIZE) {
187            return Err(Error::InvalidPartitionOffset);
188        }
189
190        if !partition_size.is_multiple_of(FLASH_SECTOR_SIZE) {
191            return Err(Error::InvalidPartitionSize);
192        }
193
194        let sectors = partition_size / FLASH_SECTOR_SIZE;
195        if sectors > u16::MAX as usize {
196            return Err(Error::InvalidPartitionSize);
197        }
198
199        let mut nvs: Nvs<T> = Self {
200            hal,
201            base_address: partition_offset,
202            sectors: sectors as u16,
203            namespaces: BTreeMap::new(),
204            free_pages: Default::default(),
205            pages: Default::default(),
206            faulted: false,
207        };
208
209        match nvs.load_sectors() {
210            Ok(()) => Ok(nvs),
211            Err(Error::FlashError) => {
212                nvs.faulted = true;
213                Err(Error::FlashError)
214            }
215            Err(e) => Err(e),
216        }
217    }
218
219    /// Get a value from the flash.
220    ///
221    /// Supported types are bool, singed and unsigned integers up to 64-bit width, String and Vec.
222    ///
223    /// Both namespace and may have up to 15 characters.
224    pub fn get<R>(&mut self, namespace: &Key, key: &Key) -> Result<R, Error>
225    where
226        Nvs<T>: Get<R>,
227    {
228        match Get::get(self, namespace, key) {
229            Ok(val) => Ok(val),
230            Err(Error::FlashError) => {
231                self.faulted = true;
232                Err(Error::FlashError)
233            }
234            Err(e) => Err(e),
235        }
236    }
237
238    /// Set a value and write it to the flash
239    ///
240    /// Type support:
241    ///  * bool, singed and unsigned integers up to 64-bit width: saved as primitive value with 32 bytes
242    ///  * &str: Saved on a single page with a max size of 4000 bytes
243    ///  * &[u8]: May span multiple pages, max size ~500kB
244    pub fn set<R>(&mut self, namespace: &Key, key: &Key, value: R) -> Result<(), Error>
245    where
246        Nvs<T>: Set<R>,
247    {
248        if self.faulted {
249            return Err(Error::FlashError);
250        }
251
252        match Set::set(self, namespace, key, value) {
253            Ok(()) => Ok(()),
254            Err(Error::FlashError) => {
255                self.faulted = true;
256                Err(Error::FlashError)
257            }
258            Err(e) => Err(e),
259        }
260    }
261
262    /// Returns an iterator over all known namespaces.
263    pub fn namespaces(&self) -> impl Iterator<Item = &Key> {
264        self.namespaces.keys()
265    }
266
267    /// Returns an iterator over all keys in all namespaces.
268    ///
269    /// # Errors
270    ///
271    /// The iterator yields an error if there is a flash read error.
272    pub fn keys(&mut self) -> impl Iterator<Item = Result<(Key, Key), Error>> {
273        IterKeys::new(&self.pages, &mut self.hal, &self.namespaces)
274    }
275
276    /// Delete a key
277    ///
278    /// Ignores missing keys or the namespaces
279    pub fn delete(&mut self, namespace: &Key, key: &Key) -> Result<(), Error> {
280        if self.faulted {
281            return Err(Error::FlashError);
282        }
283
284        if key.0[MAX_KEY_LENGTH] != b'\0' {
285            return Err(Error::KeyMalformed);
286        }
287        if namespace.0[MAX_KEY_LENGTH] != b'\0' {
288            return Err(Error::NamespaceMalformed);
289        }
290
291        let namespace_index = match self.namespaces.get(namespace) {
292            Some(&idx) => idx,
293            None => return Ok(()), // Namespace doesn't exist, that's fine
294        };
295        let result = self.delete_key(namespace_index, key, ChunkIndex::Any);
296        match result {
297            Err(Error::KeyNotFound) => Ok(()),
298            Err(Error::FlashError) => {
299                self.faulted = true;
300                Err(Error::FlashError)
301            }
302            other => other,
303        }
304    }
305
306    /// Returns detailed statistics about the NVS partition usage
307    pub fn statistics(&mut self) -> Result<NvsStatistics, Error> {
308        if self.faulted {
309            return Err(Error::FlashError);
310        }
311
312        let mut page_stats = PageStatistics {
313            empty: 0,
314            active: 0,
315            full: 0,
316            erasing: 0,
317            corrupted: 0,
318        };
319
320        let mut all_pages: Vec<&ThinPage> = Vec::with_capacity(self.sectors as _);
321        all_pages.extend(self.pages.iter());
322        all_pages.extend(self.free_pages.iter());
323        // sorted for stable output as this is also used in tests
324        all_pages.sort_by_key(|page| page.address);
325
326        let entries_per_page = all_pages
327            .into_iter()
328            .map(|page| {
329                match page.get_state() {
330                    internal::ThinPageState::Active => page_stats.active += 1,
331                    internal::ThinPageState::Full => page_stats.full += 1,
332                    internal::ThinPageState::Freeing => page_stats.erasing += 1,
333                    internal::ThinPageState::Corrupt => page_stats.corrupted += 1,
334                    internal::ThinPageState::Invalid => page_stats.corrupted += 1,
335                    internal::ThinPageState::Uninitialized => page_stats.empty += 1,
336                }
337
338                if *page.get_state() == internal::ThinPageState::Corrupt {
339                    EntryStatistics {
340                        empty: 0,
341                        written: 0,
342                        erased: 0,
343                        illegal: ENTRIES_PER_PAGE as _,
344                    }
345                } else {
346                    let (empty, written, erased, illegal) = page.get_entry_statistics();
347                    EntryStatistics {
348                        empty,
349                        written,
350                        erased,
351                        illegal,
352                    }
353                }
354            })
355            .collect::<Vec<_>>();
356
357        let entries_overall = entries_per_page.iter().fold(
358            EntryStatistics {
359                empty: 0,
360                written: 0,
361                erased: 0,
362                illegal: 0,
363            },
364            |acc, x| EntryStatistics {
365                empty: acc.empty + x.empty,
366                written: acc.written + x.written,
367                erased: acc.erased + x.erased,
368                illegal: acc.illegal + x.illegal,
369            },
370        );
371
372        Ok(NvsStatistics {
373            pages: page_stats,
374            entries_per_page,
375            entries_overall,
376        })
377    }
378}
379
380struct IterLoadedItems<'a, T: Platform> {
381    pages: &'a [ThinPage],
382    current: Option<IterPageItems<'a, T>>,
383}
384
385impl<'a, T: Platform> IterLoadedItems<'a, T> {
386    fn new(mut pages: &'a [ThinPage], hal: &'a mut T) -> Self {
387        let first = pages.split_off_first();
388
389        Self {
390            pages,
391            current: first.map(|page| page.items(hal)),
392        }
393    }
394}
395
396impl<'a, T: Platform> Iterator for IterLoadedItems<'a, T> {
397    type Item = Result<Item, Error>;
398
399    fn next(&mut self) -> Option<Self::Item> {
400        // self.current is only None if there are no pages at all
401        let current = self.current.as_mut()?;
402
403        // if the current page is exhausted, move to next page that has items (or until we run out of pages)
404        while current.is_empty() {
405            let next_page = self.pages.split_off_first()?;
406
407            current.switch_to_page(next_page);
408        }
409
410        current.next()
411    }
412}
413
414struct IterKeys<'a, T: Platform> {
415    items: IterLoadedItems<'a, T>,
416    namespaces: &'a BTreeMap<Key, u8>,
417}
418
419impl<'a, T: Platform> IterKeys<'a, T> {
420    fn new(pages: &'a [ThinPage], hal: &'a mut T, namespaces: &'a BTreeMap<Key, u8>) -> Self {
421        Self {
422            items: IterLoadedItems::new(pages, hal),
423            namespaces,
424        }
425    }
426
427    fn item_to_keys(&self, item: Item) -> (Key, Key) {
428        let (namespace_key, _) = self
429            .namespaces
430            .iter()
431            .find(|(_, idx)| **idx == item.namespace_index)
432            // a key should always have a namespace
433            .unwrap();
434
435        (*namespace_key, item.key)
436    }
437}
438
439impl<'a, T: Platform> Iterator for IterKeys<'a, T> {
440    type Item = Result<(Key, Key), Error>;
441
442    fn next(&mut self) -> Option<Self::Item> {
443        loop {
444            return match self.items.next()? {
445                Ok(item) => {
446                    // Skip namespace entries (namespace_index == 0), and blobs (they are represented by their BlobData)
447                    if item.namespace_index == 0
448                        || item.type_ == ItemType::Blob
449                        || item.type_ == ItemType::BlobIndex
450                    {
451                        continue;
452                    }
453
454                    if item.type_ == ItemType::BlobData
455                        && item.chunk_index != VersionOffset::V0 as u8
456                        && item.chunk_index != VersionOffset::V1 as u8
457                    {
458                        continue;
459                    }
460
461                    Some(Ok(self.item_to_keys(item)))
462                }
463                Err(err) => Some(Err(err)),
464            };
465        }
466    }
467}