Skip to main content

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}