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 async fn find_last_page(
120 &mut self,
121 starting_page_index: usize,
122 page_state: PageState,
123 ) -> Result<Option<usize>, Error<S::Error>> {
124 let mut last_page_index = None;
125 for page_index in self.get_pages(starting_page_index) {
126 if page_state == self.get_page_state_cached(page_index).await? {
127 last_page_index = Some(page_index);
128 } else {
129 if last_page_index.is_some() {
130 return Ok(last_page_index);
131 }
132 }
133 }
134
135 Ok(last_page_index)
136 }
137
138 fn page_count(&self) -> NonZeroUsize {
139 let page_count = self.flash_range.len() / S::ERASE_SIZE;
140 NonZeroUsize::new(page_count.max(1)).unwrap()
142 }
143
144 fn get_pages(
146 &self,
147 starting_page_index: usize,
148 ) -> impl DoubleEndedIterator<Item = usize> + use<S, C, KEY> {
149 let page_count = self.page_count();
150 (0..page_count.get()).map(move |index| (index + starting_page_index) % page_count)
151 }
152
153 fn next_page(&self, page_index: usize) -> usize {
155 let page_count = self.page_count();
156 (page_index + 1) % page_count
157 }
158
159 fn previous_page(&self, page_index: usize) -> usize {
161 let page_count = self.page_count();
162
163 match page_index.checked_sub(1) {
164 Some(new_page_index) => new_page_index,
165 None => page_count.get() - 1,
166 }
167 }
168
169 async fn get_page_state_cached(
171 &mut self,
172 page_index: usize,
173 ) -> Result<PageState, Error<S::Error>> {
174 if let Some(cached_page_state) = self.cache.get_page_state(page_index) {
175 if cfg!(feature = "_check-cache") {
176 let discovered_state = self.get_page_state(page_index).await?;
177 assert_eq!(
178 cached_page_state, discovered_state,
179 "At page index: {page_index}",
180 );
181 }
182
183 return Ok(cached_page_state);
184 }
185
186 let discovered_state = self.get_page_state(page_index).await?;
187
188 self.cache
190 .notice_page_state(page_index, discovered_state, false);
191
192 Ok(discovered_state)
193 }
194
195 async fn get_page_state(&mut self, page_index: usize) -> Result<PageState, Error<S::Error>> {
197 let page_address = calculate_page_address::<S>(self.flash_range.clone(), page_index);
198 const HALF_MARKER_BITS: u32 = 4;
202
203 let mut buffer = [0; MAX_WORD_SIZE];
204 self.flash
205 .read(page_address, &mut buffer[..S::READ_SIZE])
206 .await
207 .map_err(|e| Error::Storage {
208 value: e,
209 #[cfg(feature = "_test")]
210 backtrace: std::backtrace::Backtrace::capture(),
211 })?;
212 let start_marked = buffer[..S::READ_SIZE]
213 .iter()
214 .map(|marker_byte| marker_byte.count_zeros())
215 .sum::<u32>()
216 >= HALF_MARKER_BITS;
217
218 self.flash
219 .read(
220 page_address + (S::ERASE_SIZE - S::READ_SIZE) as u32,
221 &mut buffer[..S::READ_SIZE],
222 )
223 .await
224 .map_err(|e| Error::Storage {
225 value: e,
226 #[cfg(feature = "_test")]
227 backtrace: std::backtrace::Backtrace::capture(),
228 })?;
229 let end_marked = buffer[..S::READ_SIZE]
230 .iter()
231 .map(|marker_byte| marker_byte.count_zeros())
232 .sum::<u32>()
233 >= HALF_MARKER_BITS;
234
235 let discovered_state = match (start_marked, end_marked) {
236 (true, true) => PageState::Closed,
237 (true, false) => PageState::PartialOpen,
238 (false, true) => {
240 return Err(Error::Corrupted {
241 #[cfg(feature = "_test")]
242 backtrace: std::backtrace::Backtrace::capture(),
243 });
244 }
245 (false, false) => PageState::Open,
246 };
247
248 Ok(discovered_state)
249 }
250
251 async fn open_page(&mut self, page_index: usize) -> Result<(), Error<S::Error>> {
253 self.cache
254 .notice_page_state(page_index, PageState::Open, true);
255
256 let page_address = calculate_page_address::<S>(self.flash_range.clone(), page_index);
257 let page_end_address =
258 calculate_page_end_address::<S>(self.flash_range.clone(), page_index);
259
260 self.flash
261 .erase(page_address, page_end_address)
262 .await
263 .map_err(|e| Error::Storage {
264 value: e,
265 #[cfg(feature = "_test")]
266 backtrace: std::backtrace::Backtrace::capture(),
267 })?;
268
269 Ok(())
270 }
271
272 async fn close_page(&mut self, page_index: usize) -> Result<(), Error<S::Error>> {
274 let current_state = self.partial_close_page(page_index).await?;
275
276 if current_state != PageState::PartialOpen {
277 return Ok(());
278 }
279
280 self.cache
281 .notice_page_state(page_index, PageState::Closed, true);
282
283 let buffer = AlignedBuf([MARKER; MAX_WORD_SIZE]);
284 let page_end_address =
285 calculate_page_end_address::<S>(self.flash_range.clone(), page_index)
286 - S::WORD_SIZE as u32;
287 self.flash
289 .write(page_end_address, &buffer[..S::WORD_SIZE])
290 .await
291 .map_err(|e| Error::Storage {
292 value: e,
293 #[cfg(feature = "_test")]
294 backtrace: std::backtrace::Backtrace::capture(),
295 })?;
296
297 Ok(())
298 }
299
300 async fn partial_close_page(
302 &mut self,
303 page_index: usize,
304 ) -> Result<PageState, Error<S::Error>> {
305 let current_state = self.get_page_state_cached(page_index).await?;
306
307 if current_state != PageState::Open {
308 return Ok(current_state);
309 }
310
311 let new_state = match current_state {
312 PageState::Closed => PageState::Closed,
313 PageState::PartialOpen | PageState::Open => PageState::PartialOpen,
314 };
315
316 self.cache.notice_page_state(page_index, new_state, true);
317
318 let buffer = AlignedBuf([MARKER; MAX_WORD_SIZE]);
319 let page_start_address = calculate_page_address::<S>(self.flash_range.clone(), page_index);
320 self.flash
322 .write(page_start_address, &buffer[..S::WORD_SIZE])
323 .await
324 .map_err(|e| Error::Storage {
325 value: e,
326 #[cfg(feature = "_test")]
327 backtrace: std::backtrace::Backtrace::capture(),
328 })?;
329
330 Ok(new_state)
331 }
332
333 #[cfg(any(test, feature = "std", fuzzing))]
334 pub async fn print_items(&mut self) -> String {
336 use crate::NorFlashExt;
337 use std::fmt::Write;
338
339 let mut buf = [0; 1024 * 16];
340
341 let mut s = String::new();
342
343 writeln!(s, "Items in flash:").unwrap();
344
345 for page_index in self.get_pages(0) {
346 writeln!(
347 s,
348 " Page {page_index} ({}):",
349 match self.get_page_state(page_index).await {
350 Ok(value) => format!("{value:?}"),
351 Err(e) => format!("Error ({e:?})"),
352 },
353 )
354 .unwrap();
355 let page_data_start =
356 crate::calculate_page_address::<S>(self.flash_range.clone(), page_index)
357 + S::WORD_SIZE as u32;
358 let page_data_end =
359 crate::calculate_page_end_address::<S>(self.flash_range.clone(), page_index)
360 - S::WORD_SIZE as u32;
361
362 let mut it = crate::item::ItemHeaderIter::new(page_data_start, page_data_end);
363 while let (Some(header), item_address) =
364 it.traverse(&mut self.flash, |_, _| false).await.unwrap()
365 {
366 let next_item_address = header.next_item_address::<S>(item_address);
367 let maybe_item = match header
368 .read_item(&mut self.flash, &mut buf, item_address, page_data_end)
369 .await
370 {
371 Ok(maybe_item) => maybe_item,
372 Err(e) => {
373 writeln!(
374 s,
375 " Item COULD NOT BE READ at {item_address}..{next_item_address}"
376 )
377 .unwrap();
378
379 println!("{s}");
380 panic!("{e:?}");
381 }
382 };
383
384 writeln!(
385 s,
386 " Item {maybe_item:?} at {item_address}..{next_item_address}"
387 )
388 .unwrap();
389 }
390 }
391
392 s
393 }
394
395 pub fn destroy(self) -> (S, C) {
399 (self.flash, self.cache)
400 }
401
402 pub const fn flash(&mut self) -> &mut S {
404 &mut self.flash
405 }
406
407 pub const fn flash_range(&self) -> Range<u32> {
409 self.flash_range.start..self.flash_range.end
410 }
411}
412
413const fn round_up_to_alignment<S: NorFlash>(value: u32) -> u32 {
416 value.next_multiple_of(S::WORD_SIZE as u32)
417}
418
419const fn round_up_to_alignment_usize<S: NorFlash>(value: usize) -> usize {
422 value.next_multiple_of(S::WORD_SIZE)
423}
424
425const fn round_down_to_alignment<S: NorFlash>(value: u32) -> u32 {
428 let alignment = S::WORD_SIZE as u32;
429 (value / alignment) * alignment
430}
431
432const fn round_down_to_alignment_usize<S: NorFlash>(value: usize) -> usize {
435 round_down_to_alignment::<S>(value as u32) as usize
436}
437
438const fn calculate_page_address<S: NorFlash>(flash_range: Range<u32>, page_index: usize) -> u32 {
440 flash_range.start + (S::ERASE_SIZE * page_index) as u32
441}
442
443const fn calculate_page_end_address<S: NorFlash>(
445 flash_range: Range<u32>,
446 page_index: usize,
447) -> u32 {
448 flash_range.start + (S::ERASE_SIZE * (page_index + 1)) as u32
449}
450
451const fn calculate_page_index<S: NorFlash>(flash_range: Range<u32>, address: u32) -> usize {
453 (address - flash_range.start) as usize / S::ERASE_SIZE
454}
455
456const fn calculate_page_size<S: NorFlash>() -> usize {
457 S::ERASE_SIZE - S::WORD_SIZE * 2
459}
460
461const MARKER: u8 = 0;
463
464#[derive(Debug, Clone, Copy, PartialEq, Eq)]
466#[cfg_attr(feature = "defmt", derive(defmt::Format))]
467pub enum PageState {
468 Closed,
470 PartialOpen,
472 Open,
474}
475
476#[allow(dead_code)]
477impl PageState {
478 #[must_use]
482 fn is_closed(self) -> bool {
483 matches!(self, Self::Closed)
484 }
485
486 #[must_use]
490 fn is_partial_open(self) -> bool {
491 matches!(self, Self::PartialOpen)
492 }
493
494 #[must_use]
498 fn is_open(self) -> bool {
499 matches!(self, Self::Open)
500 }
501}
502
503#[non_exhaustive]
505#[derive(Debug)]
506#[cfg_attr(feature = "defmt", derive(defmt::Format))]
507pub enum Error<S> {
508 Storage {
510 value: S,
512 #[cfg(feature = "_test")]
513 backtrace: std::backtrace::Backtrace,
515 },
516 FullStorage,
518 Corrupted {
521 #[cfg(feature = "_test")]
522 backtrace: std::backtrace::Backtrace,
524 },
525 LogicBug {
528 #[cfg(feature = "_test")]
529 backtrace: std::backtrace::Backtrace,
531 },
532 BufferTooBig,
534 BufferTooSmall(usize),
536 SerializationError(SerializationError),
538 ItemTooBig,
543}
544
545impl<S> From<SerializationError> for Error<S> {
546 fn from(v: SerializationError) -> Self {
547 Self::SerializationError(v)
548 }
549}
550
551impl<S: PartialEq> PartialEq for Error<S> {
552 fn eq(&self, other: &Self) -> bool {
553 match (self, other) {
554 (Self::Storage { value: l_value, .. }, Self::Storage { value: r_value, .. }) => {
555 l_value == r_value
556 }
557 (Self::BufferTooSmall(l0), Self::BufferTooSmall(r0)) => l0 == r0,
558 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
559 }
560 }
561}
562
563impl<S> core::fmt::Display for Error<S>
564where
565 S: core::fmt::Display,
566{
567 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
568 match self {
569 Error::Storage { value, .. } => write!(f, "Storage error: {value}"),
570 Error::FullStorage => write!(f, "Storage is full"),
571 #[cfg(not(feature = "_test"))]
572 Error::Corrupted { .. } => write!(f, "Storage is corrupted"),
573 #[cfg(feature = "_test")]
574 Error::Corrupted { backtrace } => write!(f, "Storage is corrupted\n{backtrace}"),
575 #[cfg(not(feature = "_test"))]
576 Error::LogicBug { .. } => write!(f, "Logic bug"),
577 #[cfg(feature = "_test")]
578 Error::LogicBug { backtrace } => write!(f, "Logic bug\n{backtrace}"),
579 Error::BufferTooBig => write!(f, "A provided buffer was to big to be used"),
580 Error::BufferTooSmall(needed) => write!(
581 f,
582 "A provided buffer was to small to be used. Needed was {needed}"
583 ),
584 Error::SerializationError(value) => write!(f, "Map value error: {value}"),
585 Error::ItemTooBig => write!(f, "The item is too big to fit in the flash"),
586 }
587 }
588}
589
590impl<S> core::error::Error for Error<S> where S: core::fmt::Display + core::fmt::Debug {}
591
592#[repr(align(4))]
594pub(crate) struct AlignedBuf<const SIZE: usize>(pub(crate) [u8; SIZE]);
595impl<const SIZE: usize> Deref for AlignedBuf<SIZE> {
596 type Target = [u8];
597 fn deref(&self) -> &Self::Target {
598 &self.0
599 }
600}
601
602impl<const SIZE: usize> DerefMut for AlignedBuf<SIZE> {
603 fn deref_mut(&mut self) -> &mut Self::Target {
604 &mut self.0
605 }
606}
607
608trait NorFlashExt {
610 const WORD_SIZE: usize;
612}
613
614impl<S: NorFlash> NorFlashExt for S {
615 const WORD_SIZE: usize = {
616 assert_read_write_sizes(Self::WRITE_SIZE, Self::READ_SIZE);
617
618 if Self::WRITE_SIZE > Self::READ_SIZE {
619 Self::WRITE_SIZE
620 } else {
621 Self::READ_SIZE
622 }
623 };
624}
625
626#[track_caller]
627const fn assert_read_write_sizes(write_size: usize, read_size: usize) {
628 assert!(
629 write_size.is_multiple_of(read_size) || read_size.is_multiple_of(write_size),
630 "Only flash with read and write sizes that are multiple of each other are supported"
631 );
632}
633
634macro_rules! run_with_auto_repair {
635 (function = $function:expr, repair = $repair_function:expr) => {
636 match $function {
637 Err(Error::Corrupted {
638 #[cfg(feature = "_test")]
639 backtrace: _backtrace,
640 ..
641 }) => {
642 #[cfg(all(feature = "_test", fuzzing))]
643 eprintln!(
644 "### Encountered curruption! Repairing now. Originated from:\n{_backtrace:#}"
645 );
646 $repair_function;
647 $function
648 }
649 val => val,
650 }
651 };
652}
653
654pub(crate) use run_with_auto_repair;
655
656use crate::cache::CacheImpl;
657
658#[cfg(test)]
659mod tests {
660 use crate::cache::Cache;
661
662 use super::*;
663 use futures_test::test;
664
665 type MockFlash = mock_flash::MockFlashBase<4, 4, 64>;
666
667 async fn write_aligned(
668 flash: &mut MockFlash,
669 offset: u32,
670 bytes: &[u8],
671 ) -> Result<(), mock_flash::MockFlashError> {
672 let mut buf = AlignedBuf([0; 256]);
673 buf[..bytes.len()].copy_from_slice(bytes);
674 flash.write(offset, &buf[..bytes.len()]).await
675 }
676
677 #[test]
678 async fn test_find_pages() {
679 let mut flash = MockFlash::default();
686
687 write_aligned(&mut flash, 0x000, &[MARKER, 0, 0, 0])
689 .await
690 .unwrap();
691 write_aligned(&mut flash, 0x100 - 4, &[0, 0, 0, MARKER])
692 .await
693 .unwrap();
694 write_aligned(&mut flash, 0x100, &[MARKER, 0, 0, 0])
696 .await
697 .unwrap();
698 write_aligned(&mut flash, 0x200 - 4, &[0, 0, 0, MARKER])
699 .await
700 .unwrap();
701 write_aligned(&mut flash, 0x200, &[MARKER, 0, 0, 0])
703 .await
704 .unwrap();
705
706 let mut storage = GenericStorage {
707 flash: flash,
708 flash_range: 0x000..0x400,
709 cache: Cache::new_uncached(),
710 _phantom: PhantomData::<()>,
711 };
712
713 assert_eq!(
714 storage.find_first_page(0, PageState::Open).await.unwrap(),
715 Some(3)
716 );
717 assert_eq!(
718 storage
719 .find_first_page(0, PageState::PartialOpen)
720 .await
721 .unwrap(),
722 Some(2)
723 );
724 assert_eq!(
725 storage
726 .find_first_page(1, PageState::PartialOpen)
727 .await
728 .unwrap(),
729 Some(2)
730 );
731 assert_eq!(
732 storage
733 .find_first_page(2, PageState::PartialOpen)
734 .await
735 .unwrap(),
736 Some(2)
737 );
738 assert_eq!(
739 storage.find_first_page(3, PageState::Open).await.unwrap(),
740 Some(3)
741 );
742
743 storage.flash_range = 0x000..0x200;
744 assert_eq!(
745 storage
746 .find_first_page(0, PageState::PartialOpen)
747 .await
748 .unwrap(),
749 None
750 );
751 storage.flash_range = 0x000..0x400;
752
753 assert_eq!(
754 storage.find_first_page(0, PageState::Closed).await.unwrap(),
755 Some(0)
756 );
757 assert_eq!(
758 storage.find_first_page(1, PageState::Closed).await.unwrap(),
759 Some(1)
760 );
761 assert_eq!(
762 storage.find_first_page(2, PageState::Closed).await.unwrap(),
763 Some(0)
764 );
765 assert_eq!(
766 storage.find_first_page(3, PageState::Closed).await.unwrap(),
767 Some(0)
768 );
769
770 storage.flash_range = 0x200..0x400;
771 assert_eq!(
772 storage.find_first_page(0, PageState::Closed).await.unwrap(),
773 None
774 );
775 }
776
777 #[test]
778 async fn read_write_sizes() {
779 assert_read_write_sizes(1, 1);
780 assert_read_write_sizes(1, 4);
781 assert_read_write_sizes(4, 4);
782 assert_read_write_sizes(4, 1);
783 }
784}