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 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); 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 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 Slot::HEADER_SIZE + SLOT_SIZE * 2 + 2,
154 );
155 assert_eq!(slot.next_slot::<SLOT_SIZE, SLOT_COUNT>(), 3);
156 }
157}