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
12const MAX_KEY_LENGTH: usize = 15;
14const MAX_KEY_NUL_TERMINATED_LENGTH: usize = MAX_KEY_LENGTH + 1;
15
16#[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 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 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 pub const fn from_str(s: &str) -> Self {
63 let bytes = s.as_bytes();
64 Self::from_slice(bytes)
65 }
66
67 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
114pub 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 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 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 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 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 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(()), };
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 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 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}