pamoja_update/slots.rs
1//! Where images live, and what the device believes about each one.
2//!
3//! A device that can be updated safely needs somewhere to put the new image that
4//! is not where the running one lives, so a failed update leaves something to go
5//! back to. That is all a slot is.
6//!
7//! Storage itself is the integrator's: internal flash, an external chip, a file on
8//! an SD card. [`SlotStore`] is the seam, so the update rules can be exercised in
9//! full with no hardware, the way `MemoryLink` does for the MAVLink link layer.
10
11use alloc::vec;
12use alloc::vec::Vec;
13
14use crate::error::{Refusal, Result};
15use crate::manifest::DIGEST_LEN;
16
17/// What the device believes about a slot.
18#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
19pub enum SlotState {
20 /// Nothing here, or nothing worth keeping.
21 #[default]
22 Empty,
23 /// Holds part of an image that is still arriving. Never bootable: the bytes
24 /// are unverified until the whole image is in, and the state exists so a
25 /// transfer cut off by a dead link can pick up where it stopped.
26 Receiving,
27 /// Holds a verified image that has not been booted yet.
28 Staged,
29 /// Was booted but has not yet reported itself healthy.
30 Pending,
31 /// Booted and confirmed healthy. This is what the device falls back to.
32 Confirmed,
33 /// Was booted and never confirmed, so it is not to be tried again.
34 Failed,
35}
36
37/// What a slot holds.
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
39pub struct SlotRecord {
40 /// The slot's state.
41 pub state: SlotState,
42 /// The sequence number of the image in the slot.
43 pub sequence: u64,
44 /// The image's length in bytes.
45 pub size: u32,
46 /// The image's digest.
47 pub digest: [u8; DIGEST_LEN],
48 /// How many bytes of the image have been stored so far.
49 ///
50 /// Equal to `size` once the image is complete. While it is smaller, this is
51 /// where a resumed transfer starts again.
52 pub written: u32,
53}
54
55/// Somewhere to keep images, and the device's belief about each one.
56///
57/// An implementation must keep the record durable across a reboot. If it does not,
58/// a device that loses power mid-update cannot tell what it was doing, which is
59/// exactly the situation slots exist to survive.
60pub trait SlotStore {
61 /// Returns how many slots this device has.
62 fn slot_count(&self) -> u8;
63
64 /// Returns how many bytes a slot can hold.
65 ///
66 /// # Arguments
67 ///
68 /// * `slot` - the slot to size.
69 ///
70 /// # Returns
71 ///
72 /// The slot's capacity in bytes.
73 ///
74 /// # Errors
75 ///
76 /// Returns [`Refusal::NoSuchSlot`] if the slot does not exist.
77 fn capacity(&self, slot: u8) -> Result<u32>;
78
79 /// Reads what the device believes about a slot.
80 ///
81 /// # Arguments
82 ///
83 /// * `slot` - the slot to read.
84 ///
85 /// # Returns
86 ///
87 /// The slot's record.
88 ///
89 /// # Errors
90 ///
91 /// Returns [`Refusal::NoSuchSlot`] if the slot does not exist.
92 fn record(&self, slot: u8) -> Result<SlotRecord>;
93
94 /// Writes what the device believes about a slot, durably.
95 ///
96 /// # Arguments
97 ///
98 /// * `slot` - the slot to describe.
99 /// * `record` - the new record.
100 ///
101 /// # Returns
102 ///
103 /// `Ok(())` once the record will survive a reboot.
104 ///
105 /// # Errors
106 ///
107 /// Returns [`Refusal::NoSuchSlot`] if the slot does not exist.
108 fn set_record(&mut self, slot: u8, record: SlotRecord) -> Result<()>;
109
110 /// Clears a slot's contents and marks it empty.
111 ///
112 /// # Arguments
113 ///
114 /// * `slot` - the slot to clear.
115 ///
116 /// # Returns
117 ///
118 /// `Ok(())` once the slot holds nothing.
119 ///
120 /// # Errors
121 ///
122 /// Returns [`Refusal::NoSuchSlot`] if the slot does not exist.
123 fn erase(&mut self, slot: u8) -> Result<()>;
124
125 /// Writes image bytes at an offset within a slot.
126 ///
127 /// # Arguments
128 ///
129 /// * `slot` - the slot to write into.
130 /// * `offset` - where in the slot the bytes belong.
131 /// * `bytes` - the bytes to write.
132 ///
133 /// # Returns
134 ///
135 /// `Ok(())` once the bytes are stored.
136 ///
137 /// # Errors
138 ///
139 /// Returns [`Refusal::NoSuchSlot`] if the slot does not exist, or
140 /// [`Refusal::SlotTooSmall`] if the write would run past the slot's end.
141 fn write(&mut self, slot: u8, offset: u32, bytes: &[u8]) -> Result<()>;
142
143 /// Reads image bytes from an offset within a slot.
144 ///
145 /// # Arguments
146 ///
147 /// * `slot` - the slot to read from.
148 /// * `offset` - where in the slot to start.
149 /// * `buf` - the destination.
150 ///
151 /// # Returns
152 ///
153 /// How many bytes were read, which is short only at the slot's end.
154 ///
155 /// # Errors
156 ///
157 /// Returns [`Refusal::NoSuchSlot`] if the slot does not exist.
158 fn read(&self, slot: u8, offset: u32, buf: &mut [u8]) -> Result<usize>;
159}
160
161/// A [`SlotStore`] held in memory, for tests and for running the flow with no
162/// hardware.
163///
164/// It forgets everything when dropped, which is the one thing a real store must
165/// not do, so it stands in for storage without pretending to be it.
166#[derive(Clone, Debug)]
167pub struct MemoryStore {
168 slots: Vec<Vec<u8>>,
169 records: Vec<SlotRecord>,
170}
171
172impl MemoryStore {
173 /// Creates a store with `count` slots of `capacity` bytes each.
174 ///
175 /// # Arguments
176 ///
177 /// * `count` - how many slots the device has.
178 /// * `capacity` - how many bytes each slot holds.
179 ///
180 /// # Returns
181 ///
182 /// An empty store.
183 pub fn new(count: u8, capacity: u32) -> Self {
184 Self {
185 slots: vec![vec![0u8; capacity as usize]; count as usize],
186 records: vec![SlotRecord::default(); count as usize],
187 }
188 }
189
190 /// Returns the index of a slot, refusing one this device does not have.
191 fn index(&self, slot: u8) -> Result<usize> {
192 if usize::from(slot) >= self.slots.len() {
193 return Err(Refusal::NoSuchSlot);
194 }
195 Ok(usize::from(slot))
196 }
197}
198
199impl SlotStore for MemoryStore {
200 fn slot_count(&self) -> u8 {
201 self.slots.len() as u8
202 }
203
204 fn capacity(&self, slot: u8) -> Result<u32> {
205 Ok(self.slots[self.index(slot)?].len() as u32)
206 }
207
208 fn record(&self, slot: u8) -> Result<SlotRecord> {
209 Ok(self.records[self.index(slot)?])
210 }
211
212 fn set_record(&mut self, slot: u8, record: SlotRecord) -> Result<()> {
213 let at = self.index(slot)?;
214 self.records[at] = record;
215 Ok(())
216 }
217
218 fn erase(&mut self, slot: u8) -> Result<()> {
219 let at = self.index(slot)?;
220 self.slots[at].fill(0);
221 self.records[at] = SlotRecord::default();
222 Ok(())
223 }
224
225 fn write(&mut self, slot: u8, offset: u32, bytes: &[u8]) -> Result<()> {
226 let at = self.index(slot)?;
227 let start = offset as usize;
228 let end = start
229 .checked_add(bytes.len())
230 .ok_or(Refusal::SlotTooSmall)?;
231 if end > self.slots[at].len() {
232 return Err(Refusal::SlotTooSmall);
233 }
234 self.slots[at][start..end].copy_from_slice(bytes);
235 Ok(())
236 }
237
238 fn read(&self, slot: u8, offset: u32, buf: &mut [u8]) -> Result<usize> {
239 let at = self.index(slot)?;
240 let start = (offset as usize).min(self.slots[at].len());
241 let len = buf.len().min(self.slots[at].len() - start);
242 buf[..len].copy_from_slice(&self.slots[at][start..start + len]);
243 Ok(len)
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn a_fresh_store_has_empty_slots() {
253 let store = MemoryStore::new(2, 64);
254 assert_eq!(store.slot_count(), 2);
255 assert_eq!(store.capacity(0).expect("capacity"), 64);
256 assert_eq!(store.record(0).expect("record").state, SlotState::Empty);
257 }
258
259 #[test]
260 fn writes_read_back() {
261 let mut store = MemoryStore::new(1, 16);
262 store.write(0, 4, b"abcd").expect("write");
263 let mut buf = [0u8; 4];
264 assert_eq!(store.read(0, 4, &mut buf).expect("read"), 4);
265 assert_eq!(&buf, b"abcd");
266 }
267
268 #[test]
269 fn a_write_past_the_end_is_refused() {
270 let mut store = MemoryStore::new(1, 8);
271 assert_eq!(
272 store.write(0, 6, b"abcd"),
273 Err(Refusal::SlotTooSmall),
274 "a slot must never be written past its capacity"
275 );
276 }
277
278 #[test]
279 fn a_slot_the_device_does_not_have_is_refused() {
280 let mut store = MemoryStore::new(1, 8);
281 assert_eq!(store.capacity(3), Err(Refusal::NoSuchSlot));
282 assert_eq!(store.record(3), Err(Refusal::NoSuchSlot));
283 assert_eq!(store.write(3, 0, b"x"), Err(Refusal::NoSuchSlot));
284 }
285
286 #[test]
287 fn erasing_clears_both_the_bytes_and_the_record() {
288 let mut store = MemoryStore::new(1, 8);
289 store.write(0, 0, b"abcd").expect("write");
290 store
291 .set_record(
292 0,
293 SlotRecord {
294 state: SlotState::Confirmed,
295 sequence: 4,
296 size: 4,
297 digest: [7; DIGEST_LEN],
298 written: 4,
299 },
300 )
301 .expect("record");
302
303 store.erase(0).expect("erase");
304 assert_eq!(store.record(0).expect("record"), SlotRecord::default());
305 let mut buf = [0xffu8; 4];
306 store.read(0, 0, &mut buf).expect("read");
307 assert_eq!(&buf, &[0, 0, 0, 0]);
308 }
309
310 #[test]
311 fn reading_past_the_end_returns_what_there_is() {
312 let store = MemoryStore::new(1, 4);
313 let mut buf = [0u8; 8];
314 assert_eq!(store.read(0, 2, &mut buf).expect("read"), 2);
315 assert_eq!(store.read(0, 99, &mut buf).expect("read"), 0);
316 }
317}