sequential_storage/
lib.rs

1#![cfg_attr(not(any(test, doctest, feature = "std")), no_std)]
2#![deny(missing_docs)]
3#![doc = include_str!("../README.md")]
4
5// Assumptions made in this crate:
6//
7// - flash erase size is quite big, aka, this is a paged flash
8// - flash write size is quite small, so it writes words and not full pages
9
10use 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"))]
30/// An in-memory flash type that can be used for mocking.
31pub mod mock_flash;
32
33/// The biggest wordsize we support.
34///
35/// Stm32 internal flash has 256-bit words, so 32 bytes.
36/// Many flashes have 4-byte or 1-byte words.
37const MAX_WORD_SIZE: usize = 32;
38
39/// Resets the flash in the entire given flash range.
40///
41/// This is just a thin helper function as it just calls the flash's erase function.
42pub 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
56/// Get the minimal overhead size per stored item for the given flash type.
57///
58/// The associated data of each item is additionally padded to a full flash word size, but that's not part of this number.  
59/// This means the full item length is `returned number + (data length).next_multiple_of(S::WORD_SIZE)`.
60pub const fn item_overhead_size<S: NorFlash>() -> u32 {
61    item::ItemHeader::data_address::<S>(0)
62}
63
64// Type representing buffer aligned to 4 byte boundary.
65#[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    // Loop through the pages and get their state. If one returns the corrupted error,
86    // the page is likely half-erased. Fix for that is to re-erase again to hopefully finish the job.
87    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
99/// Find the first page that is in the given page state.
100///
101/// The search starts at starting_page_index (and wraps around back to 0 if required)
102async 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
118/// Get all pages in the flash range from the given start to end (that might wrap back to 0)
119fn 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
130/// Get the next page index (wrapping around to 0 if required)
131fn 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
136/// Get the previous page index (wrapping around to the biggest page if required)
137fn 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
146/// Calculate the first address of the given page
147const 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}
150/// Calculate the last address (exclusive) of the given page
151const 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/// Get the page index from any address located inside that page
158#[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    // Page minus the two page status words
165    S::ERASE_SIZE - S::WORD_SIZE * 2
166}
167
168/// The marker being used for page states
169const MARKER: u8 = 0;
170
171/// Get the state of the page located at the given index
172async 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    /// We only care about the data in the first byte to aid shutdown/cancellation.
184    /// But we also don't want it to be too too definitive because we want to survive the occasional bitflip.
185    /// So only half of the byte needs to be zero.
186    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        // Probably an interrupted erase
224        (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    // Not dirty because nothing changed and nothing can be inconsistent
234    cache.notice_page_state(page_index, discovered_state, false);
235
236    Ok(discovered_state)
237}
238
239/// Erase the page to open it again
240async 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
263/// Fully closes a page by writing both the start and end marker
264async 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    // Close the end marker
281    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
296/// Partially close a page by writing the start marker
297async 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    // Close the start marker
319    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/// The state of a page
335#[derive(Debug, Clone, Copy, PartialEq, Eq)]
336#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
337enum PageState {
338    /// This page was fully written and has now been sealed
339    Closed,
340    /// This page has been written to, but may have some space left over
341    PartialOpen,
342    /// This page is fully erased
343    Open,
344}
345
346#[allow(dead_code)]
347impl PageState {
348    /// Returns `true` if the page state is [`Closed`].
349    ///
350    /// [`Closed`]: PageState::Closed
351    #[must_use]
352    fn is_closed(&self) -> bool {
353        matches!(self, Self::Closed)
354    }
355
356    /// Returns `true` if the page state is [`PartialOpen`].
357    ///
358    /// [`PartialOpen`]: PageState::PartialOpen
359    #[must_use]
360    fn is_partial_open(&self) -> bool {
361        matches!(self, Self::PartialOpen)
362    }
363
364    /// Returns `true` if the page state is [`Open`].
365    ///
366    /// [`Open`]: PageState::Open
367    #[must_use]
368    fn is_open(&self) -> bool {
369        matches!(self, Self::Open)
370    }
371}
372
373/// The main error type
374#[non_exhaustive]
375#[derive(Debug)]
376#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
377pub enum Error<S> {
378    /// An error in the storage (flash)
379    Storage {
380        /// The error value
381        value: S,
382        #[cfg(feature = "_test")]
383        /// Backtrace made at the construction of the error
384        backtrace: std::backtrace::Backtrace,
385    },
386    /// The item cannot be stored anymore because the storage is full.
387    FullStorage,
388    /// It's been detected that the memory is likely corrupted.
389    /// You may want to erase the memory to recover.
390    Corrupted {
391        #[cfg(feature = "_test")]
392        /// Backtrace made at the construction of the error
393        backtrace: std::backtrace::Backtrace,
394    },
395    /// A provided buffer was to big to be used
396    BufferTooBig,
397    /// A provided buffer was to small to be used (usize is size needed)
398    BufferTooSmall(usize),
399    /// A serialization error (from the key or value)
400    SerializationError(SerializationError),
401    /// The item does not fit in flash, ever.
402    /// This is different from [Error::FullStorage] because this item is too big to fit even in empty flash.
403    ///
404    /// See the readme for more info about the constraints on item sizes.
405    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
449/// Round up the the given number to align with the wordsize of the flash.
450/// If the number is already aligned, it is not changed.
451const 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
459/// Round up the the given number to align with the wordsize of the flash.
460/// If the number is already aligned, it is not changed.
461const fn round_up_to_alignment_usize<S: NorFlash>(value: usize) -> usize {
462    round_up_to_alignment::<S>(value as u32) as usize
463}
464
465/// Round down the the given number to align with the wordsize of the flash.
466/// If the number is already aligned, it is not changed.
467const fn round_down_to_alignment<S: NorFlash>(value: u32) -> u32 {
468    let alignment = S::WORD_SIZE as u32;
469    (value / alignment) * alignment
470}
471
472/// Round down the the given number to align with the wordsize of the flash.
473/// If the number is already aligned, it is not changed.
474const fn round_down_to_alignment_usize<S: NorFlash>(value: usize) -> usize {
475    round_down_to_alignment::<S>(value as u32) as usize
476}
477
478/// Extension trait to get the overall word size, which is the largest of the write and read word size
479trait NorFlashExt {
480    /// The largest of the write and read word size
481    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        // Page setup:
534        // 0: closed
535        // 1: closed
536        // 2: partial-open
537        // 3: open
538
539        let mut flash = MockFlash::default();
540        // Page 0 markers
541        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        // Page 1 markers
548        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        // Page 2 markers
555        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}