embedded_savegame/
lib.rs

1#![no_std]
2
3pub mod chksum;
4#[cfg(feature = "eeprom24x")]
5pub mod eeprom24x;
6#[cfg(any(test, feature = "mock"))]
7pub mod mock;
8pub mod storage;
9#[cfg(feature = "w25q")]
10pub mod w25q;
11
12use crate::chksum::Chksum;
13
14#[derive(Debug, PartialEq)]
15pub struct Slot {
16    pub idx: usize,
17    pub prev: Chksum,
18    pub chksum: Chksum,
19    pub len: u32,
20}
21
22impl Slot {
23    /// Two checksums and one length field.
24    /// The first byte of the checksum is also used to tell if the slot is in use.
25    pub const HEADER_SIZE: usize = Chksum::SIZE * 2 + 4;
26
27    pub fn create(idx: usize, prev: Chksum, data: &[u8]) -> Self {
28        let chksum = Chksum::hash(prev, data);
29        let len = data.len() as u32;
30        Self {
31            idx,
32            prev,
33            chksum,
34            len,
35        }
36    }
37
38    pub fn is_valid(&self) -> bool {
39        self.prev.is_valid() && self.chksum.is_valid()
40    }
41
42    pub fn is_update_to(&self, other: &Self) -> bool {
43        self.prev == other.chksum
44    }
45
46    pub fn used_bytes<const SLOT_SIZE: usize>(&self) -> usize {
47        let mut size = Self::HEADER_SIZE;
48        let mut remaining_data = self.len as usize;
49        let mut remaining_space = SLOT_SIZE - Self::HEADER_SIZE;
50
51        loop {
52            let this_round = remaining_space.min(remaining_data);
53            size = size.saturating_add(this_round);
54            remaining_data = remaining_data.saturating_sub(this_round);
55
56            if remaining_data == 0 {
57                break;
58            }
59
60            size = size.saturating_add(1); // for the next slot's header byte
61            remaining_space = SLOT_SIZE - 1;
62        }
63
64        size
65    }
66
67    pub fn next_slot<const SLOT_SIZE: usize, const SLOT_COUNT: usize>(&self) -> usize {
68        let used_slots = self.used_bytes::<SLOT_SIZE>().div_ceil(SLOT_SIZE);
69        self.idx.saturating_add(used_slots) % SLOT_COUNT
70    }
71
72    pub fn to_bytes(&self) -> [u8; Self::HEADER_SIZE] {
73        let mut buf = [0u8; Self::HEADER_SIZE];
74        let slice = &mut buf[..];
75
76        let (dest, slice) = slice.split_at_mut(Chksum::SIZE);
77        dest.copy_from_slice(&self.prev.to_bytes());
78
79        let (dest, slice) = slice.split_at_mut(Chksum::SIZE);
80        dest.copy_from_slice(&self.chksum.to_bytes());
81
82        let (dest, _slice) = slice.split_at_mut(4);
83        dest.copy_from_slice(&self.len.to_be_bytes());
84
85        buf
86    }
87
88    pub fn from_bytes(idx: usize, bytes: [u8; Self::HEADER_SIZE]) -> Self {
89        let (prev, chksum, len) = arrayref::array_refs![&bytes, Chksum::SIZE, Chksum::SIZE, 4];
90
91        Self {
92            idx,
93            prev: Chksum::from_bytes(*prev),
94            chksum: Chksum::from_bytes(*chksum),
95            len: u32::from_be_bytes(*len),
96        }
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    const SLOT_SIZE: usize = 64;
105    const SLOT_COUNT: usize = 8;
106
107    #[test]
108    fn test_slot_to_bytes() {
109        let slot = Slot::create(0, Chksum::zero(), b"hello");
110        assert_eq!(slot.to_bytes(), [0, 0, 0, 0, 22, 59, 69, 53, 0, 0, 0, 5,]);
111
112        let append = Slot::create(1, slot.chksum, b"world");
113        assert_eq!(
114            append.to_bytes(),
115            [22, 59, 69, 53, 95, 165, 74, 224, 0, 0, 0, 5]
116        );
117    }
118
119    #[test]
120    fn test_slot_size_small() {
121        let slot = Slot::create(0, Chksum::zero(), b"ohai!");
122        assert_eq!(slot.used_bytes::<SLOT_SIZE>(), Slot::HEADER_SIZE + 5);
123        assert_eq!(slot.next_slot::<SLOT_SIZE, SLOT_COUNT>(), 1);
124    }
125
126    #[test]
127    fn test_slot_size_full() {
128        let bytes = [b'B'; SLOT_SIZE - Slot::HEADER_SIZE];
129        let slot = Slot::create(0, Chksum::zero(), &bytes);
130        assert_eq!(slot.used_bytes::<SLOT_SIZE>(), SLOT_SIZE);
131        assert_eq!(slot.next_slot::<SLOT_SIZE, SLOT_COUNT>(), 1);
132    }
133
134    #[test]
135    fn test_slot_spill_over() {
136        let bytes = [b'B'; SLOT_SIZE];
137        let slot = Slot::create(0, Chksum::zero(), &bytes);
138        assert_eq!(
139            slot.used_bytes::<SLOT_SIZE>(),
140            // One extra because the continue-header
141            Slot::HEADER_SIZE + SLOT_SIZE + 1,
142        );
143        assert_eq!(slot.next_slot::<SLOT_SIZE, SLOT_COUNT>(), 2);
144    }
145
146    #[test]
147    fn test_slot_spill_over_twice() {
148        let bytes = [b'B'; SLOT_SIZE * 2];
149        let slot = Slot::create(0, Chksum::zero(), &bytes);
150        assert_eq!(
151            slot.used_bytes::<SLOT_SIZE>(),
152            // Two extra because the continue-header
153            Slot::HEADER_SIZE + SLOT_SIZE * 2 + 2,
154        );
155        assert_eq!(slot.next_slot::<SLOT_SIZE, SLOT_COUNT>(), 3);
156    }
157}