1#![cfg_attr(not(any(test, doctest, feature = "std")), no_std)]
2#![deny(missing_docs)]
3#![doc = include_str!("../README.md")]
4
5use cache::PrivateCacheImpl;
11use core::{
12 fmt::Debug,
13 ops::{Deref, DerefMut, Range},
14};
15use embedded_storage_async::nor_flash::NorFlash;
16use map::SerializationError;
17
18#[cfg(feature = "alloc")]
19mod alloc_impl;
20#[cfg(feature = "arrayvec")]
21mod arrayvec_impl;
22pub mod cache;
23#[cfg(feature = "heapless")]
24mod heapless_impl;
25mod item;
26pub mod map;
27pub mod queue;
28
29#[cfg(any(test, doctest, feature = "_test"))]
30pub mod mock_flash;
32
33const MAX_WORD_SIZE: usize = 32;
38
39pub async fn erase_all<S: NorFlash>(
43 flash: &mut S,
44 flash_range: Range<u32>,
45) -> Result<(), Error<S::Error>> {
46 flash
47 .erase(flash_range.start, flash_range.end)
48 .await
49 .map_err(|e| Error::Storage {
50 value: e,
51 #[cfg(feature = "_test")]
52 backtrace: std::backtrace::Backtrace::capture(),
53 })
54}
55
56pub const fn item_overhead_size<S: NorFlash>() -> u32 {
61 item::ItemHeader::data_address::<S>(0)
62}
63
64#[repr(align(4))]
66pub(crate) struct AlignedBuf<const SIZE: usize>(pub(crate) [u8; SIZE]);
67impl<const SIZE: usize> Deref for AlignedBuf<SIZE> {
68 type Target = [u8];
69 fn deref(&self) -> &Self::Target {
70 &self.0
71 }
72}
73
74impl<const SIZE: usize> DerefMut for AlignedBuf<SIZE> {
75 fn deref_mut(&mut self) -> &mut Self::Target {
76 &mut self.0
77 }
78}
79
80async fn try_general_repair<S: NorFlash>(
81 flash: &mut S,
82 flash_range: Range<u32>,
83 cache: &mut impl PrivateCacheImpl,
84) -> Result<(), Error<S::Error>> {
85 for page_index in get_pages::<S>(flash_range.clone(), 0) {
88 if matches!(
89 get_page_state(flash, flash_range.clone(), cache, page_index).await,
90 Err(Error::Corrupted { .. })
91 ) {
92 open_page(flash, flash_range.clone(), cache, page_index).await?;
93 }
94 }
95
96 Ok(())
97}
98
99async fn find_first_page<S: NorFlash>(
103 flash: &mut S,
104 flash_range: Range<u32>,
105 cache: &mut impl PrivateCacheImpl,
106 starting_page_index: usize,
107 page_state: PageState,
108) -> Result<Option<usize>, Error<S::Error>> {
109 for page_index in get_pages::<S>(flash_range.clone(), starting_page_index) {
110 if page_state == get_page_state::<S>(flash, flash_range.clone(), cache, page_index).await? {
111 return Ok(Some(page_index));
112 }
113 }
114
115 Ok(None)
116}
117
118fn get_pages<S: NorFlash>(
120 flash_range: Range<u32>,
121 starting_page_index: usize,
122) -> impl DoubleEndedIterator<Item = usize> {
123 let page_count = flash_range.len() / S::ERASE_SIZE;
124 flash_range
125 .step_by(S::ERASE_SIZE)
126 .enumerate()
127 .map(move |(index, _)| (index + starting_page_index) % page_count)
128}
129
130fn next_page<S: NorFlash>(flash_range: Range<u32>, page_index: usize) -> usize {
132 let page_count = flash_range.len() / S::ERASE_SIZE;
133 (page_index + 1) % page_count
134}
135
136fn previous_page<S: NorFlash>(flash_range: Range<u32>, page_index: usize) -> usize {
138 let page_count = flash_range.len() / S::ERASE_SIZE;
139
140 match page_index.checked_sub(1) {
141 Some(new_page_index) => new_page_index,
142 None => page_count - 1,
143 }
144}
145
146const fn calculate_page_address<S: NorFlash>(flash_range: Range<u32>, page_index: usize) -> u32 {
148 flash_range.start + (S::ERASE_SIZE * page_index) as u32
149}
150const fn calculate_page_end_address<S: NorFlash>(
152 flash_range: Range<u32>,
153 page_index: usize,
154) -> u32 {
155 flash_range.start + (S::ERASE_SIZE * (page_index + 1)) as u32
156}
157#[allow(unused)]
159const fn calculate_page_index<S: NorFlash>(flash_range: Range<u32>, address: u32) -> usize {
160 (address - flash_range.start) as usize / S::ERASE_SIZE
161}
162
163const fn calculate_page_size<S: NorFlash>() -> usize {
164 S::ERASE_SIZE - S::WORD_SIZE * 2
166}
167
168const MARKER: u8 = 0;
170
171async fn get_page_state<S: NorFlash>(
173 flash: &mut S,
174 flash_range: Range<u32>,
175 cache: &mut impl PrivateCacheImpl,
176 page_index: usize,
177) -> Result<PageState, Error<S::Error>> {
178 if let Some(cached_page_state) = cache.get_page_state(page_index) {
179 return Ok(cached_page_state);
180 }
181
182 let page_address = calculate_page_address::<S>(flash_range, page_index);
183 const HALF_MARKER_BITS: u32 = 4;
187
188 let mut buffer = [0; MAX_WORD_SIZE];
189 flash
190 .read(page_address, &mut buffer[..S::READ_SIZE])
191 .await
192 .map_err(|e| Error::Storage {
193 value: e,
194 #[cfg(feature = "_test")]
195 backtrace: std::backtrace::Backtrace::capture(),
196 })?;
197 let start_marked = buffer[..S::READ_SIZE]
198 .iter()
199 .map(|marker_byte| marker_byte.count_zeros())
200 .sum::<u32>()
201 >= HALF_MARKER_BITS;
202
203 flash
204 .read(
205 page_address + (S::ERASE_SIZE - S::READ_SIZE) as u32,
206 &mut buffer[..S::READ_SIZE],
207 )
208 .await
209 .map_err(|e| Error::Storage {
210 value: e,
211 #[cfg(feature = "_test")]
212 backtrace: std::backtrace::Backtrace::capture(),
213 })?;
214 let end_marked = buffer[..S::READ_SIZE]
215 .iter()
216 .map(|marker_byte| marker_byte.count_zeros())
217 .sum::<u32>()
218 >= HALF_MARKER_BITS;
219
220 let discovered_state = match (start_marked, end_marked) {
221 (true, true) => PageState::Closed,
222 (true, false) => PageState::PartialOpen,
223 (false, true) => {
225 return Err(Error::Corrupted {
226 #[cfg(feature = "_test")]
227 backtrace: std::backtrace::Backtrace::capture(),
228 })
229 }
230 (false, false) => PageState::Open,
231 };
232
233 cache.notice_page_state(page_index, discovered_state, false);
235
236 Ok(discovered_state)
237}
238
239async fn open_page<S: NorFlash>(
241 flash: &mut S,
242 flash_range: Range<u32>,
243 cache: &mut impl PrivateCacheImpl,
244 page_index: usize,
245) -> Result<(), Error<S::Error>> {
246 cache.notice_page_state(page_index, PageState::Open, true);
247
248 flash
249 .erase(
250 calculate_page_address::<S>(flash_range.clone(), page_index),
251 calculate_page_end_address::<S>(flash_range.clone(), page_index),
252 )
253 .await
254 .map_err(|e| Error::Storage {
255 value: e,
256 #[cfg(feature = "_test")]
257 backtrace: std::backtrace::Backtrace::capture(),
258 })?;
259
260 Ok(())
261}
262
263async fn close_page<S: NorFlash>(
265 flash: &mut S,
266 flash_range: Range<u32>,
267 cache: &mut impl PrivateCacheImpl,
268 page_index: usize,
269) -> Result<(), Error<S::Error>> {
270 let current_state =
271 partial_close_page::<S>(flash, flash_range.clone(), cache, page_index).await?;
272
273 if current_state != PageState::PartialOpen {
274 return Ok(());
275 }
276
277 cache.notice_page_state(page_index, PageState::Closed, true);
278
279 let buffer = AlignedBuf([MARKER; MAX_WORD_SIZE]);
280 flash
282 .write(
283 calculate_page_end_address::<S>(flash_range, page_index) - S::WORD_SIZE as u32,
284 &buffer[..S::WORD_SIZE],
285 )
286 .await
287 .map_err(|e| Error::Storage {
288 value: e,
289 #[cfg(feature = "_test")]
290 backtrace: std::backtrace::Backtrace::capture(),
291 })?;
292
293 Ok(())
294}
295
296async fn partial_close_page<S: NorFlash>(
298 flash: &mut S,
299 flash_range: Range<u32>,
300 cache: &mut impl PrivateCacheImpl,
301 page_index: usize,
302) -> Result<PageState, Error<S::Error>> {
303 let current_state = get_page_state::<S>(flash, flash_range.clone(), cache, page_index).await?;
304
305 if current_state != PageState::Open {
306 return Ok(current_state);
307 }
308
309 let new_state = match current_state {
310 PageState::Closed => PageState::Closed,
311 PageState::PartialOpen => PageState::PartialOpen,
312 PageState::Open => PageState::PartialOpen,
313 };
314
315 cache.notice_page_state(page_index, new_state, true);
316
317 let buffer = AlignedBuf([MARKER; MAX_WORD_SIZE]);
318 flash
320 .write(
321 calculate_page_address::<S>(flash_range, page_index),
322 &buffer[..S::WORD_SIZE],
323 )
324 .await
325 .map_err(|e| Error::Storage {
326 value: e,
327 #[cfg(feature = "_test")]
328 backtrace: std::backtrace::Backtrace::capture(),
329 })?;
330
331 Ok(new_state)
332}
333
334#[derive(Debug, Clone, Copy, PartialEq, Eq)]
336#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
337enum PageState {
338 Closed,
340 PartialOpen,
342 Open,
344}
345
346#[allow(dead_code)]
347impl PageState {
348 #[must_use]
352 fn is_closed(&self) -> bool {
353 matches!(self, Self::Closed)
354 }
355
356 #[must_use]
360 fn is_partial_open(&self) -> bool {
361 matches!(self, Self::PartialOpen)
362 }
363
364 #[must_use]
368 fn is_open(&self) -> bool {
369 matches!(self, Self::Open)
370 }
371}
372
373#[non_exhaustive]
375#[derive(Debug)]
376#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
377pub enum Error<S> {
378 Storage {
380 value: S,
382 #[cfg(feature = "_test")]
383 backtrace: std::backtrace::Backtrace,
385 },
386 FullStorage,
388 Corrupted {
391 #[cfg(feature = "_test")]
392 backtrace: std::backtrace::Backtrace,
394 },
395 BufferTooBig,
397 BufferTooSmall(usize),
399 SerializationError(SerializationError),
401 ItemTooBig,
406}
407
408impl<S> From<SerializationError> for Error<S> {
409 fn from(v: SerializationError) -> Self {
410 Self::SerializationError(v)
411 }
412}
413
414impl<S: PartialEq> PartialEq for Error<S> {
415 fn eq(&self, other: &Self) -> bool {
416 match (self, other) {
417 (Self::Storage { value: l_value, .. }, Self::Storage { value: r_value, .. }) => {
418 l_value == r_value
419 }
420 (Self::BufferTooSmall(l0), Self::BufferTooSmall(r0)) => l0 == r0,
421 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
422 }
423 }
424}
425
426impl<S> core::fmt::Display for Error<S>
427where
428 S: core::fmt::Display,
429{
430 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
431 match self {
432 Error::Storage { value, .. } => write!(f, "Storage error: {value}"),
433 Error::FullStorage => write!(f, "Storage is full"),
434 Error::Corrupted { .. } => write!(f, "Storage is corrupted"),
435 Error::BufferTooBig => write!(f, "A provided buffer was to big to be used"),
436 Error::BufferTooSmall(needed) => write!(
437 f,
438 "A provided buffer was to small to be used. Needed was {needed}"
439 ),
440 Error::SerializationError(value) => write!(f, "Map value error: {value}"),
441 Error::ItemTooBig => write!(f, "The item is too big to fit in the flash"),
442 }
443 }
444}
445
446#[cfg(feature = "std")]
447impl<S> std::error::Error for Error<S> where S: std::error::Error {}
448
449const fn round_up_to_alignment<S: NorFlash>(value: u32) -> u32 {
452 let alignment = S::WORD_SIZE as u32;
453 match value % alignment {
454 0 => value,
455 r => value + (alignment - r),
456 }
457}
458
459const fn round_up_to_alignment_usize<S: NorFlash>(value: usize) -> usize {
462 round_up_to_alignment::<S>(value as u32) as usize
463}
464
465const fn round_down_to_alignment<S: NorFlash>(value: u32) -> u32 {
468 let alignment = S::WORD_SIZE as u32;
469 (value / alignment) * alignment
470}
471
472const fn round_down_to_alignment_usize<S: NorFlash>(value: usize) -> usize {
475 round_down_to_alignment::<S>(value as u32) as usize
476}
477
478trait NorFlashExt {
480 const WORD_SIZE: usize;
482}
483
484impl<S: NorFlash> NorFlashExt for S {
485 const WORD_SIZE: usize = if Self::WRITE_SIZE > Self::READ_SIZE {
486 Self::WRITE_SIZE
487 } else {
488 Self::READ_SIZE
489 };
490}
491
492macro_rules! run_with_auto_repair {
493 (function = $function:expr, repair = $repair_function:expr) => {
494 match $function {
495 Err(Error::Corrupted {
496 #[cfg(feature = "_test")]
497 backtrace: _backtrace,
498 ..
499 }) => {
500 #[cfg(all(feature = "_test", fuzzing_repro))]
501 eprintln!(
502 "### Encountered curruption! Repairing now. Originated from:\n{_backtrace:#}"
503 );
504 $repair_function;
505 $function
506 }
507 val => val,
508 }
509 };
510}
511
512pub(crate) use run_with_auto_repair;
513
514#[cfg(test)]
515mod tests {
516 use super::*;
517 use futures_test::test;
518
519 type MockFlash = mock_flash::MockFlashBase<4, 4, 64>;
520
521 async fn write_aligned(
522 flash: &mut MockFlash,
523 offset: u32,
524 bytes: &[u8],
525 ) -> Result<(), mock_flash::MockFlashError> {
526 let mut buf = AlignedBuf([0; 256]);
527 buf[..bytes.len()].copy_from_slice(bytes);
528 flash.write(offset, &buf[..bytes.len()]).await
529 }
530
531 #[test]
532 async fn test_find_pages() {
533 let mut flash = MockFlash::default();
540 write_aligned(&mut flash, 0x000, &[MARKER, 0, 0, 0])
542 .await
543 .unwrap();
544 write_aligned(&mut flash, 0x100 - 4, &[0, 0, 0, MARKER])
545 .await
546 .unwrap();
547 write_aligned(&mut flash, 0x100, &[MARKER, 0, 0, 0])
549 .await
550 .unwrap();
551 write_aligned(&mut flash, 0x200 - 4, &[0, 0, 0, MARKER])
552 .await
553 .unwrap();
554 write_aligned(&mut flash, 0x200, &[MARKER, 0, 0, 0])
556 .await
557 .unwrap();
558
559 assert_eq!(
560 find_first_page(
561 &mut flash,
562 0x000..0x400,
563 &mut cache::NoCache::new(),
564 0,
565 PageState::Open
566 )
567 .await
568 .unwrap(),
569 Some(3)
570 );
571 assert_eq!(
572 find_first_page(
573 &mut flash,
574 0x000..0x400,
575 &mut cache::NoCache::new(),
576 0,
577 PageState::PartialOpen
578 )
579 .await
580 .unwrap(),
581 Some(2)
582 );
583 assert_eq!(
584 find_first_page(
585 &mut flash,
586 0x000..0x400,
587 &mut cache::NoCache::new(),
588 1,
589 PageState::PartialOpen
590 )
591 .await
592 .unwrap(),
593 Some(2)
594 );
595 assert_eq!(
596 find_first_page(
597 &mut flash,
598 0x000..0x400,
599 &mut cache::NoCache::new(),
600 2,
601 PageState::PartialOpen
602 )
603 .await
604 .unwrap(),
605 Some(2)
606 );
607 assert_eq!(
608 find_first_page(
609 &mut flash,
610 0x000..0x400,
611 &mut cache::NoCache::new(),
612 3,
613 PageState::Open
614 )
615 .await
616 .unwrap(),
617 Some(3)
618 );
619 assert_eq!(
620 find_first_page(
621 &mut flash,
622 0x000..0x200,
623 &mut cache::NoCache::new(),
624 0,
625 PageState::PartialOpen
626 )
627 .await
628 .unwrap(),
629 None
630 );
631
632 assert_eq!(
633 find_first_page(
634 &mut flash,
635 0x000..0x400,
636 &mut cache::NoCache::new(),
637 0,
638 PageState::Closed
639 )
640 .await
641 .unwrap(),
642 Some(0)
643 );
644 assert_eq!(
645 find_first_page(
646 &mut flash,
647 0x000..0x400,
648 &mut cache::NoCache::new(),
649 1,
650 PageState::Closed
651 )
652 .await
653 .unwrap(),
654 Some(1)
655 );
656 assert_eq!(
657 find_first_page(
658 &mut flash,
659 0x000..0x400,
660 &mut cache::NoCache::new(),
661 2,
662 PageState::Closed
663 )
664 .await
665 .unwrap(),
666 Some(0)
667 );
668 assert_eq!(
669 find_first_page(
670 &mut flash,
671 0x000..0x400,
672 &mut cache::NoCache::new(),
673 3,
674 PageState::Closed
675 )
676 .await
677 .unwrap(),
678 Some(0)
679 );
680 assert_eq!(
681 find_first_page(
682 &mut flash,
683 0x200..0x400,
684 &mut cache::NoCache::new(),
685 0,
686 PageState::Closed
687 )
688 .await
689 .unwrap(),
690 None
691 );
692 }
693}