use alloc::vec;
use alloc::vec::Vec;
use crate::error::{Refusal, Result};
use crate::manifest::DIGEST_LEN;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum SlotState {
#[default]
Empty,
Receiving,
Staged,
Pending,
Confirmed,
Failed,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct SlotRecord {
pub state: SlotState,
pub sequence: u64,
pub size: u32,
pub digest: [u8; DIGEST_LEN],
pub written: u32,
}
pub trait SlotStore {
fn slot_count(&self) -> u8;
fn capacity(&self, slot: u8) -> Result<u32>;
fn record(&self, slot: u8) -> Result<SlotRecord>;
fn set_record(&mut self, slot: u8, record: SlotRecord) -> Result<()>;
fn erase(&mut self, slot: u8) -> Result<()>;
fn write(&mut self, slot: u8, offset: u32, bytes: &[u8]) -> Result<()>;
fn read(&self, slot: u8, offset: u32, buf: &mut [u8]) -> Result<usize>;
}
#[derive(Clone, Debug)]
pub struct MemoryStore {
slots: Vec<Vec<u8>>,
records: Vec<SlotRecord>,
}
impl MemoryStore {
pub fn new(count: u8, capacity: u32) -> Self {
Self {
slots: vec![vec![0u8; capacity as usize]; count as usize],
records: vec![SlotRecord::default(); count as usize],
}
}
fn index(&self, slot: u8) -> Result<usize> {
if usize::from(slot) >= self.slots.len() {
return Err(Refusal::NoSuchSlot);
}
Ok(usize::from(slot))
}
}
impl SlotStore for MemoryStore {
fn slot_count(&self) -> u8 {
self.slots.len() as u8
}
fn capacity(&self, slot: u8) -> Result<u32> {
Ok(self.slots[self.index(slot)?].len() as u32)
}
fn record(&self, slot: u8) -> Result<SlotRecord> {
Ok(self.records[self.index(slot)?])
}
fn set_record(&mut self, slot: u8, record: SlotRecord) -> Result<()> {
let at = self.index(slot)?;
self.records[at] = record;
Ok(())
}
fn erase(&mut self, slot: u8) -> Result<()> {
let at = self.index(slot)?;
self.slots[at].fill(0);
self.records[at] = SlotRecord::default();
Ok(())
}
fn write(&mut self, slot: u8, offset: u32, bytes: &[u8]) -> Result<()> {
let at = self.index(slot)?;
let start = offset as usize;
let end = start
.checked_add(bytes.len())
.ok_or(Refusal::SlotTooSmall)?;
if end > self.slots[at].len() {
return Err(Refusal::SlotTooSmall);
}
self.slots[at][start..end].copy_from_slice(bytes);
Ok(())
}
fn read(&self, slot: u8, offset: u32, buf: &mut [u8]) -> Result<usize> {
let at = self.index(slot)?;
let start = (offset as usize).min(self.slots[at].len());
let len = buf.len().min(self.slots[at].len() - start);
buf[..len].copy_from_slice(&self.slots[at][start..start + len]);
Ok(len)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_fresh_store_has_empty_slots() {
let store = MemoryStore::new(2, 64);
assert_eq!(store.slot_count(), 2);
assert_eq!(store.capacity(0).expect("capacity"), 64);
assert_eq!(store.record(0).expect("record").state, SlotState::Empty);
}
#[test]
fn writes_read_back() {
let mut store = MemoryStore::new(1, 16);
store.write(0, 4, b"abcd").expect("write");
let mut buf = [0u8; 4];
assert_eq!(store.read(0, 4, &mut buf).expect("read"), 4);
assert_eq!(&buf, b"abcd");
}
#[test]
fn a_write_past_the_end_is_refused() {
let mut store = MemoryStore::new(1, 8);
assert_eq!(
store.write(0, 6, b"abcd"),
Err(Refusal::SlotTooSmall),
"a slot must never be written past its capacity"
);
}
#[test]
fn a_slot_the_device_does_not_have_is_refused() {
let mut store = MemoryStore::new(1, 8);
assert_eq!(store.capacity(3), Err(Refusal::NoSuchSlot));
assert_eq!(store.record(3), Err(Refusal::NoSuchSlot));
assert_eq!(store.write(3, 0, b"x"), Err(Refusal::NoSuchSlot));
}
#[test]
fn erasing_clears_both_the_bytes_and_the_record() {
let mut store = MemoryStore::new(1, 8);
store.write(0, 0, b"abcd").expect("write");
store
.set_record(
0,
SlotRecord {
state: SlotState::Confirmed,
sequence: 4,
size: 4,
digest: [7; DIGEST_LEN],
written: 4,
},
)
.expect("record");
store.erase(0).expect("erase");
assert_eq!(store.record(0).expect("record"), SlotRecord::default());
let mut buf = [0xffu8; 4];
store.read(0, 0, &mut buf).expect("read");
assert_eq!(&buf, &[0, 0, 0, 0]);
}
#[test]
fn reading_past_the_end_returns_what_there_is() {
let store = MemoryStore::new(1, 4);
let mut buf = [0u8; 8];
assert_eq!(store.read(0, 2, &mut buf).expect("read"), 2);
assert_eq!(store.read(0, 99, &mut buf).expect("read"), 0);
}
}