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