1#![cfg_attr(not(any(test, doctest, feature = "std", fuzzing)), no_std)]
2#![warn(missing_docs)]
3#![doc = include_str!("../README.md")]
4#![allow(clippy::cast_possible_truncation)]
5
6use core::num::NonZeroUsize;
7use core::{
8 fmt::Debug,
9 marker::PhantomData,
10 ops::{Deref, DerefMut, Range},
11};
12use embedded_storage_async::nor_flash::NorFlash;
13use map::SerializationError;
14
15#[cfg(feature = "alloc")]
16mod alloc_impl;
17#[cfg(feature = "arrayvec")]
18mod arrayvec_impl;
19pub mod cache;
20#[cfg(feature = "heapless-09")]
21mod heapless_09_impl;
22#[cfg(feature = "heapless")]
23mod heapless_impl;
24mod item;
25pub mod map;
26pub mod queue;
27
28#[cfg(any(test, doctest, feature = "_test"))]
29pub mod mock_flash;
31
32const MAX_WORD_SIZE: usize = 32;
37
38struct GenericStorage<S: NorFlash, C: CacheImpl<KEY>, KEY> {
47 flash: S,
48 flash_range: Range<u32>,
49 cache: C,
50 _phantom: PhantomData<KEY>,
51}
52
53impl<S: NorFlash, C: CacheImpl<KEY>, KEY> GenericStorage<S, C, KEY> {
54 pub async fn erase_all(&mut self) -> Result<(), Error<S::Error>> {
58 self.flash
59 .erase(self.flash_range.start, self.flash_range.end)
60 .await
61 .map_err(|e| Error::Storage {
62 value: e,
63 #[cfg(feature = "_test")]
64 backtrace: std::backtrace::Backtrace::capture(),
65 })
66 }
67
68 #[must_use]
73 pub const fn item_overhead_size() -> u32 {
74 item::ItemHeader::data_address::<S>(0)
75 }
76
77 async fn try_general_repair(&mut self) -> Result<(), Error<S::Error>> {
78 for page_index in self.get_pages(0) {
81 if matches!(
82 self.get_page_state(page_index).await,
83 Err(Error::Corrupted { .. })
84 ) {
85 self.open_page(page_index).await?;
86 }
87 }
88
89 #[cfg(fuzzing)]
90 eprintln!("General repair has been called");
91
92 Ok(())
93 }
94
95 async fn find_first_page(
99 &mut self,
100 starting_page_index: usize,
101 page_state: PageState,
102 ) -> Result<Option<usize>, Error<S::Error>> {
103 for page_index in self.get_pages(starting_page_index) {
104 if page_state == self.get_page_state_cached(page_index).await? {
105 return Ok(Some(page_index));
106 }
107 }
108
109 Ok(None)
110 }
111
112 fn page_count(&self) -> NonZeroUsize {
113 let page_count = self.flash_range.len() / S::ERASE_SIZE;
114 NonZeroUsize::new(page_count.max(1)).unwrap()
116 }
117
118 fn get_pages(
120 &self,
121 starting_page_index: usize,
122 ) -> impl DoubleEndedIterator<Item = usize> + use<S, C, KEY> {
123 let page_count = self.page_count();
124 (0..page_count.get()).map(move |index| (index + starting_page_index) % page_count)
125 }
126
127 fn next_page(&self, page_index: usize) -> usize {
129 let page_count = self.page_count();
130 (page_index + 1) % page_count
131 }
132
133 fn previous_page(&self, page_index: usize) -> usize {
135 let page_count = self.page_count();
136
137 match page_index.checked_sub(1) {
138 Some(new_page_index) => new_page_index,
139 None => page_count.get() - 1,
140 }
141 }
142
143 async fn get_page_state_cached(
145 &mut self,
146 page_index: usize,
147 ) -> Result<PageState, Error<S::Error>> {
148 if let Some(cached_page_state) = self.cache.get_page_state(page_index) {
149 if cfg!(feature = "_check-cache") {
150 let discovered_state = self.get_page_state(page_index).await?;
151 assert_eq!(
152 cached_page_state, discovered_state,
153 "At page index: {page_index}",
154 );
155 }
156
157 return Ok(cached_page_state);
158 }
159
160 let discovered_state = self.get_page_state(page_index).await?;
161
162 self.cache
164 .notice_page_state(page_index, discovered_state, false);
165
166 Ok(discovered_state)
167 }
168
169 async fn get_page_state(&mut self, page_index: usize) -> Result<PageState, Error<S::Error>> {
171 let page_address = calculate_page_address::<S>(self.flash_range.clone(), page_index);
172 const HALF_MARKER_BITS: u32 = 4;
176
177 let mut buffer = [0; MAX_WORD_SIZE];
178 self.flash
179 .read(page_address, &mut buffer[..S::READ_SIZE])
180 .await
181 .map_err(|e| Error::Storage {
182 value: e,
183 #[cfg(feature = "_test")]
184 backtrace: std::backtrace::Backtrace::capture(),
185 })?;
186 let start_marked = buffer[..S::READ_SIZE]
187 .iter()
188 .map(|marker_byte| marker_byte.count_zeros())
189 .sum::<u32>()
190 >= HALF_MARKER_BITS;
191
192 self.flash
193 .read(
194 page_address + (S::ERASE_SIZE - S::READ_SIZE) as u32,
195 &mut buffer[..S::READ_SIZE],
196 )
197 .await
198 .map_err(|e| Error::Storage {
199 value: e,
200 #[cfg(feature = "_test")]
201 backtrace: std::backtrace::Backtrace::capture(),
202 })?;
203 let end_marked = buffer[..S::READ_SIZE]
204 .iter()
205 .map(|marker_byte| marker_byte.count_zeros())
206 .sum::<u32>()
207 >= HALF_MARKER_BITS;
208
209 let discovered_state = match (start_marked, end_marked) {
210 (true, true) => PageState::Closed,
211 (true, false) => PageState::PartialOpen,
212 (false, true) => {
214 return Err(Error::Corrupted {
215 #[cfg(feature = "_test")]
216 backtrace: std::backtrace::Backtrace::capture(),
217 });
218 }
219 (false, false) => PageState::Open,
220 };
221
222 Ok(discovered_state)
223 }
224
225 async fn open_page(&mut self, page_index: usize) -> Result<(), Error<S::Error>> {
227 self.cache
228 .notice_page_state(page_index, PageState::Open, true);
229
230 let page_address = calculate_page_address::<S>(self.flash_range.clone(), page_index);
231 let page_end_address =
232 calculate_page_end_address::<S>(self.flash_range.clone(), page_index);
233
234 self.flash
235 .erase(page_address, page_end_address)
236 .await
237 .map_err(|e| Error::Storage {
238 value: e,
239 #[cfg(feature = "_test")]
240 backtrace: std::backtrace::Backtrace::capture(),
241 })?;
242
243 Ok(())
244 }
245
246 async fn close_page(&mut self, page_index: usize) -> Result<(), Error<S::Error>> {
248 let current_state = self.partial_close_page(page_index).await?;
249
250 if current_state != PageState::PartialOpen {
251 return Ok(());
252 }
253
254 self.cache
255 .notice_page_state(page_index, PageState::Closed, true);
256
257 let buffer = AlignedBuf([MARKER; MAX_WORD_SIZE]);
258 let page_end_address =
259 calculate_page_end_address::<S>(self.flash_range.clone(), page_index)
260 - S::WORD_SIZE as u32;
261 self.flash
263 .write(page_end_address, &buffer[..S::WORD_SIZE])
264 .await
265 .map_err(|e| Error::Storage {
266 value: e,
267 #[cfg(feature = "_test")]
268 backtrace: std::backtrace::Backtrace::capture(),
269 })?;
270
271 Ok(())
272 }
273
274 async fn partial_close_page(
276 &mut self,
277 page_index: usize,
278 ) -> Result<PageState, Error<S::Error>> {
279 let current_state = self.get_page_state_cached(page_index).await?;
280
281 if current_state != PageState::Open {
282 return Ok(current_state);
283 }
284
285 let new_state = match current_state {
286 PageState::Closed => PageState::Closed,
287 PageState::PartialOpen | PageState::Open => PageState::PartialOpen,
288 };
289
290 self.cache.notice_page_state(page_index, new_state, true);
291
292 let buffer = AlignedBuf([MARKER; MAX_WORD_SIZE]);
293 let page_start_address = calculate_page_address::<S>(self.flash_range.clone(), page_index);
294 self.flash
296 .write(page_start_address, &buffer[..S::WORD_SIZE])
297 .await
298 .map_err(|e| Error::Storage {
299 value: e,
300 #[cfg(feature = "_test")]
301 backtrace: std::backtrace::Backtrace::capture(),
302 })?;
303
304 Ok(new_state)
305 }
306
307 #[cfg(any(test, feature = "std", fuzzing))]
308 pub async fn print_items(&mut self) -> String {
310 use crate::NorFlashExt;
311 use std::fmt::Write;
312
313 let mut buf = [0; 1024 * 16];
314
315 let mut s = String::new();
316
317 writeln!(s, "Items in flash:").unwrap();
318
319 for page_index in self.get_pages(0) {
320 writeln!(
321 s,
322 " Page {page_index} ({}):",
323 match self.get_page_state(page_index).await {
324 Ok(value) => format!("{value:?}"),
325 Err(e) => format!("Error ({e:?})"),
326 },
327 )
328 .unwrap();
329 let page_data_start =
330 crate::calculate_page_address::<S>(self.flash_range.clone(), page_index)
331 + S::WORD_SIZE as u32;
332 let page_data_end =
333 crate::calculate_page_end_address::<S>(self.flash_range.clone(), page_index)
334 - S::WORD_SIZE as u32;
335
336 let mut it = crate::item::ItemHeaderIter::new(page_data_start, page_data_end);
337 while let (Some(header), item_address) =
338 it.traverse(&mut self.flash, |_, _| false).await.unwrap()
339 {
340 let next_item_address = header.next_item_address::<S>(item_address);
341 let maybe_item = match header
342 .read_item(&mut self.flash, &mut buf, item_address, page_data_end)
343 .await
344 {
345 Ok(maybe_item) => maybe_item,
346 Err(e) => {
347 writeln!(
348 s,
349 " Item COULD NOT BE READ at {item_address}..{next_item_address}"
350 )
351 .unwrap();
352
353 println!("{s}");
354 panic!("{e:?}");
355 }
356 };
357
358 writeln!(
359 s,
360 " Item {maybe_item:?} at {item_address}..{next_item_address}"
361 )
362 .unwrap();
363 }
364 }
365
366 s
367 }
368
369 pub fn destroy(self) -> (S, C) {
373 (self.flash, self.cache)
374 }
375
376 pub const fn flash(&mut self) -> &mut S {
378 &mut self.flash
379 }
380
381 pub const fn flash_range(&self) -> Range<u32> {
383 self.flash_range.start..self.flash_range.end
384 }
385}
386
387const fn round_up_to_alignment<S: NorFlash>(value: u32) -> u32 {
390 value.next_multiple_of(S::WORD_SIZE as u32)
391}
392
393const fn round_up_to_alignment_usize<S: NorFlash>(value: usize) -> usize {
396 value.next_multiple_of(S::WORD_SIZE)
397}
398
399const fn round_down_to_alignment<S: NorFlash>(value: u32) -> u32 {
402 let alignment = S::WORD_SIZE as u32;
403 (value / alignment) * alignment
404}
405
406const fn round_down_to_alignment_usize<S: NorFlash>(value: usize) -> usize {
409 round_down_to_alignment::<S>(value as u32) as usize
410}
411
412const fn calculate_page_address<S: NorFlash>(flash_range: Range<u32>, page_index: usize) -> u32 {
414 flash_range.start + (S::ERASE_SIZE * page_index) as u32
415}
416
417const fn calculate_page_end_address<S: NorFlash>(
419 flash_range: Range<u32>,
420 page_index: usize,
421) -> u32 {
422 flash_range.start + (S::ERASE_SIZE * (page_index + 1)) as u32
423}
424
425const fn calculate_page_index<S: NorFlash>(flash_range: Range<u32>, address: u32) -> usize {
427 (address - flash_range.start) as usize / S::ERASE_SIZE
428}
429
430const fn calculate_page_size<S: NorFlash>() -> usize {
431 S::ERASE_SIZE - S::WORD_SIZE * 2
433}
434
435const MARKER: u8 = 0;
437
438#[derive(Debug, Clone, Copy, PartialEq, Eq)]
440#[cfg_attr(feature = "defmt", derive(defmt::Format))]
441pub enum PageState {
442 Closed,
444 PartialOpen,
446 Open,
448}
449
450#[allow(dead_code)]
451impl PageState {
452 #[must_use]
456 fn is_closed(self) -> bool {
457 matches!(self, Self::Closed)
458 }
459
460 #[must_use]
464 fn is_partial_open(self) -> bool {
465 matches!(self, Self::PartialOpen)
466 }
467
468 #[must_use]
472 fn is_open(self) -> bool {
473 matches!(self, Self::Open)
474 }
475}
476
477#[non_exhaustive]
479#[derive(Debug)]
480#[cfg_attr(feature = "defmt", derive(defmt::Format))]
481pub enum Error<S> {
482 Storage {
484 value: S,
486 #[cfg(feature = "_test")]
487 backtrace: std::backtrace::Backtrace,
489 },
490 FullStorage,
492 Corrupted {
495 #[cfg(feature = "_test")]
496 backtrace: std::backtrace::Backtrace,
498 },
499 LogicBug {
502 #[cfg(feature = "_test")]
503 backtrace: std::backtrace::Backtrace,
505 },
506 BufferTooBig,
508 BufferTooSmall(usize),
510 SerializationError(SerializationError),
512 ItemTooBig,
517}
518
519impl<S> From<SerializationError> for Error<S> {
520 fn from(v: SerializationError) -> Self {
521 Self::SerializationError(v)
522 }
523}
524
525impl<S: PartialEq> PartialEq for Error<S> {
526 fn eq(&self, other: &Self) -> bool {
527 match (self, other) {
528 (Self::Storage { value: l_value, .. }, Self::Storage { value: r_value, .. }) => {
529 l_value == r_value
530 }
531 (Self::BufferTooSmall(l0), Self::BufferTooSmall(r0)) => l0 == r0,
532 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
533 }
534 }
535}
536
537impl<S> core::fmt::Display for Error<S>
538where
539 S: core::fmt::Display,
540{
541 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
542 match self {
543 Error::Storage { value, .. } => write!(f, "Storage error: {value}"),
544 Error::FullStorage => write!(f, "Storage is full"),
545 #[cfg(not(feature = "_test"))]
546 Error::Corrupted { .. } => write!(f, "Storage is corrupted"),
547 #[cfg(feature = "_test")]
548 Error::Corrupted { backtrace } => write!(f, "Storage is corrupted\n{backtrace}"),
549 #[cfg(not(feature = "_test"))]
550 Error::LogicBug { .. } => write!(f, "Logic bug"),
551 #[cfg(feature = "_test")]
552 Error::LogicBug { backtrace } => write!(f, "Logic bug\n{backtrace}"),
553 Error::BufferTooBig => write!(f, "A provided buffer was to big to be used"),
554 Error::BufferTooSmall(needed) => write!(
555 f,
556 "A provided buffer was to small to be used. Needed was {needed}"
557 ),
558 Error::SerializationError(value) => write!(f, "Map value error: {value}"),
559 Error::ItemTooBig => write!(f, "The item is too big to fit in the flash"),
560 }
561 }
562}
563
564impl<S> core::error::Error for Error<S> where S: core::fmt::Display + core::fmt::Debug {}
565
566#[repr(align(4))]
568pub(crate) struct AlignedBuf<const SIZE: usize>(pub(crate) [u8; SIZE]);
569impl<const SIZE: usize> Deref for AlignedBuf<SIZE> {
570 type Target = [u8];
571 fn deref(&self) -> &Self::Target {
572 &self.0
573 }
574}
575
576impl<const SIZE: usize> DerefMut for AlignedBuf<SIZE> {
577 fn deref_mut(&mut self) -> &mut Self::Target {
578 &mut self.0
579 }
580}
581
582trait NorFlashExt {
584 const WORD_SIZE: usize;
586}
587
588impl<S: NorFlash> NorFlashExt for S {
589 const WORD_SIZE: usize = {
590 assert_read_write_sizes(Self::WRITE_SIZE, Self::READ_SIZE);
591
592 if Self::WRITE_SIZE > Self::READ_SIZE {
593 Self::WRITE_SIZE
594 } else {
595 Self::READ_SIZE
596 }
597 };
598}
599
600#[track_caller]
601const fn assert_read_write_sizes(write_size: usize, read_size: usize) {
602 assert!(
603 write_size.is_multiple_of(read_size) || read_size.is_multiple_of(write_size),
604 "Only flash with read and write sizes that are multiple of each other are supported"
605 );
606}
607
608macro_rules! run_with_auto_repair {
609 (function = $function:expr, repair = $repair_function:expr) => {
610 match $function {
611 Err(Error::Corrupted {
612 #[cfg(feature = "_test")]
613 backtrace: _backtrace,
614 ..
615 }) => {
616 #[cfg(all(feature = "_test", fuzzing))]
617 eprintln!(
618 "### Encountered curruption! Repairing now. Originated from:\n{_backtrace:#}"
619 );
620 $repair_function;
621 $function
622 }
623 val => val,
624 }
625 };
626}
627
628pub(crate) use run_with_auto_repair;
629
630use crate::cache::CacheImpl;
631
632#[cfg(test)]
633mod tests {
634 use crate::cache::Cache;
635
636 use super::*;
637 use futures_test::test;
638
639 type MockFlash = mock_flash::MockFlashBase<4, 4, 64>;
640
641 async fn write_aligned(
642 flash: &mut MockFlash,
643 offset: u32,
644 bytes: &[u8],
645 ) -> Result<(), mock_flash::MockFlashError> {
646 let mut buf = AlignedBuf([0; 256]);
647 buf[..bytes.len()].copy_from_slice(bytes);
648 flash.write(offset, &buf[..bytes.len()]).await
649 }
650
651 #[test]
652 async fn test_find_pages() {
653 let mut flash = MockFlash::default();
660
661 write_aligned(&mut flash, 0x000, &[MARKER, 0, 0, 0])
663 .await
664 .unwrap();
665 write_aligned(&mut flash, 0x100 - 4, &[0, 0, 0, MARKER])
666 .await
667 .unwrap();
668 write_aligned(&mut flash, 0x100, &[MARKER, 0, 0, 0])
670 .await
671 .unwrap();
672 write_aligned(&mut flash, 0x200 - 4, &[0, 0, 0, MARKER])
673 .await
674 .unwrap();
675 write_aligned(&mut flash, 0x200, &[MARKER, 0, 0, 0])
677 .await
678 .unwrap();
679
680 let mut storage = GenericStorage {
681 flash: flash,
682 flash_range: 0x000..0x400,
683 cache: Cache::new_uncached(),
684 _phantom: PhantomData::<()>,
685 };
686
687 assert_eq!(
688 storage.find_first_page(0, PageState::Open).await.unwrap(),
689 Some(3)
690 );
691 assert_eq!(
692 storage
693 .find_first_page(0, PageState::PartialOpen)
694 .await
695 .unwrap(),
696 Some(2)
697 );
698 assert_eq!(
699 storage
700 .find_first_page(1, PageState::PartialOpen)
701 .await
702 .unwrap(),
703 Some(2)
704 );
705 assert_eq!(
706 storage
707 .find_first_page(2, PageState::PartialOpen)
708 .await
709 .unwrap(),
710 Some(2)
711 );
712 assert_eq!(
713 storage.find_first_page(3, PageState::Open).await.unwrap(),
714 Some(3)
715 );
716
717 storage.flash_range = 0x000..0x200;
718 assert_eq!(
719 storage
720 .find_first_page(0, PageState::PartialOpen)
721 .await
722 .unwrap(),
723 None
724 );
725 storage.flash_range = 0x000..0x400;
726
727 assert_eq!(
728 storage.find_first_page(0, PageState::Closed).await.unwrap(),
729 Some(0)
730 );
731 assert_eq!(
732 storage.find_first_page(1, PageState::Closed).await.unwrap(),
733 Some(1)
734 );
735 assert_eq!(
736 storage.find_first_page(2, PageState::Closed).await.unwrap(),
737 Some(0)
738 );
739 assert_eq!(
740 storage.find_first_page(3, PageState::Closed).await.unwrap(),
741 Some(0)
742 );
743
744 storage.flash_range = 0x200..0x400;
745 assert_eq!(
746 storage.find_first_page(0, PageState::Closed).await.unwrap(),
747 None
748 );
749 }
750
751 #[test]
752 async fn read_write_sizes() {
753 assert_read_write_sizes(1, 1);
754 assert_read_write_sizes(1, 4);
755 assert_read_write_sizes(4, 4);
756 assert_read_write_sizes(4, 1);
757 }
758}