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-09")]
24mod heapless_09_impl;
25#[cfg(feature = "heapless")]
26mod heapless_impl;
27mod item;
28pub mod map;
29pub mod queue;
30
31#[cfg(any(test, doctest, feature = "_test"))]
32/// An in-memory flash type that can be used for mocking.
33pub mod mock_flash;
34
35/// The biggest wordsize we support.
36///
37/// Stm32 internal flash has 256-bit words, so 32 bytes.
38/// Many flashes have 4-byte or 1-byte words.
39const MAX_WORD_SIZE: usize = 32;
40
41/// Resets the flash in the entire given flash range.
42///
43/// This is just a thin helper function as it just calls the flash's erase function.
44pub async fn erase_all<S: NorFlash>(
45    flash: &mut S,
46    flash_range: Range<u32>,
47) -> Result<(), Error<S::Error>> {
48    flash
49        .erase(flash_range.start, flash_range.end)
50        .await
51        .map_err(|e| Error::Storage {
52            value: e,
53            #[cfg(feature = "_test")]
54            backtrace: std::backtrace::Backtrace::capture(),
55        })
56}
57
58/// Get the minimal overhead size per stored item for the given flash type.
59///
60/// The associated data of each item is additionally padded to a full flash word size, but that's not part of this number.  
61/// This means the full item length is `returned number + (data length).next_multiple_of(S::WORD_SIZE)`.
62pub const fn item_overhead_size<S: NorFlash>() -> u32 {
63    item::ItemHeader::data_address::<S>(0)
64}
65
66// Type representing buffer aligned to 4 byte boundary.
67#[repr(align(4))]
68pub(crate) struct AlignedBuf<const SIZE: usize>(pub(crate) [u8; SIZE]);
69impl<const SIZE: usize> Deref for AlignedBuf<SIZE> {
70    type Target = [u8];
71    fn deref(&self) -> &Self::Target {
72        &self.0
73    }
74}
75
76impl<const SIZE: usize> DerefMut for AlignedBuf<SIZE> {
77    fn deref_mut(&mut self) -> &mut Self::Target {
78        &mut self.0
79    }
80}
81
82async fn try_general_repair<S: NorFlash>(
83    flash: &mut S,
84    flash_range: Range<u32>,
85    cache: &mut impl PrivateCacheImpl,
86) -> Result<(), Error<S::Error>> {
87    // Loop through the pages and get their state. If one returns the corrupted error,
88    // the page is likely half-erased. Fix for that is to re-erase again to hopefully finish the job.
89    for page_index in get_pages::<S>(flash_range.clone(), 0) {
90        if matches!(
91            get_page_state(flash, flash_range.clone(), cache, page_index).await,
92            Err(Error::Corrupted { .. })
93        ) {
94            open_page(flash, flash_range.clone(), cache, page_index).await?;
95        }
96    }
97
98    #[cfg(fuzzing_repro)]
99    eprintln!("General repair has been called");
100
101    Ok(())
102}
103
104/// Find the first page that is in the given page state.
105///
106/// The search starts at starting_page_index (and wraps around back to 0 if required)
107async fn find_first_page<S: NorFlash>(
108    flash: &mut S,
109    flash_range: Range<u32>,
110    cache: &mut impl PrivateCacheImpl,
111    starting_page_index: usize,
112    page_state: PageState,
113) -> Result<Option<usize>, Error<S::Error>> {
114    for page_index in get_pages::<S>(flash_range.clone(), starting_page_index) {
115        if page_state == get_page_state::<S>(flash, flash_range.clone(), cache, page_index).await? {
116            return Ok(Some(page_index));
117        }
118    }
119
120    Ok(None)
121}
122
123/// Get all pages in the flash range from the given start to end (that might wrap back to 0)
124fn get_pages<S: NorFlash>(
125    flash_range: Range<u32>,
126    starting_page_index: usize,
127) -> impl DoubleEndedIterator<Item = usize> {
128    let page_count = flash_range.len() / S::ERASE_SIZE;
129    flash_range
130        .step_by(S::ERASE_SIZE)
131        .enumerate()
132        .map(move |(index, _)| (index + starting_page_index) % page_count)
133}
134
135/// Get the next page index (wrapping around to 0 if required)
136fn next_page<S: NorFlash>(flash_range: Range<u32>, page_index: usize) -> usize {
137    let page_count = flash_range.len() / S::ERASE_SIZE;
138    (page_index + 1) % page_count
139}
140
141/// Get the previous page index (wrapping around to the biggest page if required)
142fn previous_page<S: NorFlash>(flash_range: Range<u32>, page_index: usize) -> usize {
143    let page_count = flash_range.len() / S::ERASE_SIZE;
144
145    match page_index.checked_sub(1) {
146        Some(new_page_index) => new_page_index,
147        None => page_count - 1,
148    }
149}
150
151/// Calculate the first address of the given page
152const fn calculate_page_address<S: NorFlash>(flash_range: Range<u32>, page_index: usize) -> u32 {
153    flash_range.start + (S::ERASE_SIZE * page_index) as u32
154}
155/// Calculate the last address (exclusive) of the given page
156const fn calculate_page_end_address<S: NorFlash>(
157    flash_range: Range<u32>,
158    page_index: usize,
159) -> u32 {
160    flash_range.start + (S::ERASE_SIZE * (page_index + 1)) as u32
161}
162/// Get the page index from any address located inside that page
163#[allow(unused)]
164const fn calculate_page_index<S: NorFlash>(flash_range: Range<u32>, address: u32) -> usize {
165    (address - flash_range.start) as usize / S::ERASE_SIZE
166}
167
168const fn calculate_page_size<S: NorFlash>() -> usize {
169    // Page minus the two page status words
170    S::ERASE_SIZE - S::WORD_SIZE * 2
171}
172
173/// The marker being used for page states
174const MARKER: u8 = 0;
175
176/// Get the state of the page located at the given index
177async fn get_page_state<S: NorFlash>(
178    flash: &mut S,
179    flash_range: Range<u32>,
180    cache: &mut impl PrivateCacheImpl,
181    page_index: usize,
182) -> Result<PageState, Error<S::Error>> {
183    if let Some(cached_page_state) = cache.get_page_state(page_index) {
184        return Ok(cached_page_state);
185    }
186
187    let page_address = calculate_page_address::<S>(flash_range, page_index);
188    /// We only care about the data in the first byte to aid shutdown/cancellation.
189    /// But we also don't want it to be too too definitive because we want to survive the occasional bitflip.
190    /// So only half of the byte needs to be zero.
191    const HALF_MARKER_BITS: u32 = 4;
192
193    let mut buffer = [0; MAX_WORD_SIZE];
194    flash
195        .read(page_address, &mut buffer[..S::READ_SIZE])
196        .await
197        .map_err(|e| Error::Storage {
198            value: e,
199            #[cfg(feature = "_test")]
200            backtrace: std::backtrace::Backtrace::capture(),
201        })?;
202    let start_marked = buffer[..S::READ_SIZE]
203        .iter()
204        .map(|marker_byte| marker_byte.count_zeros())
205        .sum::<u32>()
206        >= HALF_MARKER_BITS;
207
208    flash
209        .read(
210            page_address + (S::ERASE_SIZE - S::READ_SIZE) as u32,
211            &mut buffer[..S::READ_SIZE],
212        )
213        .await
214        .map_err(|e| Error::Storage {
215            value: e,
216            #[cfg(feature = "_test")]
217            backtrace: std::backtrace::Backtrace::capture(),
218        })?;
219    let end_marked = buffer[..S::READ_SIZE]
220        .iter()
221        .map(|marker_byte| marker_byte.count_zeros())
222        .sum::<u32>()
223        >= HALF_MARKER_BITS;
224
225    let discovered_state = match (start_marked, end_marked) {
226        (true, true) => PageState::Closed,
227        (true, false) => PageState::PartialOpen,
228        // Probably an interrupted erase
229        (false, true) => {
230            return Err(Error::Corrupted {
231                #[cfg(feature = "_test")]
232                backtrace: std::backtrace::Backtrace::capture(),
233            });
234        }
235        (false, false) => PageState::Open,
236    };
237
238    // Not dirty because nothing changed and nothing can be inconsistent
239    cache.notice_page_state(page_index, discovered_state, false);
240
241    Ok(discovered_state)
242}
243
244/// Erase the page to open it again
245async fn open_page<S: NorFlash>(
246    flash: &mut S,
247    flash_range: Range<u32>,
248    cache: &mut impl PrivateCacheImpl,
249    page_index: usize,
250) -> Result<(), Error<S::Error>> {
251    cache.notice_page_state(page_index, PageState::Open, true);
252
253    flash
254        .erase(
255            calculate_page_address::<S>(flash_range.clone(), page_index),
256            calculate_page_end_address::<S>(flash_range.clone(), page_index),
257        )
258        .await
259        .map_err(|e| Error::Storage {
260            value: e,
261            #[cfg(feature = "_test")]
262            backtrace: std::backtrace::Backtrace::capture(),
263        })?;
264
265    Ok(())
266}
267
268/// Fully closes a page by writing both the start and end marker
269async fn close_page<S: NorFlash>(
270    flash: &mut S,
271    flash_range: Range<u32>,
272    cache: &mut impl PrivateCacheImpl,
273    page_index: usize,
274) -> Result<(), Error<S::Error>> {
275    let current_state =
276        partial_close_page::<S>(flash, flash_range.clone(), cache, page_index).await?;
277
278    if current_state != PageState::PartialOpen {
279        return Ok(());
280    }
281
282    cache.notice_page_state(page_index, PageState::Closed, true);
283
284    let buffer = AlignedBuf([MARKER; MAX_WORD_SIZE]);
285    // Close the end marker
286    flash
287        .write(
288            calculate_page_end_address::<S>(flash_range, page_index) - S::WORD_SIZE as u32,
289            &buffer[..S::WORD_SIZE],
290        )
291        .await
292        .map_err(|e| Error::Storage {
293            value: e,
294            #[cfg(feature = "_test")]
295            backtrace: std::backtrace::Backtrace::capture(),
296        })?;
297
298    Ok(())
299}
300
301/// Partially close a page by writing the start marker
302async fn partial_close_page<S: NorFlash>(
303    flash: &mut S,
304    flash_range: Range<u32>,
305    cache: &mut impl PrivateCacheImpl,
306    page_index: usize,
307) -> Result<PageState, Error<S::Error>> {
308    let current_state = get_page_state::<S>(flash, flash_range.clone(), cache, page_index).await?;
309
310    if current_state != PageState::Open {
311        return Ok(current_state);
312    }
313
314    let new_state = match current_state {
315        PageState::Closed => PageState::Closed,
316        PageState::PartialOpen => PageState::PartialOpen,
317        PageState::Open => PageState::PartialOpen,
318    };
319
320    cache.notice_page_state(page_index, new_state, true);
321
322    let buffer = AlignedBuf([MARKER; MAX_WORD_SIZE]);
323    // Close the start marker
324    flash
325        .write(
326            calculate_page_address::<S>(flash_range, page_index),
327            &buffer[..S::WORD_SIZE],
328        )
329        .await
330        .map_err(|e| Error::Storage {
331            value: e,
332            #[cfg(feature = "_test")]
333            backtrace: std::backtrace::Backtrace::capture(),
334        })?;
335
336    Ok(new_state)
337}
338
339/// The state of a page
340#[derive(Debug, Clone, Copy, PartialEq, Eq)]
341#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
342enum PageState {
343    /// This page was fully written and has now been sealed
344    Closed,
345    /// This page has been written to, but may have some space left over
346    PartialOpen,
347    /// This page is fully erased
348    Open,
349}
350
351#[allow(dead_code)]
352impl PageState {
353    /// Returns `true` if the page state is [`Closed`].
354    ///
355    /// [`Closed`]: PageState::Closed
356    #[must_use]
357    fn is_closed(&self) -> bool {
358        matches!(self, Self::Closed)
359    }
360
361    /// Returns `true` if the page state is [`PartialOpen`].
362    ///
363    /// [`PartialOpen`]: PageState::PartialOpen
364    #[must_use]
365    fn is_partial_open(&self) -> bool {
366        matches!(self, Self::PartialOpen)
367    }
368
369    /// Returns `true` if the page state is [`Open`].
370    ///
371    /// [`Open`]: PageState::Open
372    #[must_use]
373    fn is_open(&self) -> bool {
374        matches!(self, Self::Open)
375    }
376}
377
378/// The main error type
379#[non_exhaustive]
380#[derive(Debug)]
381#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
382pub enum Error<S> {
383    /// An error in the storage (flash)
384    Storage {
385        /// The error value
386        value: S,
387        #[cfg(feature = "_test")]
388        /// Backtrace made at the construction of the error
389        backtrace: std::backtrace::Backtrace,
390    },
391    /// The item cannot be stored anymore because the storage is full.
392    FullStorage,
393    /// It's been detected that the memory is likely corrupted.
394    /// You may want to erase the memory to recover.
395    Corrupted {
396        #[cfg(feature = "_test")]
397        /// Backtrace made at the construction of the error
398        backtrace: std::backtrace::Backtrace,
399    },
400    /// A provided buffer was to big to be used
401    BufferTooBig,
402    /// A provided buffer was to small to be used (usize is size needed)
403    BufferTooSmall(usize),
404    /// A serialization error (from the key or value)
405    SerializationError(SerializationError),
406    /// The item does not fit in flash, ever.
407    /// This is different from [Error::FullStorage] because this item is too big to fit even in empty flash.
408    ///
409    /// See the readme for more info about the constraints on item sizes.
410    ItemTooBig,
411}
412
413impl<S> From<SerializationError> for Error<S> {
414    fn from(v: SerializationError) -> Self {
415        Self::SerializationError(v)
416    }
417}
418
419impl<S: PartialEq> PartialEq for Error<S> {
420    fn eq(&self, other: &Self) -> bool {
421        match (self, other) {
422            (Self::Storage { value: l_value, .. }, Self::Storage { value: r_value, .. }) => {
423                l_value == r_value
424            }
425            (Self::BufferTooSmall(l0), Self::BufferTooSmall(r0)) => l0 == r0,
426            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
427        }
428    }
429}
430
431impl<S> core::fmt::Display for Error<S>
432where
433    S: core::fmt::Display,
434{
435    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
436        match self {
437            Error::Storage { value, .. } => write!(f, "Storage error: {value}"),
438            Error::FullStorage => write!(f, "Storage is full"),
439            #[cfg(not(feature = "_test"))]
440            Error::Corrupted { .. } => write!(f, "Storage is corrupted"),
441            #[cfg(feature = "_test")]
442            Error::Corrupted { backtrace } => write!(f, "Storage is corrupted\n{backtrace}"),
443            Error::BufferTooBig => write!(f, "A provided buffer was to big to be used"),
444            Error::BufferTooSmall(needed) => write!(
445                f,
446                "A provided buffer was to small to be used. Needed was {needed}"
447            ),
448            Error::SerializationError(value) => write!(f, "Map value error: {value}"),
449            Error::ItemTooBig => write!(f, "The item is too big to fit in the flash"),
450        }
451    }
452}
453
454impl<S> core::error::Error for Error<S> where S: core::fmt::Display + core::fmt::Debug {}
455
456/// Round up the the given number to align with the wordsize of the flash.
457/// If the number is already aligned, it is not changed.
458const fn round_up_to_alignment<S: NorFlash>(value: u32) -> u32 {
459    let alignment = S::WORD_SIZE as u32;
460    match value % alignment {
461        0 => value,
462        r => value + (alignment - r),
463    }
464}
465
466/// Round up the the given number to align with the wordsize of the flash.
467/// If the number is already aligned, it is not changed.
468const fn round_up_to_alignment_usize<S: NorFlash>(value: usize) -> usize {
469    round_up_to_alignment::<S>(value as u32) as usize
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<S: NorFlash>(value: u32) -> u32 {
475    let alignment = S::WORD_SIZE as u32;
476    (value / alignment) * alignment
477}
478
479/// Round down the the given number to align with the wordsize of the flash.
480/// If the number is already aligned, it is not changed.
481const fn round_down_to_alignment_usize<S: NorFlash>(value: usize) -> usize {
482    round_down_to_alignment::<S>(value as u32) as usize
483}
484
485/// Extension trait to get the overall word size, which is the largest of the write and read word size
486trait NorFlashExt {
487    /// The largest of the write and read word size
488    const WORD_SIZE: usize;
489}
490
491impl<S: NorFlash> NorFlashExt for S {
492    const WORD_SIZE: usize = {
493        assert_read_write_sizes(Self::WRITE_SIZE, Self::READ_SIZE);
494
495        if Self::WRITE_SIZE > Self::READ_SIZE {
496            Self::WRITE_SIZE
497        } else {
498            Self::READ_SIZE
499        }
500    };
501}
502
503#[track_caller]
504const fn assert_read_write_sizes(write_size: usize, read_size: usize) {
505    assert!(
506        write_size.is_multiple_of(read_size) || read_size.is_multiple_of(write_size),
507        "Only flash with read and write sizes that are multiple of each other are supported"
508    );
509}
510
511macro_rules! run_with_auto_repair {
512    (function = $function:expr, repair = $repair_function:expr) => {
513        match $function {
514            Err(Error::Corrupted {
515                #[cfg(feature = "_test")]
516                    backtrace: _backtrace,
517                ..
518            }) => {
519                #[cfg(all(feature = "_test", fuzzing_repro))]
520                eprintln!(
521                    "### Encountered curruption! Repairing now. Originated from:\n{_backtrace:#}"
522                );
523                $repair_function;
524                $function
525            }
526            val => val,
527        }
528    };
529}
530
531pub(crate) use run_with_auto_repair;
532
533#[cfg(test)]
534mod tests {
535    use super::*;
536    use futures_test::test;
537
538    type MockFlash = mock_flash::MockFlashBase<4, 4, 64>;
539
540    async fn write_aligned(
541        flash: &mut MockFlash,
542        offset: u32,
543        bytes: &[u8],
544    ) -> Result<(), mock_flash::MockFlashError> {
545        let mut buf = AlignedBuf([0; 256]);
546        buf[..bytes.len()].copy_from_slice(bytes);
547        flash.write(offset, &buf[..bytes.len()]).await
548    }
549
550    #[test]
551    async fn test_find_pages() {
552        // Page setup:
553        // 0: closed
554        // 1: closed
555        // 2: partial-open
556        // 3: open
557
558        let mut flash = MockFlash::default();
559        // Page 0 markers
560        write_aligned(&mut flash, 0x000, &[MARKER, 0, 0, 0])
561            .await
562            .unwrap();
563        write_aligned(&mut flash, 0x100 - 4, &[0, 0, 0, MARKER])
564            .await
565            .unwrap();
566        // Page 1 markers
567        write_aligned(&mut flash, 0x100, &[MARKER, 0, 0, 0])
568            .await
569            .unwrap();
570        write_aligned(&mut flash, 0x200 - 4, &[0, 0, 0, MARKER])
571            .await
572            .unwrap();
573        // Page 2 markers
574        write_aligned(&mut flash, 0x200, &[MARKER, 0, 0, 0])
575            .await
576            .unwrap();
577
578        assert_eq!(
579            find_first_page(
580                &mut flash,
581                0x000..0x400,
582                &mut cache::NoCache::new(),
583                0,
584                PageState::Open
585            )
586            .await
587            .unwrap(),
588            Some(3)
589        );
590        assert_eq!(
591            find_first_page(
592                &mut flash,
593                0x000..0x400,
594                &mut cache::NoCache::new(),
595                0,
596                PageState::PartialOpen
597            )
598            .await
599            .unwrap(),
600            Some(2)
601        );
602        assert_eq!(
603            find_first_page(
604                &mut flash,
605                0x000..0x400,
606                &mut cache::NoCache::new(),
607                1,
608                PageState::PartialOpen
609            )
610            .await
611            .unwrap(),
612            Some(2)
613        );
614        assert_eq!(
615            find_first_page(
616                &mut flash,
617                0x000..0x400,
618                &mut cache::NoCache::new(),
619                2,
620                PageState::PartialOpen
621            )
622            .await
623            .unwrap(),
624            Some(2)
625        );
626        assert_eq!(
627            find_first_page(
628                &mut flash,
629                0x000..0x400,
630                &mut cache::NoCache::new(),
631                3,
632                PageState::Open
633            )
634            .await
635            .unwrap(),
636            Some(3)
637        );
638        assert_eq!(
639            find_first_page(
640                &mut flash,
641                0x000..0x200,
642                &mut cache::NoCache::new(),
643                0,
644                PageState::PartialOpen
645            )
646            .await
647            .unwrap(),
648            None
649        );
650
651        assert_eq!(
652            find_first_page(
653                &mut flash,
654                0x000..0x400,
655                &mut cache::NoCache::new(),
656                0,
657                PageState::Closed
658            )
659            .await
660            .unwrap(),
661            Some(0)
662        );
663        assert_eq!(
664            find_first_page(
665                &mut flash,
666                0x000..0x400,
667                &mut cache::NoCache::new(),
668                1,
669                PageState::Closed
670            )
671            .await
672            .unwrap(),
673            Some(1)
674        );
675        assert_eq!(
676            find_first_page(
677                &mut flash,
678                0x000..0x400,
679                &mut cache::NoCache::new(),
680                2,
681                PageState::Closed
682            )
683            .await
684            .unwrap(),
685            Some(0)
686        );
687        assert_eq!(
688            find_first_page(
689                &mut flash,
690                0x000..0x400,
691                &mut cache::NoCache::new(),
692                3,
693                PageState::Closed
694            )
695            .await
696            .unwrap(),
697            Some(0)
698        );
699        assert_eq!(
700            find_first_page(
701                &mut flash,
702                0x200..0x400,
703                &mut cache::NoCache::new(),
704                0,
705                PageState::Closed
706            )
707            .await
708            .unwrap(),
709            None
710        );
711    }
712
713    #[test]
714    async fn read_write_sizes() {
715        assert_read_write_sizes(1, 1);
716        assert_read_write_sizes(1, 4);
717        assert_read_write_sizes(4, 4);
718        assert_read_write_sizes(4, 1);
719    }
720}