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