Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nvs: support StorageIterate #249

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 110 additions & 2 deletions src/nvs.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
//! Non-Volatile Storage (NVS)
use core::ptr;
use core::{marker::PhantomData, mem::MaybeUninit, ptr};

extern crate alloc;
use alloc::sync::Arc;

use ::log::*;

use embedded_svc::storage::{RawStorage, StorageBase};
use embedded_svc::storage::{RawStorage, StorageBase, StorageEntry, StorageIterate};

use esp_idf_sys::*;

Expand Down Expand Up @@ -663,6 +663,25 @@ impl<T: NvsPartitionId> EspNvs<T> {

Ok(())
}

pub fn entries<'a>(&'a self) -> Result<EspNvsIterEntries<'a, T>, EspError> {
let mut iter = MaybeUninit::uninit();

match esp!(unsafe {
nvs_entry_find_in_handle(self.1, nvs_type_t_NVS_TYPE_ANY, iter.as_mut_ptr())
}) {
Err(e) if e.code() == ESP_ERR_NVS_NOT_FOUND => return Ok(EspNvsIterEntries::empty()),
Err(e) => return Err(e),
_ => (),
}

let iter = unsafe { iter.assume_init() };

Ok(EspNvsIterEntries {
nvs: PhantomData,
iter,
})
}
}

impl<T: NvsPartitionId> Drop for EspNvs<T> {
Expand Down Expand Up @@ -710,3 +729,92 @@ impl<T: NvsPartitionId> RawStorage for EspNvs<T> {
EspNvs::set_raw(self, name, buf)
}
}

impl<T: NvsPartitionId> StorageIterate for EspNvs<T> {
type Error = EspError;
type Entries<'a> = EspNvsIterEntries<'a, T>
where Self: 'a;
type Entry = NvsEntry;

fn entries<'a>(&'a self) -> Result<Self::Entries<'a>, Self::Error> {
EspNvs::entries(self)
}
}

pub struct EspNvsIterEntries<'a, T: NvsPartitionId> {
nvs: PhantomData<&'a EspNvs<T>>,
iter: nvs_iterator_t,
}

impl<'a, T: NvsPartitionId> EspNvsIterEntries<'a, T> {
fn empty() -> Self {
EspNvsIterEntries {
nvs: PhantomData,
iter: ptr::null_mut(),
}
}
}

impl<'a, T: NvsPartitionId> Drop for EspNvsIterEntries<'a, T> {
fn drop(&mut self) {
unsafe {
nvs_release_iterator(self.iter);
}
}
}

impl<'a, T: NvsPartitionId> Iterator for EspNvsIterEntries<'a, T> {
type Item = Result<NvsEntry, EspError>;

fn next(&mut self) -> Option<Self::Item> {
loop {
if self.iter.is_null() {
return None;
}

let mut info = MaybeUninit::<nvs_entry_info_t>::uninit();
// NOTE: because args are non-null, no error can occur
unsafe { nvs_entry_info(self.iter, info.as_mut_ptr()) };
let info = unsafe { info.assume_init() };

match esp!(unsafe { nvs_entry_next(&mut self.iter) }) {
Err(err) if err.code() == ESP_ERR_NVS_NOT_FOUND => {
self.iter = ptr::null_mut();
}
// No other errors are used in the esp-idf for nvs_entry_next when its args are non-null
Err(_err) => unreachable!(),
Ok(_) => (),
}

// skip non-utf8 keys
// SAFETY: nvs_entry_info_t is garunteed to have a null-terminated key
if unsafe { CStr::from_ptr(info.key.as_ptr()) }
.to_str()
.is_ok()
{
return Some(Ok(NvsEntry { info }));
}
}
}
}

pub struct NvsEntry {
info: nvs_entry_info_t,
}

impl NvsEntry {
pub fn name(&self) -> &str {
// SAFETY: nvs_entry_info_t guarantees that the key is null-terminated
// NOTE: should not panic because we check for utf8 in TryFrom
unsafe { CStr::from_ptr(self.info.key.as_ptr()) }
.to_str()
.ok()
.unwrap()
}
}

impl StorageEntry for NvsEntry {
fn name(&self) -> &str {
NvsEntry::name(self)
}
}