use std::any::type_name;
use std::fmt::{self, Write as _};
use std::fs;
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, PoisonError};
use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor};
use serde::ser::{Serialize, SerializeTuple, Serializer};
use sha2::{Digest, Sha256};
use crate::emulator::{Error as EmulatorError, Host, Resume, Stop};
use crate::machine::Board;
use crate::session::Session;
use crate::soc::{Region, Width};
use crate::version::RUNTIME;
const MAGIC: &[u8; 16] = b"rigless snapshot";
const FORMAT: u16 = 1;
const TARGET: &str = "target";
pub trait Store {
fn store(&self, to: &mut Writer) -> Result<(), Error> {
let _ = to;
Err(Error::Unsaved(type_name::<Self>()))
}
fn load(&mut self, from: &mut Reader) -> Result<(), Error> {
let _ = from;
Err(Error::Unsaved(type_name::<Self>()))
}
}
#[derive(Debug, Default)]
pub struct Writer(Vec<u8>);
impl Writer {
pub fn put<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
let bytes = std::mem::take(&mut self.0);
self.0 =
postcard::to_extend(value, bytes).map_err(|error| Error::Bytes(error.to_string()))?;
Ok(())
}
}
#[derive(Debug)]
pub struct Reader(&'static [u8]);
impl Reader {
pub fn take<T: Deserialize<'static>>(&mut self) -> Result<T, Error> {
let (value, rest) =
postcard::take_from_bytes(self.0).map_err(|error| Error::Bytes(error.to_string()))?;
self.0 = rest;
Ok(value)
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("{0} can't be kept in a snapshot")]
Unsaved(&'static str),
#[error("the run had already failed, so there's nothing to keep")]
Failed,
#[error(
"a reset was asked for at this stop and it hasn't happened yet, so take the snapshot at another"
)]
Resetting,
#[error("the run hasn't got back to the stop it was resumed at")]
Resuming,
#[error("{} isn't a snapshot: {why}", path.display())]
NotASnapshot {
path: PathBuf,
why: &'static str,
},
#[error("the snapshot is of {what} {snapshot}, and this is {here}")]
Differs {
what: &'static str,
snapshot: String,
here: String,
},
#[error("the snapshot has {saved} of {what} where this build has {built}")]
Shape {
what: &'static str,
saved: usize,
built: usize,
},
#[error("the snapshot's bytes don't read back: {0}")]
Bytes(String),
#[error("{0} bytes were left over reading the snapshot")]
Trailing(usize),
#[error(transparent)]
Emulator(#[from] EmulatorError),
#[error("reading {}: {source}", path.display())]
Io {
path: PathBuf,
#[source]
source: std::io::Error,
},
}
#[macro_export]
macro_rules! store {
($type:ident) => {
impl $crate::snapshot::Store for $type {
fn store(
&self,
to: &mut $crate::snapshot::Writer,
) -> ::core::result::Result<(), $crate::snapshot::Error> {
to.put(self)
}
fn load(
&mut self,
from: &mut $crate::snapshot::Reader,
) -> ::core::result::Result<(), $crate::snapshot::Error> {
*self = from.take()?;
::core::result::Result::Ok(())
}
}
};
($type:ident {
values: [$($value:ident),* $(,)?],
parts: [$($part:ident),* $(,)?],
built: [$($built:ident),* $(,)?] $(,)?
}) => {
impl $crate::snapshot::Store for $type {
fn store(
&self,
to: &mut $crate::snapshot::Writer,
) -> ::core::result::Result<(), $crate::snapshot::Error> {
let Self { $($value,)* $($part,)* $($built: _,)* } = self;
$(to.put($value)?;)*
$($crate::snapshot::Store::store($part, to)?;)*
::core::result::Result::Ok(())
}
fn load(
&mut self,
from: &mut $crate::snapshot::Reader,
) -> ::core::result::Result<(), $crate::snapshot::Error> {
let Self { $($value,)* $($part,)* $($built: _,)* } = self;
$(*$value = from.take()?;)*
$($crate::snapshot::Store::load($part, from)?;)*
::core::result::Result::Ok(())
}
}
};
}
pub use crate::store;
impl<T: Store, const N: usize> Store for [T; N] {
fn store(&self, to: &mut Writer) -> Result<(), Error> {
self.iter().try_for_each(|element| element.store(to))
}
fn load(&mut self, from: &mut Reader) -> Result<(), Error> {
self.iter_mut().try_for_each(|element| element.load(from))
}
}
impl<T: Store> Store for Vec<T> {
fn store(&self, to: &mut Writer) -> Result<(), Error> {
to.put(&self.len())?;
self.iter().try_for_each(|element| element.store(to))
}
fn load(&mut self, from: &mut Reader) -> Result<(), Error> {
let saved: usize = from.take()?;
same_shape::<Self>(saved, self.len())?;
self.iter_mut().try_for_each(|element| element.load(from))
}
}
impl<T: Store> Store for Option<T> {
fn store(&self, to: &mut Writer) -> Result<(), Error> {
to.put(&self.is_some())?;
self.as_ref().map_or(Ok(()), |value| value.store(to))
}
fn load(&mut self, from: &mut Reader) -> Result<(), Error> {
let saved: bool = from.take()?;
same_shape::<Self>(saved.into(), self.is_some().into())?;
self.as_mut().map_or(Ok(()), |value| value.load(from))
}
}
fn same_shape<T: ?Sized>(saved: usize, built: usize) -> Result<(), Error> {
if saved == built {
Ok(())
} else {
Err(Error::Shape {
what: type_name::<T>(),
saved,
built,
})
}
}
pub(crate) mod array {
use super::{
Deserialize, Deserializer, PhantomData, SeqAccess, Serialize, SerializeTuple, Serializer,
Visitor, fmt,
};
pub(crate) fn serialize<S: Serializer, T: Serialize, const N: usize>(
array: &[T; N],
serializer: S,
) -> Result<S::Ok, S::Error> {
let mut tuple = serializer.serialize_tuple(N)?;
for element in array {
tuple.serialize_element(element)?;
}
tuple.end()
}
pub(crate) fn deserialize<'de, D: Deserializer<'de>, T: Deserialize<'de>, const N: usize>(
deserializer: D,
) -> Result<[T; N], D::Error> {
deserializer.deserialize_tuple(N, Elements::<T, N>(PhantomData))
}
struct Elements<T, const N: usize>(PhantomData<T>);
impl<'de, T: Deserialize<'de>, const N: usize> Visitor<'de> for Elements<T, N> {
type Value = [T; N];
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{N} elements")
}
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<[T; N], A::Error> {
let mut elements = Vec::with_capacity(N);
while let Some(element) = seq.next_element()? {
elements.push(element);
}
let read = elements.len();
elements
.try_into()
.map_err(|_short| serde::de::Error::invalid_length(read, &self))
}
}
}
#[derive(Debug)]
pub struct Taken {
pub file: Vec<u8>,
pub hash: [u8; 32],
}
pub(crate) fn parse<'a>(file: &'a [u8], path: &Path) -> Result<(Body<'a>, [u8; 32]), Error> {
let (body, hash) = target_part(file).map_err(|why| Error::NotASnapshot {
path: path.to_path_buf(),
why,
})?;
let (body, left) = postcard::take_from_bytes::<Body<'a>>(body)
.map_err(|error| Error::Bytes(error.to_string()))?;
if !left.is_empty() {
return Err(Error::Trailing(left.len()));
}
same("the runtime", body.runtime, RUNTIME)?;
Ok((body, hash))
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct Body<'a> {
runtime: &'a str,
machine: &'a str,
target: &'a str,
firmware: [u8; 32],
stop: Stop,
#[serde(serialize_with = "bytes")]
emulator: &'a [u8],
#[serde(borrow)]
memory: Vec<Contents<'a>>,
#[serde(serialize_with = "bytes")]
session: &'a [u8],
#[serde(serialize_with = "bytes")]
driver: &'a [u8],
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct Contents<'a> {
base: u64,
#[serde(serialize_with = "bytes")]
bytes: &'a [u8],
}
fn bytes<S: Serializer>(bytes: &&[u8], serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_bytes(bytes)
}
pub fn take<D: Board + ?Sized>(
session: &mut Session<D>,
host: &mut dyn Host,
stop: Stop,
firmware: &[u8; 32],
driver: &dyn Store,
) -> Result<Taken, Error> {
let emulator = host.save_state()?;
let memory = saved_regions(session.mcu().bus().memory())
.map(|region| Ok((region.base, read_region(host, region)?)))
.collect::<Result<Vec<_>, Error>>()?;
let mut state = Writer::default();
session.store(&mut state)?;
let mut kept = Writer::default();
driver.store(&mut kept)?;
let target = session.board().target_names()[session.target()];
let body = Body {
runtime: RUNTIME,
machine: session.board().name(),
target,
firmware: *firmware,
stop,
emulator: &emulator,
memory: memory
.iter()
.map(|(base, bytes)| Contents { base: *base, bytes })
.collect(),
session: &state.0,
driver: &kept.0,
};
let body = postcard::to_allocvec(&body).map_err(|error| Error::Bytes(error.to_string()))?;
let mut file = MAGIC.to_vec();
file.extend_from_slice(&FORMAT.to_le_bytes());
let hash = put_part(&mut file, TARGET, &body);
Ok(Taken { file, hash })
}
fn put_part(file: &mut Vec<u8>, name: &str, body: &[u8]) -> [u8; 32] {
let hash = digest(body);
file.push(u8::try_from(name.len()).expect("a short name"));
file.extend_from_slice(name.as_bytes());
file.extend_from_slice(
&u64::try_from(body.len())
.expect("a length fits")
.to_le_bytes(),
);
file.extend_from_slice(&hash);
file.extend_from_slice(body);
hash
}
fn target_part(file: &[u8]) -> Result<(&[u8], [u8; 32]), &'static str> {
const ENDS_EARLY: &str = "it ends partway through a part";
fn split<'a>(bytes: &mut &'a [u8], length: usize) -> Result<&'a [u8], &'static str> {
let (taken, rest) = bytes.split_at_checked(length).ok_or(ENDS_EARLY)?;
*bytes = rest;
Ok(taken)
}
let mut rest = file.strip_prefix(MAGIC).ok_or("it doesn't start as one")?;
if split(&mut rest, 2)? != FORMAT.to_le_bytes() {
return Err("it's laid out in a format this runtime doesn't read");
}
while !rest.is_empty() {
let name = split(&mut rest, 1)?[0];
let name = split(&mut rest, name.into())?;
let length = u64::from_le_bytes(split(&mut rest, 8)?.try_into().expect("eight bytes"));
let hash: [u8; 32] = split(&mut rest, 32)?.try_into().expect("thirty-two bytes");
let body = split(
&mut rest,
usize::try_from(length).map_err(|_huge| ENDS_EARLY)?,
)?;
if name == TARGET.as_bytes() {
return if digest(body) == hash {
Ok((body, hash))
} else {
Err("its target part doesn't match its own hash")
};
}
}
Err("it has no target part")
}
fn saved_regions(memory: &[Region]) -> impl Iterator<Item = &Region> {
memory
.iter()
.filter(|region| region.kind.is_writable() || region.programmer.is_some())
}
fn read_region(host: &mut dyn Host, region: &Region) -> Result<Vec<u8>, Error> {
let size = usize::try_from(region.size).expect("a region fits in memory");
let mut bytes = Vec::with_capacity(size);
let mut at = region.base;
while bytes.len() + 8 <= size {
bytes.extend_from_slice(&crate::emulator::memory::to_target(
host.read(at, Width::W64)?,
8,
host.endian(),
));
at += 8;
}
while bytes.len() < size {
bytes.push(host.read(at, Width::W8)?.to_le_bytes()[0]);
at += 1;
}
Ok(bytes)
}
#[must_use]
pub fn digest(bytes: &[u8]) -> [u8; 32] {
Sha256::digest(bytes).into()
}
fn hex(digest: &[u8; 32]) -> String {
digest
.iter()
.fold(String::with_capacity(64), |mut text, byte| {
write!(text, "{byte:02x}").expect("a string takes any text");
text
})
}
#[derive(Debug)]
pub struct Snapshot {
hash: [u8; 32],
body: Body<'static>,
}
impl Snapshot {
pub fn read(path: &Path) -> Result<Self, Error> {
let file = fs::read(path).map_err(|source| Error::Io {
path: path.to_path_buf(),
source,
})?;
Self::from_bytes(keep(file), path)
}
fn from_bytes(file: &'static [u8], path: &Path) -> Result<Self, Error> {
let (body, hash) = parse(file, path)?;
Ok(Self { hash, body })
}
#[must_use]
pub fn hash(&self) -> [u8; 32] {
self.hash
}
#[must_use]
pub fn stop(&self) -> Stop {
self.body.stop
}
pub fn restore<D: Board + ?Sized>(
&self,
session: &mut Session<D>,
firmware: &[u8; 32],
driver: &mut dyn Store,
) -> Result<Resume<'static>, Error> {
let body = &self.body;
let here = session.board().target_names()[session.target()];
same("the machine", body.machine, session.board().name())?;
same("the target", body.target, here)?;
if body.firmware != *firmware {
return Err(Error::Differs {
what: "the firmware",
snapshot: hex(&body.firmware),
here: hex(firmware),
});
}
let regions = |regions: &mut dyn Iterator<Item = (u64, usize)>| {
regions
.map(|(base, size)| format!("{size} bytes at {base:#x}"))
.collect::<Vec<_>>()
.join(", ")
};
let saved = regions(
&mut body
.memory
.iter()
.map(|contents| (contents.base, contents.bytes.len())),
);
let built = regions(
&mut saved_regions(session.mcu().bus().memory()).map(|region| {
let size = usize::try_from(region.size).expect("a region fits in memory");
(region.base, size)
}),
);
same("memory", &saved, &built)?;
load(session, body.session)?;
load(driver, body.driver)?;
Ok(Resume {
stop: self.body.stop,
state: self.body.emulator,
memory: self
.body
.memory
.iter()
.map(|contents| (contents.base, contents.bytes))
.collect(),
})
}
}
pub(crate) fn stored(kept: &dyn Store) -> Result<Vec<u8>, Error> {
let mut to = Writer::default();
kept.store(&mut to)?;
Ok(to.0)
}
pub(crate) fn keep(bytes: Vec<u8>) -> &'static [u8] {
static KEPT: Mutex<Vec<&'static [u8]>> = Mutex::new(Vec::new());
let kept: &'static [u8] = Box::leak(bytes.into_boxed_slice());
KEPT.lock()
.unwrap_or_else(PoisonError::into_inner)
.push(kept);
kept
}
fn same(what: &'static str, snapshot: &str, here: &str) -> Result<(), Error> {
if snapshot == here {
Ok(())
} else {
Err(Error::Differs {
what,
snapshot: snapshot.to_owned(),
here: here.to_owned(),
})
}
}
pub(crate) fn load(into: &mut dyn Store, bytes: &'static [u8]) -> Result<(), Error> {
let mut from = Reader(bytes);
into.load(&mut from)?;
if from.0.is_empty() {
Ok(())
} else {
Err(Error::Trailing(from.0.len()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_snapshot_saves_the_writable_regions_and_the_programmed_ones() {
let memory = [
Region::flash("flash", 0x0800_0000, 0x1000).programmed_by("PROG"),
Region::flash("other", 0x0900_0000, 0x1000),
Region::system("system", 0x0A00_0000, 0x100),
Region::ram("sram", 0x2000_0000, 0x100),
];
let saved: Vec<&str> = saved_regions(&memory).map(|region| region.name).collect();
assert_eq!(saved, ["flash", "sram"]);
}
#[derive(Debug, Default, PartialEq)]
struct Counting(u64);
impl Store for Counting {
fn store(&self, to: &mut Writer) -> Result<(), Error> {
to.put(&self.0)
}
fn load(&mut self, from: &mut Reader) -> Result<(), Error> {
self.0 = from.take()?;
Ok(())
}
}
const LAID_OUT: &str = "7269676c65737320736e617073686f740100067461726765740400000000000000230d8358dc8e8890b4c58deeb62912ee2f20357ae92a5cc861b98e68fe31acb5626f6479037269670300000000000000c00ae0a63348054ecc8343f23278fe13bfa5c44255e208ff6102ffc2d7910a53726967";
#[test]
fn the_file_is_laid_out_as_the_engine_lays_it_out_and_the_target_finds_its_own_part() {
let mut file = MAGIC.to_vec();
file.extend_from_slice(&FORMAT.to_le_bytes());
put_part(&mut file, TARGET, b"body");
put_part(&mut file, "rig", b"rig");
let laid_out = file.iter().fold(String::new(), |mut text, byte| {
write!(text, "{byte:02x}").expect("a string takes any text");
text
});
assert_eq!(laid_out, LAID_OUT);
assert_eq!(target_part(&file).unwrap().0, b"body");
}
#[test]
fn a_collection_of_another_length_or_presence_is_refused() {
let mut to = Writer::default();
vec![Counting(1), Counting(2)].store(&mut to).unwrap();
Some(Counting(3)).store(&mut to).unwrap();
let mut from = Reader(keep(to.0));
assert!(matches!(
vec![Counting(0)].load(&mut from),
Err(Error::Shape {
saved: 2,
built: 1,
..
})
));
let mut to = Writer::default();
Some(Counting(3)).store(&mut to).unwrap();
let mut from = Reader(keep(to.0));
assert!(matches!(
None::<Counting>.load(&mut from),
Err(Error::Shape { .. })
));
}
#[test]
fn bytes_left_over_are_refused() {
let mut to = Writer::default();
Counting(1).store(&mut to).unwrap();
to.put(&2_u64).unwrap();
assert!(matches!(
load(&mut Counting(0), keep(to.0)),
Err(Error::Trailing(1))
));
}
#[test]
fn an_array_of_any_length_reads_back() {
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
struct Wide {
#[serde(with = "array")]
bytes: [u8; 100],
}
let wide = Wide {
bytes: std::array::from_fn(|index| u8::try_from(index).unwrap()),
};
let mut to = Writer::default();
to.put(&wide).unwrap();
assert_eq!(Reader(keep(to.0)).take::<Wide>().unwrap(), wide);
}
}