Skip to main content

bootsmith_core/
device.rs

1//! The `Device` trait: the only abstraction every other crate is allowed to use
2//! when it needs to write to "a thing." The macOS raw-block-device impl lives
3//! in `bootsmith-disk`; in tests we substitute an in-memory `Vec<u8>` impl so
4//! `cargo test` works without root, without a USB stick, and without macOS.
5
6use crate::Result;
7
8pub trait Device: Send {
9    /// Total addressable bytes of the underlying device.
10    fn size_bytes(&self) -> Result<u64>;
11
12    /// Read exactly `buf.len()` bytes at `offset`. Errors if read short.
13    fn read_at(&mut self, offset: u64, buf: &mut [u8]) -> Result<()>;
14
15    /// Write exactly `buf.len()` bytes at `offset`. Errors if write short.
16    fn write_at(&mut self, offset: u64, buf: &[u8]) -> Result<()>;
17
18    /// Force any buffered writes to the physical medium.
19    fn sync(&mut self) -> Result<()>;
20
21    /// Human-readable identifier for log messages and the confirm prompt.
22    /// e.g. "/dev/rdisk8 (64 GB SanDisk Cruzer)" or "in-memory test device".
23    fn describe(&self) -> String;
24}
25
26/// Read `len` bytes at `offset` into a freshly-allocated `Vec<u8>`. Convenience
27/// wrapper around `Device::read_at`.
28pub fn read_vec<D: Device + ?Sized>(dev: &mut D, offset: u64, len: usize) -> Result<Vec<u8>> {
29    let mut buf = vec![0u8; len];
30    dev.read_at(offset, &mut buf)?;
31    Ok(buf)
32}
33
34/// Write `buf` at `offset`, then re-read and verify byte-equal. Returns
35/// `Error::VerifyMismatch` on the first divergence.
36///
37/// This is the "verify-by-default" primitive. Callers that legitimately want
38/// to skip verification (e.g. mid-pipeline writes that will be re-checked
39/// holistically later) call `write_at` directly.
40pub fn write_and_verify<D: Device + ?Sized>(
41    dev: &mut D,
42    offset: u64,
43    buf: &[u8],
44) -> Result<()> {
45    dev.write_at(offset, buf)?;
46    let mut check = vec![0u8; buf.len()];
47    dev.read_at(offset, &mut check)?;
48    if check != buf {
49        let first_bad = check
50            .iter()
51            .zip(buf.iter())
52            .position(|(a, b)| a != b)
53            .unwrap_or(0);
54        return Err(crate::Error::VerifyMismatch {
55            offset: offset + first_bad as u64,
56            expected: buf[first_bad..(first_bad + 16).min(buf.len())].to_vec(),
57            actual: check[first_bad..(first_bad + 16).min(check.len())].to_vec(),
58        });
59    }
60    Ok(())
61}
62
63/// In-memory `Device` impl used by unit tests. Backed by a `Vec<u8>`.
64pub struct MemoryDevice {
65    pub bytes: Vec<u8>,
66    pub label: String,
67}
68
69impl MemoryDevice {
70    pub fn new(size: u64) -> Self {
71        Self {
72            bytes: vec![0u8; size as usize],
73            label: format!("in-memory device ({size} bytes)"),
74        }
75    }
76}
77
78impl Device for MemoryDevice {
79    fn size_bytes(&self) -> Result<u64> {
80        Ok(self.bytes.len() as u64)
81    }
82
83    fn read_at(&mut self, offset: u64, buf: &mut [u8]) -> Result<()> {
84        let end = offset as usize + buf.len();
85        if end > self.bytes.len() {
86            return Err(crate::Error::Io(std::io::Error::new(
87                std::io::ErrorKind::UnexpectedEof,
88                "read past end of memory device",
89            )));
90        }
91        buf.copy_from_slice(&self.bytes[offset as usize..end]);
92        Ok(())
93    }
94
95    fn write_at(&mut self, offset: u64, buf: &[u8]) -> Result<()> {
96        let end = offset as usize + buf.len();
97        if end > self.bytes.len() {
98            return Err(crate::Error::Io(std::io::Error::new(
99                std::io::ErrorKind::UnexpectedEof,
100                "write past end of memory device",
101            )));
102        }
103        self.bytes[offset as usize..end].copy_from_slice(buf);
104        Ok(())
105    }
106
107    fn sync(&mut self) -> Result<()> {
108        Ok(())
109    }
110
111    fn describe(&self) -> String {
112        self.label.clone()
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn memory_device_round_trip() {
122        let mut d = MemoryDevice::new(4096);
123        write_and_verify(&mut d, 100, b"hello world").unwrap();
124        assert_eq!(&d.bytes[100..111], b"hello world");
125    }
126
127    #[test]
128    fn memory_device_rejects_past_end() {
129        let mut d = MemoryDevice::new(16);
130        assert!(d.write_at(8, b"more than 8 bytes").is_err());
131    }
132}