#![doc = include_str ! ("../README.md")]
#![cfg_attr(not(target_arch = "x86_64"), no_std)]
pub mod error;
mod get;
mod internal;
pub mod platform;
mod raw;
mod set;
mod u24;
const MAX_KEY_LENGTH: usize = 15;
const MAX_KEY_NUL_TERMINATED_LENGTH: usize = MAX_KEY_LENGTH + 1;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Key([u8; MAX_KEY_NUL_TERMINATED_LENGTH]);
impl Key {
pub const fn from_array<const M: usize>(src: &[u8; M]) -> Self {
assert!(M <= MAX_KEY_LENGTH);
let mut dst = [0u8; MAX_KEY_NUL_TERMINATED_LENGTH];
let mut i = 0;
while i < M {
dst[i] = src[i];
i += 1;
}
Self(dst)
}
pub const fn from_slice(src: &[u8]) -> Self {
assert!(src.len() <= MAX_KEY_LENGTH);
let mut dst = [0u8; MAX_KEY_NUL_TERMINATED_LENGTH];
let mut i = 0;
while i < src.len() {
dst[i] = src[i];
i += 1;
}
Self(dst)
}
pub const fn from_str(s: &str) -> Self {
let bytes = s.as_bytes();
Self::from_slice(bytes)
}
pub const fn as_bytes(&self) -> &[u8; MAX_KEY_NUL_TERMINATED_LENGTH] {
&self.0
}
}
impl AsRef<[u8]> for Key {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
extern crate alloc;
use crate::error::Error;
use crate::get::Get;
use crate::internal::{ChunkIndex, ThinPage};
use crate::platform::Platform;
use crate::raw::{ENTRIES_PER_PAGE, FLASH_SECTOR_SIZE};
use crate::set::Set;
use alloc::collections::{BTreeMap, BinaryHeap};
use alloc::vec::Vec;
#[derive(Debug, Clone, PartialEq)]
pub struct NvsStatistics {
pub pages: PageStatistics,
pub entries_per_page: Vec<EntryStatistics>,
pub entries_overall: EntryStatistics,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PageStatistics {
pub empty: u16,
pub active: u16,
pub full: u16,
pub erasing: u16,
pub corrupted: u16,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EntryStatistics {
pub empty: u32,
pub written: u32,
pub erased: u32,
pub illegal: u32,
}
pub struct Nvs<'a, T>
where
T: Platform,
{
pub(crate) hal: &'a mut T,
pub(crate) base_address: usize,
pub(crate) sectors: u16,
pub(crate) faulted: bool,
pub(crate) namespaces: BTreeMap<Key, u8>,
pub(crate) free_pages: BinaryHeap<ThinPage>,
pub(crate) pages: Vec<ThinPage>,
}
impl<'a, T> Nvs<'a, T>
where
T: Platform,
{
pub fn new(
partition_offset: usize,
partition_size: usize,
hal: &'a mut T,
) -> Result<Nvs<'a, T>, Error> {
if !partition_offset.is_multiple_of(FLASH_SECTOR_SIZE) {
return Err(Error::InvalidPartitionOffset);
}
if !partition_size.is_multiple_of(FLASH_SECTOR_SIZE) {
return Err(Error::InvalidPartitionSize);
}
let sectors = partition_size / FLASH_SECTOR_SIZE;
if sectors > u16::MAX as usize {
return Err(Error::InvalidPartitionSize);
}
let mut nvs: Nvs<'a, T> = Self {
hal,
base_address: partition_offset,
sectors: sectors as u16,
namespaces: BTreeMap::new(),
free_pages: Default::default(),
pages: Default::default(),
faulted: false,
};
match nvs.load_sectors() {
Ok(()) => Ok(nvs),
Err(Error::FlashError) => {
nvs.faulted = true;
Err(Error::FlashError)
}
Err(e) => Err(e),
}
}
pub fn get<R>(&mut self, namespace: &Key, key: &Key) -> Result<R, Error>
where
Nvs<'a, T>: Get<R>,
{
match Get::get(self, namespace, key) {
Ok(val) => Ok(val),
Err(Error::FlashError) => {
self.faulted = true;
Err(Error::FlashError)
}
Err(e) => Err(e),
}
}
pub fn set<R>(&mut self, namespace: &Key, key: &Key, value: R) -> Result<(), Error>
where
Nvs<'a, T>: Set<R>,
{
if self.faulted {
return Err(Error::FlashError);
}
match Set::set(self, namespace, key, value) {
Ok(()) => Ok(()),
Err(Error::FlashError) => {
self.faulted = true;
Err(Error::FlashError)
}
Err(e) => Err(e),
}
}
pub fn delete(&mut self, namespace: &Key, key: &Key) -> Result<(), Error> {
if self.faulted {
return Err(Error::FlashError);
}
if key.0[MAX_KEY_LENGTH] != b'\0' {
return Err(Error::KeyMalformed);
}
if namespace.0[MAX_KEY_LENGTH] != b'\0' {
return Err(Error::NamespaceMalformed);
}
let namespace_index = match self.namespaces.get(namespace) {
Some(&idx) => idx,
None => return Ok(()), };
let result = self.delete_key(namespace_index, key, ChunkIndex::Any);
match result {
Err(Error::KeyNotFound) => Ok(()),
Err(Error::FlashError) => {
self.faulted = true;
Err(Error::FlashError)
}
other => other,
}
}
pub fn statistics(&mut self) -> Result<NvsStatistics, Error> {
if self.faulted {
return Err(Error::FlashError);
}
let mut page_stats = PageStatistics {
empty: 0,
active: 0,
full: 0,
erasing: 0,
corrupted: 0,
};
let mut all_pages: Vec<&ThinPage> = Vec::with_capacity(self.sectors as _);
all_pages.extend(self.pages.iter());
all_pages.extend(self.free_pages.iter());
all_pages.sort_by_key(|page| page.address);
let entries_per_page = all_pages
.into_iter()
.map(|page| {
match page.get_state() {
internal::ThinPageState::Active => page_stats.active += 1,
internal::ThinPageState::Full => page_stats.full += 1,
internal::ThinPageState::Freeing => page_stats.erasing += 1,
internal::ThinPageState::Corrupt => page_stats.corrupted += 1,
internal::ThinPageState::Invalid => page_stats.corrupted += 1,
internal::ThinPageState::Uninitialized => page_stats.empty += 1,
}
if *page.get_state() == internal::ThinPageState::Corrupt {
EntryStatistics {
empty: 0,
written: 0,
erased: 0,
illegal: ENTRIES_PER_PAGE as _,
}
} else {
let (empty, written, erased, illegal) = page.get_entry_statistics();
EntryStatistics {
empty,
written,
erased,
illegal,
}
}
})
.collect::<Vec<_>>();
let entries_overall = entries_per_page.iter().fold(
EntryStatistics {
empty: 0,
written: 0,
erased: 0,
illegal: 0,
},
|acc, x| EntryStatistics {
empty: acc.empty + x.empty,
written: acc.written + x.written,
erased: acc.erased + x.erased,
illegal: acc.illegal + x.illegal,
},
);
Ok(NvsStatistics {
pages: page_stats,
entries_per_page,
entries_overall,
})
}
}