embedded_savegame/
storage.rs

1//! Flash storage abstraction and savegame management
2//!
3//! This module provides the core storage functionality, including:
4//! - The [`Flash`] trait for hardware abstraction
5//! - The [`Storage`] type for managing savegames
6//! - Methods for reading, writing, and scanning savegames
7
8use crate::{
9    Slot,
10    chksum::{self, Chksum},
11};
12use core::fmt;
13
14/// Trait for flash memory operations
15///
16/// Implement this trait for your flash hardware to use with [`Storage`].
17/// The trait is generic over the error type to support different hardware backends.
18pub trait Flash {
19    /// The error type for flash operations
20    type Error: fmt::Debug;
21
22    /// Read data from flash memory at the specified byte address
23    ///
24    /// # Arguments
25    ///
26    /// * `addr` - The byte address to read from
27    /// * `buf` - The buffer to read data into
28    fn read(&mut self, addr: u32, buf: &mut [u8]) -> Result<(), Self::Error>;
29
30    /// Write data to flash memory at the specified address
31    ///
32    /// Note: The data parameter is mutable because some flash drivers (e.g., w25q)
33    /// require mutable access during write operations.
34    fn write(&mut self, addr: u32, data: &mut [u8]) -> Result<(), Self::Error>;
35
36    /// Erase a flash sector or replace first byte to invalidate a slot
37    ///
38    /// For EEPROM, this typically sets the first byte to 0xFF.
39    /// For NOR flash, this erases an entire sector.
40    fn erase(&mut self, addr: u32) -> Result<(), Self::Error>;
41
42    /// Bulk erase multiple slots/sectors
43    ///
44    /// Some flash chips have optimized bulk erase operations.
45    /// The default implementation erases sectors one by one.
46    ///
47    /// # Arguments
48    ///
49    /// * `count` - The number of slots to erase
50    fn erase_all(&mut self, count: usize) -> Result<(), Self::Error> {
51        for idx in 0..count {
52            self.erase(idx as u32)?;
53        }
54        Ok(())
55    }
56}
57
58/// Savegame storage manager
59///
60/// Manages reading and writing savegames to flash memory with power-fail safety
61/// and wear leveling. The storage area is divided into fixed-size slots, and
62/// savegames are written sequentially across slots with automatic wrap-around.
63///
64/// # Type Parameters
65///
66/// * `F` - The flash hardware type implementing [`Flash`]
67/// * `SLOT_SIZE` - The size of each slot in bytes: this must match your flash's
68///   underlying sector/page size
69/// * `SLOT_COUNT` - The total number of slots available
70///
71/// # Power-fail Safety
72///
73/// Writes are atomic at the slot level. The slot header is written last, so a
74/// power failure during write leaves the previous savegame intact. The scanner
75/// follows the checksum chain to find the most recent complete savegame.
76///
77/// # Wear Leveling
78///
79/// Savegames are written sequentially with wrap-around, distributing writes
80/// evenly across all slots to maximize flash memory lifespan.
81#[derive(Debug)]
82pub struct Storage<F: Flash, const SLOT_SIZE: usize, const SLOT_COUNT: usize> {
83    flash: F,
84    prev: Chksum,
85    idx: usize,
86}
87
88impl<F: Flash, const SLOT_SIZE: usize, const SLOT_COUNT: usize> Storage<F, SLOT_SIZE, SLOT_COUNT> {
89    /// The total size of the storage area in bytes
90    ///
91    /// This can't be fully used for data storage, as some bytes are used
92    /// for slot metadata and headers.
93    pub const SPACE: u32 = SLOT_SIZE as u32 * SLOT_COUNT as u32;
94
95    /// Create a new storage manager
96    ///
97    /// This is a cheap operation and does not initialize or scan the flash
98    /// memory.
99    pub const fn new(flash: F) -> Self {
100        Self {
101            flash,
102            prev: Chksum::zero(),
103            idx: 0,
104        }
105    }
106
107    /// Calculate the flash memory address of a slot by its index
108    const fn addr(&self, idx: usize) -> u32 {
109        ((idx % SLOT_COUNT) * SLOT_SIZE) as u32
110    }
111
112    /// Probe a single slot for a valid savegame header
113    fn scan_slot(&mut self, idx: usize) -> Result<Option<Slot>, F::Error> {
114        let mut buf = [0u8; Slot::HEADER_SIZE];
115        let (head, tail) = arrayref::mut_array_refs![&mut buf, 1, Slot::HEADER_SIZE - 1];
116
117        // Read first byte for sanity check to allow early skip
118        let addr = self.addr(idx);
119        self.flash.read(addr, head)?;
120
121        if head[0] & chksum::BYTE_MASK != 0 {
122            return Ok(None);
123        }
124
125        // Read the rest of the header
126        let addr = addr.saturating_add(1);
127        self.flash.read(addr, tail)?;
128
129        // Parse and validate slot
130        let slot = Slot::from_bytes(idx, buf);
131        let slot = slot.is_valid().then_some(slot);
132        Ok(slot)
133    }
134
135    /// Scan all slots for the most recent valid savegame
136    ///
137    /// If found, updates internal state to point to the next free slot. If no
138    /// valid savegame is found, internal state is unchanged and `Ok(None)` is
139    /// returned.
140    pub fn scan(&mut self) -> Result<Option<Slot>, F::Error> {
141        let mut current: Option<Slot> = None;
142
143        for idx in 0..SLOT_COUNT {
144            let Some(slot) = self.scan_slot(idx)? else {
145                continue;
146            };
147
148            if let Some(existing) = &current {
149                if slot.is_update_to(existing) {
150                    current = Some(slot);
151                }
152            } else {
153                current = Some(slot);
154            }
155        }
156
157        if let Some(current) = &current {
158            self.idx = current.next_slot::<SLOT_SIZE, SLOT_COUNT>();
159            self.prev = current.chksum;
160        }
161
162        Ok(current)
163    }
164
165    /// Mark a slot as unused (by partially or fully erasing it)
166    ///
167    /// This may not securely erase all data (depending on the flash chip), but
168    /// prevents the slot from being detected as a valid savegame.
169    pub fn erase(&mut self, idx: usize) -> Result<(), F::Error> {
170        self.flash.erase(self.addr(idx))?;
171        Ok(())
172    }
173
174    /// Mark all slots as unused
175    ///
176    /// This may not securely erase data (depending on the flash chip), but
177    /// prevents them from being detected as valid savegames.
178    ///
179    /// On some flash chips, this may be optimized to a bulk erase operation.
180    pub fn erase_all(&mut self) -> Result<(), F::Error> {
181        self.idx = 0;
182        self.prev = Chksum::zero();
183        self.flash.erase_all(SLOT_COUNT)
184    }
185
186    /// Read a savegame from a specific slot index
187    ///
188    /// The slot index must point to the first slot of the savegame. This method reads
189    /// the header to determine the savegame length. If the buffer is not large enough
190    /// to hold the entire savegame, `Ok(None)` is returned. The savegame may span
191    /// multiple slots.
192    pub fn read<'a>(
193        &mut self,
194        mut idx: usize,
195        buf: &'a mut [u8],
196    ) -> Result<Option<&'a mut [u8]>, F::Error> {
197        let mut addr = self.addr(idx);
198        let mut slot = [0u8; Slot::HEADER_SIZE];
199        self.flash.read(addr, &mut slot)?;
200        addr = addr.saturating_add(Slot::HEADER_SIZE as u32);
201        let slot = Slot::from_bytes(idx, slot);
202
203        let Some(data) = buf.get_mut(..slot.len as usize) else {
204            return Ok(None);
205        };
206        let mut buf = &mut *data;
207        let mut remaining_space = SLOT_SIZE - Slot::HEADER_SIZE;
208        while !buf.is_empty() {
209            let read_size = remaining_space.min(buf.len());
210            let (to_read, remaining) = buf.split_at_mut(read_size);
211            self.flash.read(addr, to_read)?;
212            buf = remaining;
213
214            idx = idx.saturating_add(1) % SLOT_COUNT;
215            addr = self.addr(idx).saturating_add(1);
216            remaining_space = SLOT_SIZE - 1;
217        }
218
219        Ok(Some(data))
220    }
221
222    /// Read a static-sized savegame directly from a single slot
223    ///
224    /// This is a more lightweight read operation for fixed-size data that fits
225    /// within a single slot (excluding the header). The size must not exceed
226    /// `SLOT_SIZE - Slot::HEADER_SIZE`. The embedded length field is ignored.
227    pub fn read_static<const SIZE: usize>(
228        &mut self,
229        idx: usize,
230        buf: &mut [u8; SIZE],
231    ) -> Result<(), F::Error> {
232        // Sanity check
233        const {
234            let space_available = SLOT_SIZE
235                .checked_sub(Slot::HEADER_SIZE)
236                .expect("Invalid SLOT_SIZE, Slot::HEADER_SIZE doesn't fit");
237            assert!(SIZE <= space_available);
238        }
239
240        // Calculate address behind slot header
241        let addr = self.addr(idx).saturating_add(Slot::HEADER_SIZE as u32);
242        // Read data directly into the buffer in one go
243        self.flash.read(addr, buf)?;
244
245        Ok(())
246    }
247
248    /// Write a savegame starting at a specific slot index
249    ///
250    /// If the data doesn't fit in a single slot, this method automatically continues
251    /// to subsequent slots, erasing them as needed. Returns the next free slot index
252    /// and the checksum of the savegame that was just written.
253    pub fn write(
254        &mut self,
255        mut idx: usize,
256        prev: Chksum,
257        mut data: &mut [u8],
258    ) -> Result<(usize, Chksum), F::Error> {
259        let slot = Slot::create(idx, prev, data);
260        let slot_addr = self.addr(idx);
261        self.flash.erase(slot_addr)?;
262
263        let mut addr = slot_addr.saturating_add(Slot::HEADER_SIZE as u32);
264        let mut remaining_space = SLOT_SIZE - Slot::HEADER_SIZE;
265
266        loop {
267            let write_size = remaining_space.min(data.len());
268            let (to_write, remaining) = data.split_at_mut(write_size);
269            self.flash.write(addr, to_write)?;
270            data = remaining;
271            idx = idx.saturating_add(1) % SLOT_COUNT;
272
273            // erase first byte of next slot, but only if more data remains
274            if data.is_empty() {
275                break;
276            }
277
278            addr = self.addr(idx);
279            self.flash.erase(addr)?;
280
281            addr = addr.saturating_add(1);
282            remaining_space = SLOT_SIZE - 1;
283        }
284
285        // Write header last, to finalize the slot
286        // The last field is `prev`, marking the previous slot as outdated
287        let mut bytes = slot.to_bytes();
288        self.flash.write(slot_addr, &mut bytes)?;
289
290        Ok((idx, slot.chksum))
291    }
292
293    /// Write a static-sized savegame directly into a single slot
294    ///
295    /// This is a more lightweight write operation for fixed-size data that fits
296    /// within a single slot (excluding the header). The size must not exceed
297    /// `SLOT_SIZE - Slot::HEADER_SIZE`.
298    pub fn write_static<const SIZE: usize>(
299        &mut self,
300        mut idx: usize,
301        prev: Chksum,
302        data: &mut [u8; SIZE],
303    ) -> Result<(usize, Chksum), F::Error> {
304        // Sanity check
305        const {
306            let space_available = SLOT_SIZE
307                .checked_sub(Slot::HEADER_SIZE)
308                .expect("Invalid SLOT_SIZE, Slot::HEADER_SIZE doesn't fit");
309            assert!(SIZE <= space_available);
310        }
311
312        // Prepare slot header
313        let slot = Slot::create(idx, prev, data);
314        let slot_addr = self.addr(idx);
315        self.flash.erase(slot_addr)?;
316
317        // Write data directly after header
318        let addr = slot_addr.saturating_add(Slot::HEADER_SIZE as u32);
319        self.flash.write(addr, data)?;
320        idx = idx.saturating_add(1) % SLOT_COUNT;
321
322        // Write header last, to finalize the slot
323        // The last field is `prev`, marking the previous slot as outdated
324        let mut bytes = slot.to_bytes();
325        self.flash.write(slot_addr, &mut bytes)?;
326
327        Ok((idx, slot.chksum))
328    }
329
330    /// Append a new savegame at the next free slot
331    ///
332    /// The new savegame indicates it's an update to the previous savegame,
333    /// when fully written the scanner should find it as the most recent savegame.
334    pub fn append(&mut self, data: &mut [u8]) -> Result<(), F::Error> {
335        let (idx, chksum) = self.write(self.idx, self.prev, data)?;
336        self.idx = idx;
337        self.prev = chksum;
338        Ok(())
339    }
340
341    /// Append a static-sized savegame into the next free slot
342    ///
343    /// This is a more lightweight write operation for fixed-size data that fits
344    /// within a single slot (excluding the header). The size must not exceed
345    /// `SLOT_SIZE - Slot::HEADER_SIZE`.
346    pub fn append_static<const SIZE: usize>(
347        &mut self,
348        data: &mut [u8; SIZE],
349    ) -> Result<(), F::Error> {
350        let (idx, chksum) = self.write_static(self.idx, self.prev, data)?;
351        self.idx = idx;
352        self.prev = chksum;
353        Ok(())
354    }
355
356    /// Reset internal state to initial values
357    ///
358    /// This does not erase any data, but causes the next write to start at slot 0
359    /// with a zeroed previous checksum.
360    pub const fn reset(&mut self) {
361        self.idx = 0;
362        self.prev = Chksum::zero();
363    }
364
365    /// Consume the storage manager and return the underlying flash device
366    ///
367    /// This can be used to retrieve the flash device after all storage operations
368    /// are complete.
369    pub fn into_inner(self) -> F {
370        self.flash
371    }
372}
373
374#[cfg(test)]
375mod tests {
376    use super::*;
377    use crate::mock::{MeasuredMockFlash, MeasuredStats, MockFlash, SectorMockFlash};
378    use core::convert::Infallible;
379
380    const SLOT_SIZE: usize = 64;
381    const SLOT_COUNT: usize = 8;
382    const SIZE: usize = SLOT_SIZE * SLOT_COUNT;
383
384    const fn mock_storage() -> Storage<MockFlash<SIZE>, SLOT_SIZE, SLOT_COUNT> {
385        let flash = MockFlash::<SIZE>::new();
386        Storage::<_, SLOT_SIZE, SLOT_COUNT>::new(flash)
387    }
388
389    const fn mock_sector_storage()
390    -> Storage<SectorMockFlash<SLOT_SIZE, SLOT_COUNT>, SLOT_SIZE, SLOT_COUNT> {
391        let flash = SectorMockFlash::<SLOT_SIZE, SLOT_COUNT>::new();
392        Storage::<_, SLOT_SIZE, SLOT_COUNT>::new(flash)
393    }
394
395    fn mock_measured_storage() -> Storage<MeasuredMockFlash<SIZE>, SLOT_SIZE, SLOT_COUNT> {
396        let flash = MeasuredMockFlash::<SIZE>::new();
397        Storage::<_, SLOT_SIZE, SLOT_COUNT>::new(flash)
398    }
399
400    fn test_storage_empty_scan<F: Flash<Error = Infallible>>(
401        storage: &mut Storage<F, SLOT_SIZE, SLOT_COUNT>,
402    ) {
403        let Ok(slot) = storage.scan();
404        assert_eq!(slot, None);
405    }
406
407    #[test]
408    fn test_at24cxx_storage_empty_scan() {
409        let mut storage = mock_storage();
410        test_storage_empty_scan(&mut storage);
411    }
412
413    #[test]
414    fn test_w25qxx_storage_empty_scan() {
415        let mut storage = mock_sector_storage();
416        test_storage_empty_scan(&mut storage);
417    }
418
419    #[test]
420    fn test_measured_storage_empty_scan() {
421        let mut storage = mock_measured_storage();
422        test_storage_empty_scan(&mut storage);
423        assert_eq!(
424            storage.flash.stats,
425            MeasuredStats {
426                read: 8,
427                write: 0,
428                erase: 0,
429            }
430        );
431    }
432
433    #[test]
434    fn test_storage_write() {
435        let mut storage = mock_storage();
436
437        let mut data = *b"hello world";
438        storage.append(&mut data);
439
440        let mut buf = [0u8; Slot::HEADER_SIZE];
441        storage.flash.read(0, &mut buf);
442        let slot = Slot::from_bytes(0, buf);
443        assert_eq!(
444            slot,
445            Slot {
446                idx: 0,
447                chksum: Chksum::hash(Chksum::zero(), &data),
448                len: data.len() as u32,
449                prev: Chksum::zero(),
450            }
451        );
452    }
453
454    #[test]
455    fn test_storage_write_static() {
456        let mut storage = mock_storage();
457
458        let mut data = *b"hello world";
459        storage.append_static(&mut data);
460
461        let mut buf = [0u8; Slot::HEADER_SIZE];
462        storage.flash.read(0, &mut buf);
463        let slot = Slot::from_bytes(0, buf);
464        assert_eq!(
465            slot,
466            Slot {
467                idx: 0,
468                chksum: Chksum::hash(Chksum::zero(), &data),
469                len: data.len() as u32,
470                prev: Chksum::zero(),
471            }
472        );
473    }
474
475    fn test_storage_write_scan<F: Flash<Error = Infallible>>(
476        storage: &mut Storage<F, SLOT_SIZE, SLOT_COUNT>,
477    ) {
478        let mut data = *b"hello world";
479        storage.append(&mut data);
480
481        let Ok(scan) = storage.scan();
482        assert_eq!(
483            scan,
484            Some(Slot {
485                idx: 0,
486                chksum: Chksum::hash(Chksum::zero(), &data),
487                len: data.len() as u32,
488                prev: Chksum::zero(),
489            })
490        );
491    }
492
493    #[test]
494    fn test_at24cxx_storage_write_scan() {
495        let mut storage = mock_storage();
496        test_storage_write_scan(&mut storage);
497    }
498
499    #[test]
500    fn test_w25qxx_storage_write_scan() {
501        let mut storage = mock_sector_storage();
502        test_storage_write_scan(&mut storage);
503    }
504
505    #[test]
506    fn test_measured_storage_write_scan() {
507        let mut storage = mock_measured_storage();
508        test_storage_write_scan(&mut storage);
509        assert_eq!(
510            storage.flash.stats,
511            MeasuredStats {
512                read: 19,
513                write: 23,
514                erase: 1,
515            }
516        );
517    }
518
519    fn test_storage_write_read<F: Flash<Error = Infallible>>(
520        storage: &mut Storage<F, SLOT_SIZE, SLOT_COUNT>,
521    ) {
522        let mut data = *b"hello world";
523        storage.append(&mut data);
524
525        let mut buf = [0u8; 1024];
526        let Ok(slice) = storage.read(0, &mut buf);
527
528        assert_eq!(slice.map(|s| &*s), Some("hello world".as_bytes()));
529    }
530
531    #[test]
532    fn test_at24cxx_storage_write_read() {
533        let mut storage = mock_storage();
534        test_storage_write_read(&mut storage);
535    }
536
537    #[test]
538    fn test_w25qxx_storage_write_read() {
539        let mut storage = mock_sector_storage();
540        test_storage_write_read(&mut storage);
541    }
542
543    #[test]
544    fn test_measured_storage_write_read() {
545        let mut storage = mock_measured_storage();
546        test_storage_write_read(&mut storage);
547        assert_eq!(
548            storage.flash.stats,
549            MeasuredStats {
550                read: 23,
551                write: 23,
552                erase: 1,
553            }
554        );
555    }
556
557    fn test_storage_write_wrap_around<F: Flash<Error = Infallible>>(
558        storage: &mut Storage<F, SLOT_SIZE, SLOT_COUNT>,
559    ) {
560        for num in 0..(SLOT_COUNT as u32 * 3 + 2) {
561            let mut buf = [0u8; 6];
562            num.to_be_bytes().iter().enumerate().for_each(|(i, b)| {
563                buf[i] = *b;
564            });
565            storage.append(&mut buf);
566        }
567
568        let slot = storage.scan().unwrap().unwrap();
569        assert_eq!(slot.idx, 1);
570        assert_eq!(storage.idx, 2);
571
572        let mut buf = [0u8; 32];
573        let Ok(slice) = storage.read(slot.idx, &mut buf);
574        assert_eq!(slice, Some(&mut [0, 0, 0, 25, 0, 0][..]));
575    }
576
577    #[test]
578    fn test_at24cxx_storage_write_wrap_around() {
579        let mut storage = mock_storage();
580        test_storage_write_wrap_around(&mut storage);
581    }
582
583    #[test]
584    fn test_w25qxx_storage_write_wrap_around() {
585        let mut storage = mock_sector_storage();
586        test_storage_write_wrap_around(&mut storage);
587    }
588
589    #[test]
590    fn test_measured_storage_write_wrap_around() {
591        let mut storage = mock_measured_storage();
592        test_storage_write_wrap_around(&mut storage);
593        assert_eq!(
594            storage.flash.stats,
595            MeasuredStats {
596                read: 114,
597                write: 468,
598                erase: 26,
599            }
600        );
601    }
602
603    fn test_storage_big_write<F: Flash<Error = Infallible>>(
604        storage: &mut Storage<F, SLOT_SIZE, SLOT_COUNT>,
605    ) {
606        let mut buf = [b'A'; SLOT_SIZE * 5];
607        storage.append(&mut buf);
608        let slot = storage.scan().unwrap().unwrap();
609        assert_eq!(
610            slot,
611            Slot {
612                idx: 0,
613                chksum: Chksum::hash(Chksum::zero(), &buf),
614                len: buf.len() as u32,
615                prev: Chksum::zero(),
616            }
617        );
618
619        let mut buf2 = [0u8; 512];
620        let Ok(slice) = storage.read(slot.idx, &mut buf2);
621        assert_eq!(slice.map(|s| &*s), Some(&buf[..]));
622
623        let mut buf = [b'B'; SLOT_SIZE * 5];
624        storage.append(&mut buf);
625        let new_slot = storage.scan().unwrap().unwrap();
626        assert_eq!(
627            new_slot,
628            Slot {
629                idx: 6,
630                chksum: Chksum::hash(slot.chksum, &buf),
631                len: buf.len() as u32,
632                prev: slot.chksum,
633            }
634        );
635    }
636
637    #[test]
638    fn test_at24cxx_storage_big_write() {
639        let mut storage = mock_storage();
640        test_storage_big_write(&mut storage);
641    }
642
643    #[test]
644    fn test_w25qxx_storage_big_write() {
645        let mut storage = mock_sector_storage();
646        test_storage_big_write(&mut storage);
647    }
648
649    #[test]
650    fn test_measured_storage_big_write() {
651        let mut storage = mock_measured_storage();
652        test_storage_big_write(&mut storage);
653        assert_eq!(
654            storage.flash.stats,
655            MeasuredStats {
656                read: 370,
657                write: 664,
658                erase: 12,
659            }
660        );
661    }
662
663    fn test_append_after_scan<F: Flash<Error = Infallible>>(
664        storage: &mut Storage<F, SLOT_SIZE, SLOT_COUNT>,
665    ) {
666        let mut big = [b'A'; SLOT_SIZE * 2];
667        storage.append(&mut big);
668        assert_eq!(storage.idx, 3);
669        storage.idx = 0;
670
671        storage.scan().unwrap();
672        assert_eq!(storage.idx, 3);
673        assert_eq!(storage.prev, Chksum::hash(Chksum::zero(), &big));
674    }
675
676    #[test]
677    fn test_at24cxx_append_after_scan() {
678        let mut storage = mock_storage();
679        test_append_after_scan(&mut storage);
680    }
681
682    #[test]
683    fn test_w25qxx_append_after_scan() {
684        let mut storage = mock_sector_storage();
685        test_append_after_scan(&mut storage);
686    }
687
688    #[test]
689    fn test_measured_append_after_scan() {
690        let mut storage = mock_measured_storage();
691        test_append_after_scan(&mut storage);
692        assert_eq!(
693            storage.flash.stats,
694            MeasuredStats {
695                read: 19,
696                write: 140,
697                erase: 3,
698            }
699        );
700    }
701
702    fn test_append_three_times_then_scan<F: Flash<Error = Infallible>>(
703        storage: &mut Storage<F, SLOT_SIZE, SLOT_COUNT>,
704    ) {
705        let mut data = *b"first";
706        storage.append(&mut data);
707        let mut data = *b"second";
708        storage.append(&mut data);
709        let mut data = *b"third";
710        storage.append(&mut data);
711
712        let slot = storage.scan().unwrap();
713        assert_eq!(
714            slot,
715            Some(Slot {
716                idx: 2,
717                chksum: Chksum::hash(
718                    Chksum::hash(Chksum::hash(Chksum::zero(), b"first"), b"second",),
719                    b"third",
720                ),
721                len: 5,
722                prev: Chksum::hash(Chksum::hash(Chksum::zero(), b"first"), b"second",),
723            })
724        );
725        assert_eq!(storage.idx, 3);
726        assert_eq!(
727            storage.prev,
728            Chksum::hash(
729                Chksum::hash(Chksum::hash(Chksum::zero(), b"first"), b"second",),
730                b"third",
731            )
732        );
733    }
734
735    #[test]
736    fn test_at24cxx_append_three_times_then_scan() {
737        let mut storage = mock_storage();
738        test_append_three_times_then_scan(&mut storage);
739    }
740
741    #[test]
742    fn test_w25qxx_append_three_times_then_scan() {
743        let mut storage = mock_sector_storage();
744        test_append_three_times_then_scan(&mut storage);
745    }
746
747    fn test_append_static_three_times_then_scan<F: Flash<Error = Infallible>>(
748        storage: &mut Storage<F, SLOT_SIZE, SLOT_COUNT>,
749    ) {
750        let mut data = *b"first";
751        storage.append_static(&mut data);
752        let mut data = *b"second";
753        storage.append_static(&mut data);
754        let mut data = *b"third";
755        storage.append_static(&mut data);
756
757        let slot = storage.scan().unwrap();
758        assert_eq!(
759            slot,
760            Some(Slot {
761                idx: 2,
762                chksum: Chksum::hash(
763                    Chksum::hash(Chksum::hash(Chksum::zero(), b"first"), b"second",),
764                    b"third",
765                ),
766                len: 5,
767                prev: Chksum::hash(Chksum::hash(Chksum::zero(), b"first"), b"second",),
768            })
769        );
770        assert_eq!(storage.idx, 3);
771        assert_eq!(
772            storage.prev,
773            Chksum::hash(
774                Chksum::hash(Chksum::hash(Chksum::zero(), b"first"), b"second",),
775                b"third",
776            )
777        );
778    }
779
780    #[test]
781    fn test_at24cxx_append_static_three_times_then_scan() {
782        let mut storage = mock_storage();
783        test_append_static_three_times_then_scan(&mut storage);
784    }
785
786    #[test]
787    fn test_w25qxx_append_static_three_times_then_scan() {
788        let mut storage = mock_sector_storage();
789        test_append_static_three_times_then_scan(&mut storage);
790    }
791
792    #[test]
793    fn test_at24cxx_static_non_static_append_equality() {
794        let mut storage_non_static_writes = mock_storage();
795        test_append_three_times_then_scan(&mut storage_non_static_writes);
796        let mut storage_static_writes = mock_storage();
797        test_append_static_three_times_then_scan(&mut storage_static_writes);
798        assert_eq!(storage_non_static_writes.flash, storage_static_writes.flash);
799    }
800
801    #[test]
802    fn test_w25qxx_static_non_static_append_equality() {
803        let mut storage_non_static_writes = mock_sector_storage();
804        test_append_three_times_then_scan(&mut storage_non_static_writes);
805        let mut storage_static_writes = mock_sector_storage();
806        test_append_static_three_times_then_scan(&mut storage_static_writes);
807        assert_eq!(storage_non_static_writes.flash, storage_static_writes.flash);
808    }
809}