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
446impl<S> core::error::Error for Error<S> where S: core::fmt::Display + core::fmt::Debug {}
447
448/// Round up the the given number to align with the wordsize of the flash.
449/// If the number is already aligned, it is not changed.
450const fn round_up_to_alignment<S: NorFlash>(value: u32) -> u32 {
451    let alignment = S::WORD_SIZE as u32;
452    match value % alignment {
453        0 => value,
454        r => value + (alignment - r),
455    }
456}
457
458/// Round up the the given number to align with the wordsize of the flash.
459/// If the number is already aligned, it is not changed.
460const fn round_up_to_alignment_usize<S: NorFlash>(value: usize) -> usize {
461    round_up_to_alignment::<S>(value as u32) as usize
462}
463
464/// Round down the the given number to align with the wordsize of the flash.
465/// If the number is already aligned, it is not changed.
466const fn round_down_to_alignment<S: NorFlash>(value: u32) -> u32 {
467    let alignment = S::WORD_SIZE as u32;
468    (value / alignment) * alignment
469}
470
471/// Round down the the given number to align with the wordsize of the flash.
472/// If the number is already aligned, it is not changed.
473const fn round_down_to_alignment_usize<S: NorFlash>(value: usize) -> usize {
474    round_down_to_alignment::<S>(value as u32) as usize
475}
476
477/// Extension trait to get the overall word size, which is the largest of the write and read word size
478trait NorFlashExt {
479    /// The largest of the write and read word size
480    const WORD_SIZE: usize;
481}
482
483impl<S: NorFlash> NorFlashExt for S {
484    const WORD_SIZE: usize = {
485        assert!(
486            Self::WRITE_SIZE % Self::READ_SIZE == 0,
487            "Only flash with read and write sizes that are multiple of each other are supported"
488        );
489
490        if Self::WRITE_SIZE > Self::READ_SIZE {
491            Self::WRITE_SIZE
492        } else {
493            Self::READ_SIZE
494        }
495    };
496}
497
498macro_rules! run_with_auto_repair {
499    (function = $function:expr, repair = $repair_function:expr) => {
500        match $function {
501            Err(Error::Corrupted {
502                #[cfg(feature = "_test")]
503                    backtrace: _backtrace,
504                ..
505            }) => {
506                #[cfg(all(feature = "_test", fuzzing_repro))]
507                eprintln!(
508                    "### Encountered curruption! Repairing now. Originated from:\n{_backtrace:#}"
509                );
510                $repair_function;
511                $function
512            }
513            val => val,
514        }
515    };
516}
517
518pub(crate) use run_with_auto_repair;
519
520#[cfg(test)]
521mod tests {
522    use super::*;
523    use futures_test::test;
524
525    type MockFlash = mock_flash::MockFlashBase<4, 4, 64>;
526
527    async fn write_aligned(
528        flash: &mut MockFlash,
529        offset: u32,
530        bytes: &[u8],
531    ) -> Result<(), mock_flash::MockFlashError> {
532        let mut buf = AlignedBuf([0; 256]);
533        buf[..bytes.len()].copy_from_slice(bytes);
534        flash.write(offset, &buf[..bytes.len()]).await
535    }
536
537    #[test]
538    async fn test_find_pages() {
539        // Page setup:
540        // 0: closed
541        // 1: closed
542        // 2: partial-open
543        // 3: open
544
545        let mut flash = MockFlash::default();
546        // Page 0 markers
547        write_aligned(&mut flash, 0x000, &[MARKER, 0, 0, 0])
548            .await
549            .unwrap();
550        write_aligned(&mut flash, 0x100 - 4, &[0, 0, 0, MARKER])
551            .await
552            .unwrap();
553        // Page 1 markers
554        write_aligned(&mut flash, 0x100, &[MARKER, 0, 0, 0])
555            .await
556            .unwrap();
557        write_aligned(&mut flash, 0x200 - 4, &[0, 0, 0, MARKER])
558            .await
559            .unwrap();
560        // Page 2 markers
561        write_aligned(&mut flash, 0x200, &[MARKER, 0, 0, 0])
562            .await
563            .unwrap();
564
565        assert_eq!(
566            find_first_page(
567                &mut flash,
568                0x000..0x400,
569                &mut cache::NoCache::new(),
570                0,
571                PageState::Open
572            )
573            .await
574            .unwrap(),
575            Some(3)
576        );
577        assert_eq!(
578            find_first_page(
579                &mut flash,
580                0x000..0x400,
581                &mut cache::NoCache::new(),
582                0,
583                PageState::PartialOpen
584            )
585            .await
586            .unwrap(),
587            Some(2)
588        );
589        assert_eq!(
590            find_first_page(
591                &mut flash,
592                0x000..0x400,
593                &mut cache::NoCache::new(),
594                1,
595                PageState::PartialOpen
596            )
597            .await
598            .unwrap(),
599            Some(2)
600        );
601        assert_eq!(
602            find_first_page(
603                &mut flash,
604                0x000..0x400,
605                &mut cache::NoCache::new(),
606                2,
607                PageState::PartialOpen
608            )
609            .await
610            .unwrap(),
611            Some(2)
612        );
613        assert_eq!(
614            find_first_page(
615                &mut flash,
616                0x000..0x400,
617                &mut cache::NoCache::new(),
618                3,
619                PageState::Open
620            )
621            .await
622            .unwrap(),
623            Some(3)
624        );
625        assert_eq!(
626            find_first_page(
627                &mut flash,
628                0x000..0x200,
629                &mut cache::NoCache::new(),
630                0,
631                PageState::PartialOpen
632            )
633            .await
634            .unwrap(),
635            None
636        );
637
638        assert_eq!(
639            find_first_page(
640                &mut flash,
641                0x000..0x400,
642                &mut cache::NoCache::new(),
643                0,
644                PageState::Closed
645            )
646            .await
647            .unwrap(),
648            Some(0)
649        );
650        assert_eq!(
651            find_first_page(
652                &mut flash,
653                0x000..0x400,
654                &mut cache::NoCache::new(),
655                1,
656                PageState::Closed
657            )
658            .await
659            .unwrap(),
660            Some(1)
661        );
662        assert_eq!(
663            find_first_page(
664                &mut flash,
665                0x000..0x400,
666                &mut cache::NoCache::new(),
667                2,
668                PageState::Closed
669            )
670            .await
671            .unwrap(),
672            Some(0)
673        );
674        assert_eq!(
675            find_first_page(
676                &mut flash,
677                0x000..0x400,
678                &mut cache::NoCache::new(),
679                3,
680                PageState::Closed
681            )
682            .await
683            .unwrap(),
684            Some(0)
685        );
686        assert_eq!(
687            find_first_page(
688                &mut flash,
689                0x200..0x400,
690                &mut cache::NoCache::new(),
691                0,
692                PageState::Closed
693            )
694            .await
695            .unwrap(),
696            None
697        );
698    }
699}