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, Debug)]
18#[cfg_attr(feature = "defmt", derive(defmt::Format))]
19pub struct Key([u8; MAX_KEY_NUL_TERMINATED_LENGTH]);
20
21impl Key {
22    /// Creates a 16 byte, null-padded byte array used as key for values and namespaces.
23    ///
24    /// Usage: `Key::from_array(b"my_key")`
25    ///
26    /// Tip: use a const context if possible to ensure that the key is transformed at compile time:
27    ///   `let my_key = const { Key::from_array(b"my_key") };`
28    pub const fn from_array<const M: usize>(src: &[u8; M]) -> Self {
29        assert!(M <= MAX_KEY_LENGTH);
30        let mut dst = [0u8; MAX_KEY_NUL_TERMINATED_LENGTH];
31        let mut i = 0;
32        while i < M {
33            dst[i] = src[i];
34            i += 1;
35        }
36        Self(dst)
37    }
38
39    /// Creates a 16 byte, null-padded byte array used as key for values and namespaces.
40    ///
41    /// Usage: `Key::from_slice(b"my_key")`
42    ///
43    /// Tip: use a const context if possible to ensure that the key is transformed at compile time:
44    ///   `let my_key = const { Key::from_slice("my_key".as_bytes()) };`
45    pub const fn from_slice(src: &[u8]) -> Self {
46        assert!(src.len() <= MAX_KEY_LENGTH);
47        let mut dst = [0u8; MAX_KEY_NUL_TERMINATED_LENGTH];
48        let mut i = 0;
49        while i < src.len() {
50            dst[i] = src[i];
51            i += 1;
52        }
53        Self(dst)
54    }
55
56    /// Creates a 16 byte, null-padded byte array used as key for values and namespaces.
57    ///
58    /// Usage: `Key::from_str("my_key")`
59    ///
60    /// Tip: use a const context if possible to ensure that the key is transformed at compile time:
61    ///   `let my_key = const { Key::from_str("my_key") };`
62    pub const fn from_str(s: &str) -> Self {
63        let bytes = s.as_bytes();
64        Self::from_slice(bytes)
65    }
66
67    /// Converts a key to a byte array.
68    pub const fn as_bytes(&self) -> &[u8; MAX_KEY_NUL_TERMINATED_LENGTH] {
69        &self.0
70    }
71}
72
73impl AsRef<[u8]> for Key {
74    fn as_ref(&self) -> &[u8] {
75        self.as_bytes()
76    }
77}
78
79extern crate alloc;
80
81use crate::error::Error;
82use crate::get::Get;
83use crate::internal::{ChunkIndex, ThinPage};
84use crate::platform::Platform;
85use crate::raw::{ENTRIES_PER_PAGE, FLASH_SECTOR_SIZE};
86use crate::set::Set;
87use alloc::collections::{BTreeMap, BinaryHeap};
88use alloc::vec::Vec;
89
90#[derive(Debug, Clone, PartialEq)]
91pub struct NvsStatistics {
92    pub pages: PageStatistics,
93    pub entries_per_page: Vec<EntryStatistics>,
94    pub entries_overall: EntryStatistics,
95}
96
97#[derive(Debug, Clone, PartialEq)]
98pub struct PageStatistics {
99    pub empty: u16,
100    pub active: u16,
101    pub full: u16,
102    pub erasing: u16,
103    pub corrupted: u16,
104}
105
106#[derive(Debug, Clone, PartialEq)]
107pub struct EntryStatistics {
108    pub empty: u32,
109    pub written: u32,
110    pub erased: u32,
111    pub illegal: u32,
112}
113
114/// The Nvs struct keeps information about all pages in memory. Increases in size with
115/// the numer of pages in the partition.
116pub struct Nvs<'a, T>
117where
118    T: Platform,
119{
120    pub(crate) hal: &'a mut T,
121    pub(crate) base_address: usize,
122    pub(crate) sectors: u16,
123    pub(crate) faulted: bool,
124
125    // set after calling self.load_sectors
126    pub(crate) namespaces: BTreeMap<Key, u8>,
127    pub(crate) free_pages: BinaryHeap<ThinPage>,
128    pub(crate) pages: Vec<ThinPage>,
129}
130
131impl<'a, T> Nvs<'a, T>
132where
133    T: Platform,
134{
135    /// Mimics the original C++ driver behavior and reads all sectors of the given partition to
136    /// 1. Resolve all existing namespaces
137    /// 2. Create a hashed key cache per page for quicker lookups
138    /// 3. Cleanup duplicate entries
139    /// 4. Cleanup of duplicated blobs or orphaned blob data
140    ///
141    /// Pages or entries with invalid CRC32 values are marked as corrupt and are erased when necessary
142    pub fn new(
143        partition_offset: usize,
144        partition_size: usize,
145        hal: &'a mut T,
146    ) -> Result<Nvs<'a, T>, Error> {
147        if !partition_offset.is_multiple_of(FLASH_SECTOR_SIZE) {
148            return Err(Error::InvalidPartitionOffset);
149        }
150
151        if !partition_size.is_multiple_of(FLASH_SECTOR_SIZE) {
152            return Err(Error::InvalidPartitionSize);
153        }
154
155        let sectors = partition_size / FLASH_SECTOR_SIZE;
156        if sectors > u16::MAX as usize {
157            return Err(Error::InvalidPartitionSize);
158        }
159
160        let mut nvs: Nvs<'a, T> = Self {
161            hal,
162            base_address: partition_offset,
163            sectors: sectors as u16,
164            namespaces: BTreeMap::new(),
165            free_pages: Default::default(),
166            pages: Default::default(),
167            faulted: false,
168        };
169
170        match nvs.load_sectors() {
171            Ok(()) => Ok(nvs),
172            Err(Error::FlashError) => {
173                nvs.faulted = true;
174                Err(Error::FlashError)
175            }
176            Err(e) => Err(e),
177        }
178    }
179
180    /// Get a value from the flash.
181    ///
182    /// Supported types are bool, singed and unsigned integers up to 64-bit width, String and Vec.
183    ///
184    /// Both namespace and may have up to 15 characters.
185    pub fn get<R>(&mut self, namespace: &Key, key: &Key) -> Result<R, Error>
186    where
187        Nvs<'a, T>: Get<R>,
188    {
189        match Get::get(self, namespace, key) {
190            Ok(val) => Ok(val),
191            Err(Error::FlashError) => {
192                self.faulted = true;
193                Err(Error::FlashError)
194            }
195            Err(e) => Err(e),
196        }
197    }
198
199    /// Set a value and write it to the flash
200    ///
201    /// Type support:
202    ///  * bool, singed and unsigned integers up to 64-bit width: saved as primitive value with 32 bytes
203    ///  * &str: Saved on a single page with a max size of 4000 bytes
204    ///  * &[u8]: May span multiple pages, max size ~500kB
205    pub fn set<R>(&mut self, namespace: &Key, key: &Key, value: R) -> Result<(), Error>
206    where
207        Nvs<'a, T>: Set<R>,
208    {
209        if self.faulted {
210            return Err(Error::FlashError);
211        }
212
213        match Set::set(self, namespace, key, value) {
214            Ok(()) => Ok(()),
215            Err(Error::FlashError) => {
216                self.faulted = true;
217                Err(Error::FlashError)
218            }
219            Err(e) => Err(e),
220        }
221    }
222
223    /// Delete a key
224    ///
225    /// Ignores missing keys or the namespaces
226    pub fn delete(&mut self, namespace: &Key, key: &Key) -> Result<(), Error> {
227        if self.faulted {
228            return Err(Error::FlashError);
229        }
230
231        if key.0[MAX_KEY_LENGTH] != b'\0' {
232            return Err(Error::KeyMalformed);
233        }
234        if namespace.0[MAX_KEY_LENGTH] != b'\0' {
235            return Err(Error::NamespaceMalformed);
236        }
237
238        let namespace_index = match self.namespaces.get(namespace) {
239            Some(&idx) => idx,
240            None => return Ok(()), // Namespace doesn't exist, that's fine
241        };
242        let result = self.delete_key(namespace_index, key, ChunkIndex::Any);
243        match result {
244            Err(Error::KeyNotFound) => Ok(()),
245            Err(Error::FlashError) => {
246                self.faulted = true;
247                Err(Error::FlashError)
248            }
249            other => other,
250        }
251    }
252
253    /// Returns detailed statistics about the NVS partition usage
254    pub fn statistics(&mut self) -> Result<NvsStatistics, Error> {
255        if self.faulted {
256            return Err(Error::FlashError);
257        }
258
259        let mut page_stats = PageStatistics {
260            empty: 0,
261            active: 0,
262            full: 0,
263            erasing: 0,
264            corrupted: 0,
265        };
266
267        let mut all_pages: Vec<&ThinPage> = Vec::with_capacity(self.sectors as _);
268        all_pages.extend(self.pages.iter());
269        all_pages.extend(self.free_pages.iter());
270        // sorted for stable output as this is also used in tests
271        all_pages.sort_by_key(|page| page.address);
272
273        let entries_per_page = all_pages
274            .into_iter()
275            .map(|page| {
276                match page.get_state() {
277                    internal::ThinPageState::Active => page_stats.active += 1,
278                    internal::ThinPageState::Full => page_stats.full += 1,
279                    internal::ThinPageState::Freeing => page_stats.erasing += 1,
280                    internal::ThinPageState::Corrupt => page_stats.corrupted += 1,
281                    internal::ThinPageState::Invalid => page_stats.corrupted += 1,
282                    internal::ThinPageState::Uninitialized => page_stats.empty += 1,
283                }
284
285                if *page.get_state() == internal::ThinPageState::Corrupt {
286                    EntryStatistics {
287                        empty: 0,
288                        written: 0,
289                        erased: 0,
290                        illegal: ENTRIES_PER_PAGE as _,
291                    }
292                } else {
293                    let (empty, written, erased, illegal) = page.get_entry_statistics();
294                    EntryStatistics {
295                        empty,
296                        written,
297                        erased,
298                        illegal,
299                    }
300                }
301            })
302            .collect::<Vec<_>>();
303
304        let entries_overall = entries_per_page.iter().fold(
305            EntryStatistics {
306                empty: 0,
307                written: 0,
308                erased: 0,
309                illegal: 0,
310            },
311            |acc, x| EntryStatistics {
312                empty: acc.empty + x.empty,
313                written: acc.written + x.written,
314                erased: acc.erased + x.erased,
315                illegal: acc.illegal + x.illegal,
316            },
317        );
318
319        Ok(NvsStatistics {
320            pages: page_stats,
321            entries_per_page,
322            entries_overall,
323        })
324    }
325}