Skip to main content

esp_idf_svc/
nvs.rs

1//! Non-Volatile Storage (NVS)
2#[cfg(esp_idf_version_at_least_5_2_0)]
3use core::marker::PhantomData;
4use core::ptr;
5
6extern crate alloc;
7use alloc::sync::Arc;
8
9use ::log::*;
10
11use embedded_svc::storage::{RawStorage, StorageBase};
12
13use crate::sys::*;
14
15use crate::handle::RawHandle;
16use crate::private::cstr::*;
17use crate::private::mutex;
18
19static DEFAULT_TAKEN: mutex::Mutex<bool> = mutex::Mutex::new(false);
20static NONDEFAULT_LOCKED: mutex::Mutex<alloc::collections::BTreeSet<CString>> =
21    mutex::Mutex::new(alloc::collections::BTreeSet::new());
22
23pub type EspDefaultNvsPartition = EspNvsPartition<NvsDefault>;
24pub type EspCustomNvsPartition = EspNvsPartition<NvsCustom>;
25pub type EspEncryptedNvsPartition = EspNvsPartition<NvsEncrypted>;
26
27pub trait NvsPartitionId {
28    fn is_default(&self) -> bool {
29        self.name().to_bytes().is_empty()
30    }
31
32    fn name(&self) -> &CStr;
33}
34
35pub struct NvsDefault(());
36
37#[repr(u32)]
38#[allow(non_upper_case_globals)]
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum NvsDataType {
41    U8 = nvs_type_t_NVS_TYPE_U8,
42    I8 = nvs_type_t_NVS_TYPE_I8,
43    U16 = nvs_type_t_NVS_TYPE_U16,
44    I16 = nvs_type_t_NVS_TYPE_I16,
45    U32 = nvs_type_t_NVS_TYPE_U32,
46    I32 = nvs_type_t_NVS_TYPE_I32,
47    U64 = nvs_type_t_NVS_TYPE_U64,
48    I64 = nvs_type_t_NVS_TYPE_I64,
49    Str = nvs_type_t_NVS_TYPE_STR,
50    Blob = nvs_type_t_NVS_TYPE_BLOB,
51}
52
53#[allow(non_upper_case_globals)]
54impl NvsDataType {
55    /// Converts a `nvs_type_t` to an `NvsDataType`, returning `None` if the type is not recognized.
56    #[must_use]
57    pub fn from_nvs_type(nvs_type: nvs_type_t) -> Option<Self> {
58        match nvs_type {
59            nvs_type_t_NVS_TYPE_U8 => Some(Self::U8),
60            nvs_type_t_NVS_TYPE_I8 => Some(Self::I8),
61            nvs_type_t_NVS_TYPE_U16 => Some(Self::U16),
62            nvs_type_t_NVS_TYPE_I16 => Some(Self::I16),
63            nvs_type_t_NVS_TYPE_U32 => Some(Self::U32),
64            nvs_type_t_NVS_TYPE_I32 => Some(Self::I32),
65            nvs_type_t_NVS_TYPE_U64 => Some(Self::U64),
66            nvs_type_t_NVS_TYPE_I64 => Some(Self::I64),
67            nvs_type_t_NVS_TYPE_STR => Some(Self::Str),
68            nvs_type_t_NVS_TYPE_BLOB => Some(Self::Blob),
69            _ => None,
70        }
71    }
72}
73
74impl NvsDefault {
75    fn new(reinit: bool) -> Result<Self, EspError> {
76        let mut taken = DEFAULT_TAKEN.lock();
77
78        if *taken {
79            return Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>());
80        }
81
82        let default_nvs = Self::init(reinit)?;
83
84        *taken = true;
85        Ok(default_nvs)
86    }
87
88    fn init(reinit: bool) -> Result<Self, EspError> {
89        if let Some(err) = EspError::from(unsafe { nvs_flash_init() }) {
90            match err.code() {
91                ESP_ERR_NVS_NO_FREE_PAGES | ESP_ERR_NVS_NEW_VERSION_FOUND if reinit => {
92                    if err.code() == ESP_ERR_NVS_NEW_VERSION_FOUND {
93                        warn!("NVS partition has a new version, erasing and re-initializing the partition");
94                    } else {
95                        warn!("NVS partition has no free pages, erasing and re-initializing the partition");
96                    }
97
98                    esp!(unsafe { nvs_flash_erase() })?;
99                    esp!(unsafe { nvs_flash_init() })?;
100                }
101                _ => Err(err)?,
102            }
103        }
104
105        Ok(Self(()))
106    }
107}
108
109impl Drop for NvsDefault {
110    fn drop(&mut self) {
111        //esp!(nvs_flash_deinit()).unwrap(); TODO: To be checked why it fails
112        *DEFAULT_TAKEN.lock() = false;
113
114        info!("NvsDefault dropped");
115    }
116}
117
118impl NvsPartitionId for NvsDefault {
119    #[allow(clippy::manual_c_str_literals)]
120    fn name(&self) -> &CStr {
121        CStr::from_bytes_with_nul(b"\0").unwrap()
122    }
123}
124
125pub struct NvsCustom(CString);
126
127impl NvsCustom {
128    fn new(partition: &str) -> Result<Self, EspError> {
129        let mut registrations = NONDEFAULT_LOCKED.lock();
130
131        Self::init(partition, &mut registrations)
132    }
133
134    fn init(
135        partition: &str,
136        registrations: &mut alloc::collections::BTreeSet<CString>,
137    ) -> Result<Self, EspError> {
138        let c_partition = to_cstring_arg(partition)?;
139
140        if registrations.contains(c_partition.as_ref()) {
141            return Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>());
142        }
143
144        unsafe {
145            if let Some(err) = EspError::from(nvs_flash_init_partition(c_partition.as_ptr())) {
146                match err.code() {
147                    ESP_ERR_NVS_NO_FREE_PAGES | ESP_ERR_NVS_NEW_VERSION_FOUND => {
148                        esp!(nvs_flash_erase_partition(c_partition.as_ptr()))?;
149                        esp!(nvs_flash_init_partition(c_partition.as_ptr()))?;
150                    }
151                    _ => Err(err)?,
152                }
153            }
154        }
155
156        registrations.insert(c_partition.clone());
157
158        Ok(Self(c_partition))
159    }
160}
161
162impl Drop for NvsCustom {
163    fn drop(&mut self) {
164        {
165            let mut registrations = NONDEFAULT_LOCKED.lock();
166
167            esp!(unsafe { nvs_flash_deinit_partition(self.0.as_ptr()) }).unwrap();
168            registrations.remove(self.0.as_ref());
169        }
170
171        info!("NvsCustom dropped");
172    }
173}
174
175impl NvsPartitionId for NvsCustom {
176    fn name(&self) -> &CStr {
177        self.0.as_c_str()
178    }
179}
180pub struct NvsEncrypted(CString);
181
182impl NvsEncrypted {
183    fn new(partition: &str, key_partition: Option<&str>) -> Result<Self, EspError> {
184        let mut registrations = NONDEFAULT_LOCKED.lock();
185
186        Self::init(partition, key_partition, &mut registrations)
187    }
188
189    fn init(
190        partition: &str,
191        key_partition: Option<&str>,
192        registrations: &mut alloc::collections::BTreeSet<CString>,
193    ) -> Result<Self, EspError> {
194        let c_partition = to_cstring_arg(partition)?;
195
196        if registrations.contains(c_partition.as_ref()) {
197            return Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>());
198        }
199
200        let c_key_partition = if let Some(key_partition) = key_partition {
201            Some(to_cstring_arg(key_partition)?)
202        } else {
203            None
204        };
205
206        let keys_partition_ptr = unsafe {
207            esp_partition_find_first(
208                esp_partition_type_t_ESP_PARTITION_TYPE_DATA,
209                esp_partition_subtype_t_ESP_PARTITION_SUBTYPE_DATA_NVS_KEYS,
210                match c_key_partition {
211                    Some(ref v) => v.as_ptr(),
212                    None => core::ptr::null(),
213                },
214            )
215        };
216
217        if keys_partition_ptr.is_null() {
218            warn!("No NVS keys partition found");
219            return Err(EspError::from_infallible::<ESP_FAIL>());
220        }
221
222        let mut config = nvs_sec_cfg_t::default();
223        match unsafe { nvs_flash_read_security_cfg(keys_partition_ptr, &mut config as *mut _) } {
224            ESP_ERR_NVS_KEYS_NOT_INITIALIZED | ESP_ERR_NVS_CORRUPT_KEY_PART => {
225                info!("Partition not initialized, generating keys");
226                esp!(unsafe {
227                    nvs_flash_generate_keys(keys_partition_ptr, &mut config as *mut _)
228                })?;
229            }
230            other => esp!(other)?,
231        }
232
233        esp!(unsafe {
234            nvs_flash_secure_init_partition(c_partition.as_ptr(), &mut config as *mut _)
235        })?;
236
237        registrations.insert(c_partition.clone());
238
239        Ok(Self(c_partition))
240    }
241}
242
243// These functions are copied from NvsCustom, maybe there's a way to write this in a shorter way?
244impl Drop for NvsEncrypted {
245    fn drop(&mut self) {
246        {
247            let mut registrations = NONDEFAULT_LOCKED.lock();
248
249            esp!(unsafe { nvs_flash_deinit_partition(self.0.as_ptr()) }).unwrap();
250            registrations.remove(self.0.as_ref());
251        }
252
253        info!("NvsEncrypted dropped");
254    }
255}
256
257impl NvsPartitionId for NvsEncrypted {
258    fn name(&self) -> &CStr {
259        self.0.as_c_str()
260    }
261}
262
263#[derive(Debug)]
264pub struct EspNvsPartition<T: NvsPartitionId>(Arc<T>);
265
266impl EspNvsPartition<NvsDefault> {
267    /// Take the default NVS partition, initializing it if full or if a new version is detected
268    pub fn take() -> Result<Self, EspError> {
269        Self::take_with(true)
270    }
271
272    /// Take the default NVS partition
273    ///
274    /// # Arguments
275    /// - `reinit`: Whether to reinitialize the partition if full or if a new version is detected
276    pub fn take_with(reinit: bool) -> Result<Self, EspError> {
277        Ok(Self(Arc::new(NvsDefault::new(reinit)?)))
278    }
279}
280
281impl EspNvsPartition<NvsCustom> {
282    pub fn take(partition: &str) -> Result<Self, EspError> {
283        Ok(Self(Arc::new(NvsCustom::new(partition)?)))
284    }
285}
286
287impl EspNvsPartition<NvsEncrypted> {
288    pub fn take(partition: &str, keys_partition: Option<&str>) -> Result<Self, EspError> {
289        Ok(Self(Arc::new(NvsEncrypted::new(
290            partition,
291            keys_partition,
292        )?)))
293    }
294}
295
296impl<T> Clone for EspNvsPartition<T>
297where
298    T: NvsPartitionId,
299{
300    fn clone(&self) -> Self {
301        Self(self.0.clone())
302    }
303}
304
305impl RawHandle for EspNvsPartition<NvsCustom> {
306    type Handle = *const u8;
307
308    fn handle(&self) -> Self::Handle {
309        self.0.name().as_ptr() as *const _
310    }
311}
312
313impl RawHandle for EspNvsPartition<NvsEncrypted> {
314    type Handle = *const u8;
315
316    fn handle(&self) -> Self::Handle {
317        self.0.name().as_ptr() as *const _
318    }
319}
320
321pub type EspDefaultNvs = EspNvs<NvsDefault>;
322pub type EspCustomNvs = EspNvs<NvsCustom>;
323pub type EspEncryptedNvs = EspNvs<NvsEncrypted>;
324
325#[allow(dead_code)]
326pub struct EspNvs<T: NvsPartitionId>(EspNvsPartition<T>, nvs_handle_t);
327
328impl<T: NvsPartitionId> EspNvs<T> {
329    pub fn new(
330        partition: EspNvsPartition<T>,
331        namespace: &str,
332        read_write: bool,
333    ) -> Result<Self, EspError> {
334        let c_namespace = to_cstring_arg(namespace)?;
335
336        let mut handle: nvs_handle_t = 0;
337
338        if partition.0.is_default() {
339            esp!(unsafe {
340                nvs_open(
341                    c_namespace.as_ptr(),
342                    if read_write {
343                        nvs_open_mode_t_NVS_READWRITE
344                    } else {
345                        nvs_open_mode_t_NVS_READONLY
346                    },
347                    &mut handle as *mut _,
348                )
349            })?;
350        } else {
351            esp!(unsafe {
352                nvs_open_from_partition(
353                    partition.0.name().as_ptr(),
354                    c_namespace.as_ptr(),
355                    if read_write {
356                        nvs_open_mode_t_NVS_READWRITE
357                    } else {
358                        nvs_open_mode_t_NVS_READONLY
359                    },
360                    &mut handle as *mut _,
361                )
362            })?;
363        }
364
365        Ok(Self(partition, handle))
366    }
367
368    #[cfg(all(
369        not(esp_idf_version_major = "4"),
370        not(all(esp_idf_version_major = "5", esp_idf_version_minor = "1"))
371    ))]
372    pub fn find_key(&self, name: &str) -> Result<Option<NvsDataType>, EspError> {
373        let c_key = to_cstring_arg(name)?;
374        let mut entry_type: nvs_type_t = nvs_type_t_NVS_TYPE_ANY;
375
376        let result = unsafe { nvs_find_key(self.1, c_key.as_ptr(), &mut entry_type as *mut _) };
377
378        match result {
379            ESP_OK => Ok(NvsDataType::from_nvs_type(entry_type)),
380            ESP_ERR_NVS_NOT_FOUND => Ok(None),
381            err => {
382                esp!(err)?;
383                Ok(None)
384            }
385        }
386    }
387
388    pub fn remove(&self, name: &str) -> Result<bool, EspError> {
389        let c_key = to_cstring_arg(name)?;
390
391        // nvs_erase_key is not scoped by datatype
392        let result = unsafe { nvs_erase_key(self.1, c_key.as_ptr()) };
393
394        if result == ESP_ERR_NVS_NOT_FOUND {
395            Ok(false)
396        } else {
397            esp!(result)?;
398            esp!(unsafe { nvs_commit(self.1) })?;
399
400            Ok(true)
401        }
402    }
403
404    /// Returns the length of the blob stored under the key `name`.
405    ///
406    /// If the key does not exist, `Ok(None)` is returned.
407    ///
408    /// # Errors
409    ///
410    /// - `ESP_ERR_NVS_INVALID_HANDLE` if the NVS handle is invalid.
411    /// - `ESP_ERR_NVS_INVALID_NAME` if the key name is invalid.
412    pub fn blob_len(&self, name: &str) -> Result<Option<usize>, EspError> {
413        let c_key = to_cstring_arg(name)?;
414
415        #[allow(unused_assignments)]
416        let mut len = 0;
417
418        match unsafe { nvs_get_blob(self.1, c_key.as_ptr(), ptr::null_mut(), &mut len as *mut _) } {
419            ESP_ERR_NVS_NOT_FOUND => Ok(None),
420            err => {
421                // bail on error
422                esp!(err)?;
423
424                Ok(Some(len))
425            }
426        }
427    }
428
429    pub fn get_blob<'a>(
430        &self,
431        name: &str,
432        buf: &'a mut [u8],
433    ) -> Result<Option<&'a [u8]>, EspError> {
434        let c_key = to_cstring_arg(name)?;
435        let mut len = buf.len();
436
437        match unsafe {
438            nvs_get_blob(
439                self.1,
440                c_key.as_ptr(),
441                buf.as_mut_ptr() as *mut _,
442                &mut len as *mut _,
443            )
444        } {
445            ESP_ERR_NVS_NOT_FOUND => Ok(None),
446            err => {
447                // bail on error
448                esp!(err)?;
449
450                Ok(Some(&buf[..len]))
451            }
452        }
453    }
454
455    pub fn set_blob(&self, name: &str, buf: &[u8]) -> Result<(), EspError> {
456        let c_key = to_cstring_arg(name)?;
457
458        // start by just clearing this key
459        unsafe { nvs_erase_key(self.1, c_key.as_ptr()) };
460
461        esp!(unsafe { nvs_set_blob(self.1, c_key.as_ptr(), buf.as_ptr().cast(), buf.len()) })?;
462
463        esp!(unsafe { nvs_commit(self.1) })?;
464
465        Ok(())
466    }
467
468    /// Returns the length of the string stored under the key `name`.
469    ///
470    /// If the key does not exist, `Ok(None)` is returned.
471    ///
472    /// # Errors
473    ///
474    /// - `ESP_ERR_NVS_INVALID_HANDLE` if the NVS handle is invalid.
475    /// - `ESP_ERR_NVS_INVALID_NAME` if the key name is invalid.
476    ///
477    /// # Note
478    ///
479    /// The stored string is a [`CString`], which is why the returned length
480    /// includes the null terminator.
481    ///
482    /// Rust strings do not have a null terminator, so when constructing one,
483    /// make sure to only use the bytes up to `len - 1`. Alternatively one
484    /// can use [`CStr`] or [`CString`].
485    pub fn str_len(&self, name: &str) -> Result<Option<usize>, EspError> {
486        let c_key = to_cstring_arg(name)?;
487
488        #[allow(unused_assignments)]
489        let mut len = 0;
490
491        match unsafe { nvs_get_str(self.1, c_key.as_ptr(), ptr::null_mut(), &mut len as *mut _) } {
492            ESP_ERR_NVS_NOT_FOUND => Ok(None),
493            err => {
494                // bail on error
495                esp!(err)?;
496
497                Ok(Some(len))
498            }
499        }
500    }
501
502    pub fn get_str<'a>(&self, name: &str, buf: &'a mut [u8]) -> Result<Option<&'a str>, EspError> {
503        let c_key = to_cstring_arg(name)?;
504
505        let mut len = buf.len();
506        match unsafe {
507            nvs_get_str(
508                self.1,
509                c_key.as_ptr(),
510                buf.as_mut_ptr() as *mut _,
511                &mut len as *mut _,
512            )
513        } {
514            ESP_ERR_NVS_NOT_FOUND => Ok(None),
515            err => {
516                // bail on error
517                esp!(err)?;
518
519                Ok(Some(unsafe {
520                    core::str::from_utf8_unchecked(&(buf[..len - 1]))
521                }))
522            }
523        }
524    }
525
526    pub fn set_str(&self, name: &str, val: &str) -> Result<(), EspError> {
527        let c_key = to_cstring_arg(name)?;
528        let c_val = to_cstring_arg(val)?;
529
530        // start by just clearing this key
531        unsafe { nvs_erase_key(self.1, c_key.as_ptr()) };
532
533        esp!(unsafe { nvs_set_str(self.1, c_key.as_ptr(), c_val.as_ptr(),) })?;
534
535        esp!(unsafe { nvs_commit(self.1) })?;
536
537        Ok(())
538    }
539
540    pub fn get_u8(&self, name: &str) -> Result<Option<u8>, EspError> {
541        let c_key = to_cstring_arg(name)?;
542        let mut result: [u8; 1] = [0; 1];
543
544        match unsafe { nvs_get_u8(self.1, c_key.as_ptr(), &mut result[0] as *mut _) } {
545            ESP_ERR_NVS_NOT_FOUND => Ok(None),
546            err => {
547                // bail on error
548                esp!(err)?;
549
550                Ok(Some(result[0]))
551            }
552        }
553    }
554
555    pub fn set_u8(&self, name: &str, val: u8) -> Result<(), EspError> {
556        let c_key = to_cstring_arg(name)?;
557
558        esp!(unsafe { nvs_set_u8(self.1, c_key.as_ptr(), val) })?;
559
560        esp!(unsafe { nvs_commit(self.1) })?;
561
562        Ok(())
563    }
564
565    pub fn get_i8(&self, name: &str) -> Result<Option<i8>, EspError> {
566        let c_key = to_cstring_arg(name)?;
567        let mut result: [i8; 1] = [0; 1];
568
569        match unsafe { nvs_get_i8(self.1, c_key.as_ptr(), &mut result[0] as *mut _) } {
570            ESP_ERR_NVS_NOT_FOUND => Ok(None),
571            err => {
572                // bail on error
573                esp!(err)?;
574
575                Ok(Some(result[0]))
576            }
577        }
578    }
579
580    pub fn set_i8(&self, name: &str, val: i8) -> Result<(), EspError> {
581        let c_key = to_cstring_arg(name)?;
582
583        esp!(unsafe { nvs_set_i8(self.1, c_key.as_ptr(), val) })?;
584
585        esp!(unsafe { nvs_commit(self.1) })?;
586
587        Ok(())
588    }
589
590    pub fn get_u16(&self, name: &str) -> Result<Option<u16>, EspError> {
591        let c_key = to_cstring_arg(name)?;
592        let mut result: [u16; 1] = [0; 1];
593
594        match unsafe { nvs_get_u16(self.1, c_key.as_ptr(), &mut result[0] as *mut _) } {
595            ESP_ERR_NVS_NOT_FOUND => Ok(None),
596            err => {
597                // bail on error
598                esp!(err)?;
599
600                Ok(Some(result[0]))
601            }
602        }
603    }
604
605    pub fn set_u16(&self, name: &str, val: u16) -> Result<(), EspError> {
606        let c_key = to_cstring_arg(name)?;
607
608        esp!(unsafe { nvs_set_u16(self.1, c_key.as_ptr(), val) })?;
609
610        esp!(unsafe { nvs_commit(self.1) })?;
611
612        Ok(())
613    }
614
615    pub fn get_i16(&self, name: &str) -> Result<Option<i16>, EspError> {
616        let c_key = to_cstring_arg(name)?;
617        let mut result: [i16; 1] = [0; 1];
618
619        match unsafe { nvs_get_i16(self.1, c_key.as_ptr(), &mut result[0] as *mut _) } {
620            ESP_ERR_NVS_NOT_FOUND => Ok(None),
621            err => {
622                // bail on error
623                esp!(err)?;
624
625                Ok(Some(result[0]))
626            }
627        }
628    }
629
630    pub fn set_i16(&self, name: &str, val: i16) -> Result<(), EspError> {
631        let c_key = to_cstring_arg(name)?;
632
633        esp!(unsafe { nvs_set_i16(self.1, c_key.as_ptr(), val) })?;
634
635        esp!(unsafe { nvs_commit(self.1) })?;
636
637        Ok(())
638    }
639
640    pub fn get_u32(&self, name: &str) -> Result<Option<u32>, EspError> {
641        let c_key = to_cstring_arg(name)?;
642        let mut result: [u32; 1] = [0; 1];
643
644        match unsafe { nvs_get_u32(self.1, c_key.as_ptr(), &mut result[0] as *mut _) } {
645            ESP_ERR_NVS_NOT_FOUND => Ok(None),
646            err => {
647                // bail on error
648                esp!(err)?;
649
650                Ok(Some(result[0]))
651            }
652        }
653    }
654
655    pub fn set_u32(&self, name: &str, val: u32) -> Result<(), EspError> {
656        let c_key = to_cstring_arg(name)?;
657
658        esp!(unsafe { nvs_set_u32(self.1, c_key.as_ptr(), val) })?;
659
660        esp!(unsafe { nvs_commit(self.1) })?;
661
662        Ok(())
663    }
664
665    pub fn get_i32(&self, name: &str) -> Result<Option<i32>, EspError> {
666        let c_key = to_cstring_arg(name)?;
667        let mut result: [i32; 1] = [0; 1];
668
669        match unsafe { nvs_get_i32(self.1, c_key.as_ptr(), &mut result[0] as *mut _) } {
670            ESP_ERR_NVS_NOT_FOUND => Ok(None),
671            err => {
672                // bail on error
673                esp!(err)?;
674
675                Ok(Some(result[0]))
676            }
677        }
678    }
679
680    pub fn set_i32(&self, name: &str, val: i32) -> Result<(), EspError> {
681        let c_key = to_cstring_arg(name)?;
682
683        esp!(unsafe { nvs_set_i32(self.1, c_key.as_ptr(), val) })?;
684
685        esp!(unsafe { nvs_commit(self.1) })?;
686
687        Ok(())
688    }
689
690    pub fn get_u64(&self, name: &str) -> Result<Option<u64>, EspError> {
691        let c_key = to_cstring_arg(name)?;
692        let mut result: [u64; 1] = [0; 1];
693
694        match unsafe { nvs_get_u64(self.1, c_key.as_ptr(), &mut result[0] as *mut _) } {
695            ESP_ERR_NVS_NOT_FOUND => Ok(None),
696            err => {
697                // bail on error
698                esp!(err)?;
699
700                Ok(Some(result[0]))
701            }
702        }
703    }
704
705    pub fn set_u64(&self, name: &str, val: u64) -> Result<(), EspError> {
706        let c_key = to_cstring_arg(name)?;
707
708        esp!(unsafe { nvs_set_u64(self.1, c_key.as_ptr(), val) })?;
709
710        esp!(unsafe { nvs_commit(self.1) })?;
711
712        Ok(())
713    }
714
715    pub fn get_i64(&self, name: &str) -> Result<Option<i64>, EspError> {
716        let c_key = to_cstring_arg(name)?;
717        let mut result: [i64; 1] = [0; 1];
718
719        match unsafe { nvs_get_i64(self.1, c_key.as_ptr(), &mut result[0] as *mut _) } {
720            ESP_ERR_NVS_NOT_FOUND => Ok(None),
721            err => {
722                // bail on error
723                esp!(err)?;
724
725                Ok(Some(result[0]))
726            }
727        }
728    }
729
730    pub fn set_i64(&self, name: &str, val: i64) -> Result<(), EspError> {
731        let c_key = to_cstring_arg(name)?;
732
733        esp!(unsafe { nvs_set_i64(self.1, c_key.as_ptr(), val) })?;
734
735        esp!(unsafe { nvs_commit(self.1) })?;
736
737        Ok(())
738    }
739
740    /// Erases all key-value pairs in the NVS namespace.
741    ///
742    /// # Errors
743    ///
744    /// This function will return an error if the NVS erase operation fails, this can happen because of
745    /// - a corrupted NVS partition
746    /// - the NVS is opened in read-only mode
747    /// - other internal errors from the underlying storage driver
748    pub fn erase_all(&self) -> Result<(), EspError> {
749        esp!(unsafe { nvs_erase_all(self.1) })?;
750
751        esp!(unsafe { nvs_commit(self.1) })?;
752
753        Ok(())
754    }
755
756    /// Returns struct to iterate over all keys stored in this NVS namespace with the specified data type.
757    ///
758    /// A data type of `None` will return all keys regardless of their type.
759    ///
760    /// # Mutating the NVS while iterating
761    ///
762    /// Both this function and others that mutate the NVS like [`EspNvs::remove`] only require an immutable
763    /// reference, making it possible to mutate the NVS while iterating over it. For example, one could remove
764    /// keys while iterating.
765    ///
766    /// It is **not** recommended to do this, because the iterator might skip keys. It will not result in
767    /// a panic or undefinied behavior.
768    ///
769    /// # Errors
770    ///
771    /// This function will return an error if
772    /// - there is no memory available for allocation of internal structures
773    /// - for some reason the [`EspNvs::handle`] is invalid (should not happen)
774    #[cfg(esp_idf_version_at_least_5_2_0)]
775    pub fn keys(&self, data_type: Option<NvsDataType>) -> Result<EspNvsKeys<'_>, EspError> {
776        let mut raw_iter: nvs_iterator_t = core::ptr::null_mut();
777
778        match unsafe {
779            nvs_entry_find_in_handle(
780                self.1,
781                data_type
782                    .map(|ty| ty as u32)
783                    .unwrap_or(nvs_type_t_NVS_TYPE_ANY),
784                &mut raw_iter as *mut _,
785            )
786        } {
787            ESP_ERR_NVS_NOT_FOUND => {
788                return Ok(EspNvsKeys {
789                    _nvs: PhantomData,
790                    raw_iter: core::ptr::null_mut(),
791                    is_exhausted: true,
792                    key_name_buffer: [0; 16],
793                });
794            }
795            other => esp!(other)?,
796        }
797
798        Ok(EspNvsKeys {
799            _nvs: PhantomData,
800            raw_iter,
801            is_exhausted: false,
802            key_name_buffer: [0; 16],
803        })
804    }
805}
806
807impl<T: NvsPartitionId> Drop for EspNvs<T> {
808    fn drop(&mut self) {
809        unsafe {
810            nvs_close(self.1);
811        }
812
813        info!("EspNvs dropped");
814    }
815}
816
817unsafe impl<T: NvsPartitionId> Send for EspNvs<T> {}
818
819impl RawHandle for EspNvs<NvsCustom> {
820    type Handle = nvs_handle_t;
821
822    fn handle(&self) -> Self::Handle {
823        self.1
824    }
825}
826impl RawHandle for EspNvs<NvsEncrypted> {
827    type Handle = nvs_handle_t;
828
829    fn handle(&self) -> Self::Handle {
830        self.1
831    }
832}
833impl RawHandle for EspNvs<NvsDefault> {
834    type Handle = nvs_handle_t;
835
836    fn handle(&self) -> Self::Handle {
837        self.1
838    }
839}
840
841#[cfg(esp_idf_version_at_least_5_2_0)]
842pub struct EspNvsKeys<'a> {
843    // The EspNvs must not be dropped while the iterator is still in use,
844    // this reference ensures that.
845    _nvs: PhantomData<&'a ()>,
846    raw_iter: nvs_iterator_t,
847    is_exhausted: bool,
848    key_name_buffer: [u8; 16],
849}
850
851#[cfg(esp_idf_version_at_least_5_2_0)]
852impl<'a> EspNvsKeys<'a> {
853    /// Returns the next key in the NVS namespace and its data type.
854    ///
855    /// After the last key is returned, this function will return `None` on subsequent calls.
856    pub fn next_key(&mut self) -> Option<(&str, NvsDataType)> {
857        if self.is_exhausted || self.raw_iter.is_null() {
858            return None;
859        }
860
861        let mut info: nvs_entry_info_t = Default::default();
862        match unsafe { nvs_entry_info(self.raw_iter, &mut info as *mut _) } {
863            ESP_ERR_NVS_NOT_FOUND => {
864                self.is_exhausted = true;
865                None
866            }
867            ESP_OK => {
868                // For the next iteration, the iterator must be advanced to the next entry,
869                // otherwise it will return the same entry again.
870                //
871                // This function call will fail if the iterator is
872                // - null, which is checked before this call
873                // - exhausted (if it is, it will set self.raw_iter to null and iteration will stop)
874                //
875                // For convenience, the error is ignored here, because it should never happen anyway.
876                // The usage example in C simply stops the iteration on error too and does not do any
877                // error handling.
878                let _ = esp!(unsafe { nvs_entry_next(&mut self.raw_iter as *mut _) });
879
880                // Copy the current key name into the buffer to make a str
881                // that lives for the lifetime of the &mut self borrow.
882                self.key_name_buffer[..info.key.len()].copy_from_slice(&info.key[..]);
883
884                Some((
885                    from_cstr(&self.key_name_buffer[..info.key.len()]),
886                    NvsDataType::from_nvs_type(info.type_).expect("Unknown NVS data type"),
887                ))
888            }
889            // The nvs_entry_info only fails if any of the arguments are null.
890            // The nvs_entry_info is never null, and self.raw_iter is checked for null before the invocation.
891            //
892            // Therefore this should never happen.
893            err => unreachable!(
894                "Unexpected error while iterating over NVS entries: {:?}",
895                esp!(err)
896            ),
897        }
898    }
899}
900
901#[cfg(esp_idf_version_at_least_5_2_0)]
902impl<'a> Drop for EspNvsKeys<'a> {
903    fn drop(&mut self) {
904        unsafe { nvs_release_iterator(self.raw_iter) };
905    }
906}
907
908/// A specialized key-value storage wrapper around `EspNvs` that provides a simplified interface
909/// for storing and retrieving arbitrary data as byte (`u8`) slices.
910///
911/// `EspKeyValueStorage` provides an interface for:
912/// - Storing any data that can be represented as `&[u8]`
913/// - Automatic optimization: values ≤7 bytes stored as ESP-NVS `u64` values, larger values as ESP-NVS blobs
914/// - Consistent `contains()` method that works correctly with this storage strategy
915/// - Full compatibility with Rust serde implementations (postcard, json, etc.) in that these can naturally do serde
916///   over byte slices
917///
918/// ## Usage
919///
920/// ```rust,no_run
921/// use esp_idf_svc::nvs::{EspDefaultNvsPartition, EspKeyValueStorage};
922///
923/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
924/// let partition = EspDefaultNvsPartition::take()?;
925/// let storage = EspKeyValueStorage::new(partition, "my_namespace", true)?;
926///
927/// // Store data as bytes
928/// let data = b"hello world";
929/// storage.set_raw("my_key", data)?;
930///
931/// // Check if key exists (this works correctly, unlike the original EspNvs bug)
932/// assert!(storage.contains("my_key")?);
933///
934/// // Retrieve data
935/// let mut buffer = [0u8; 64];
936/// if let Some(retrieved) = storage.get_raw("my_key", &mut buffer)? {
937///     assert_eq!(retrieved, data);
938/// }
939/// # Ok(())
940/// # }
941/// ```
942pub struct EspKeyValueStorage<T: NvsPartitionId>(EspNvs<T>);
943
944impl<T: NvsPartitionId> EspKeyValueStorage<T> {
945    pub const fn new(nvs: EspNvs<T>) -> Self {
946        Self(nvs)
947    }
948
949    pub fn contains(&self, name: &str) -> Result<bool, EspError> {
950        self.len(name).map(|v| v.is_some())
951    }
952
953    pub fn remove(&self, name: &str) -> Result<bool, EspError> {
954        self.0.remove(name)
955    }
956
957    fn len(&self, name: &str) -> Result<Option<usize>, EspError> {
958        match self.0.get_u64(name)? {
959            Some(value) => {
960                // u64 value was found, decode it
961                let len: u8 = (value & 0xff) as u8;
962                Ok(Some(len as _))
963            }
964            None => self.0.blob_len(name),
965        }
966    }
967
968    pub fn get_raw<'a>(&self, name: &str, buf: &'a mut [u8]) -> Result<Option<&'a [u8]>, EspError> {
969        match self.0.get_u64(name)? {
970            Some(mut value) => {
971                // u64 value was found, decode it
972                let len: u8 = (value & 0xff) as u8;
973
974                if buf.len() < len as _ {
975                    return Err(EspError::from_infallible::<ESP_ERR_NVS_INVALID_LENGTH>());
976                }
977
978                // Shift the u64 value to remove the length byte
979                value >>= 8;
980
981                let array: [u8; 7] = [
982                    (value & 0xff) as u8,
983                    ((value >> 8) & 0xff) as u8,
984                    ((value >> 16) & 0xff) as u8,
985                    ((value >> 24) & 0xff) as u8,
986                    ((value >> 32) & 0xff) as u8,
987                    ((value >> 40) & 0xff) as u8,
988                    ((value >> 48) & 0xff) as u8,
989                ];
990
991                buf[..len as usize].copy_from_slice(&array[..len as usize]);
992
993                Ok(Some(&buf[..len as usize]))
994            }
995            None => self.0.get_blob(name, buf),
996        }
997    }
998
999    pub fn set_raw(&self, name: &str, buf: &[u8]) -> Result<bool, EspError> {
1000        // start by just clearing this key, ignoring the result since it may not exist
1001        // TODO: This is not optimal, because if the chip is shut-down right after
1002        // the call to `remove`, the key will be gone forever.
1003
1004        _ = self.0.remove(name);
1005
1006        if buf.len() < 8 {
1007            let mut u64value: u_int64_t = 0;
1008
1009            for v in buf.iter().rev() {
1010                u64value <<= 8;
1011                u64value |= *v as u_int64_t;
1012            }
1013
1014            u64value <<= 8;
1015            u64value |= buf.len() as u_int64_t;
1016
1017            self.0.set_u64(name, u64value)?;
1018            Ok(true)
1019        } else {
1020            self.0.set_blob(name, buf)?;
1021            Ok(true)
1022        }
1023    }
1024}
1025
1026impl<T: NvsPartitionId> StorageBase for EspKeyValueStorage<T> {
1027    type Error = EspError;
1028
1029    fn contains(&self, name: &str) -> Result<bool, Self::Error> {
1030        EspKeyValueStorage::contains(self, name)
1031    }
1032
1033    fn remove(&mut self, name: &str) -> Result<bool, Self::Error> {
1034        EspKeyValueStorage::remove(self, name)
1035    }
1036}
1037
1038impl<T: NvsPartitionId> RawStorage for EspKeyValueStorage<T> {
1039    fn len(&self, name: &str) -> Result<Option<usize>, Self::Error> {
1040        EspKeyValueStorage::len(self, name)
1041    }
1042
1043    fn get_raw<'a>(&self, name: &str, buf: &'a mut [u8]) -> Result<Option<&'a [u8]>, Self::Error> {
1044        EspKeyValueStorage::get_raw(self, name, buf)
1045    }
1046
1047    fn set_raw(&mut self, name: &str, buf: &[u8]) -> Result<bool, Self::Error> {
1048        EspKeyValueStorage::set_raw(self, name, buf)
1049    }
1050}