use std::ffi::OsStr;
use std::io::{self, Read, Write};
use std::num::NonZeroU64;
use std::path::{Path, PathBuf};
use std::process::{Child, ChildStdin, ChildStdout, Command, ExitStatus, Stdio};
use crate::edge::Field;
use crate::emulator::{AccessKind, Stop, StopReason};
use crate::time::VirtualNanos;
mod serve;
mod trigger;
pub use serve::{
EnginePort, Gaps, Held, Launch, ServeError, attach_engine_ports, deliver, report, sent, serve,
};
pub use trigger::Triggers;
pub const VERSION: u16 = 5;
pub(crate) const LARGEST_FRAME: usize = 1 << 20;
const KIND_ADVANCE: u8 = 1;
const KIND_REACHED: u8 = 2;
const KIND_SHUTDOWN: u8 = 3;
const KIND_REPORT: u8 = 4;
const KIND_MANIFEST: u8 = 5;
const KIND_OPEN: u8 = 6;
const KIND_SNAPSHOT: u8 = 7;
const KIND_SNAPSHOTTED: u8 = 8;
const KIND_REFUSED: u8 = 9;
pub trait Wire: Sized {
fn encode(&self, out: &mut Vec<u8>);
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum WireError {
#[error("the frame ends after {have} of the {need} bytes a value needs")]
Short {
have: usize,
need: usize,
},
#[error("{value} is no {what}")]
Invalid {
what: &'static str,
value: u64,
},
#[error("{0} bytes follow the last value of the frame")]
Trailing(usize),
}
fn take<'a>(bytes: &mut &'a [u8], need: usize) -> Result<&'a [u8], WireError> {
if bytes.len() < need {
return Err(WireError::Short {
have: bytes.len(),
need,
});
}
let (taken, rest) = bytes.split_at(need);
*bytes = rest;
Ok(taken)
}
macro_rules! integer_wire {
($($int:ty),*) => {$(
impl Wire for $int {
fn encode(&self, out: &mut Vec<u8>) {
out.extend_from_slice(&self.to_le_bytes());
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
let taken = take(bytes, size_of::<$int>())?;
Ok(<$int>::from_le_bytes(taken.try_into().expect("taken that many")))
}
}
)*};
}
integer_wire!(u8, u16, u32, u64, i8, i16, i32, i64);
impl Wire for () {
fn encode(&self, _out: &mut Vec<u8>) {}
fn decode(_bytes: &mut &[u8]) -> Result<Self, WireError> {
Ok(())
}
}
impl Wire for bool {
fn encode(&self, out: &mut Vec<u8>) {
u8::from(*self).encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
match u8::decode(bytes)? {
0 => Ok(false),
1 => Ok(true),
value => Err(WireError::Invalid {
what: "bool",
value: u64::from(value),
}),
}
}
}
impl Wire for VirtualNanos {
fn encode(&self, out: &mut Vec<u8>) {
self.0.encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
u64::decode(bytes).map(Self)
}
}
impl<T: Wire, const N: usize> Wire for [T; N] {
fn encode(&self, out: &mut Vec<u8>) {
for value in self {
value.encode(out);
}
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
let mut values = Vec::with_capacity(N);
for _ in 0..N {
values.push(T::decode(bytes)?);
}
Ok(values
.try_into()
.unwrap_or_else(|_| unreachable!("decoded N values")))
}
}
impl<T: Wire> Wire for Option<T> {
fn encode(&self, out: &mut Vec<u8>) {
match self {
None => false.encode(out),
Some(value) => {
true.encode(out);
value.encode(out);
}
}
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
if bool::decode(bytes)? {
T::decode(bytes).map(Some)
} else {
Ok(None)
}
}
}
impl<T: Wire> Wire for Vec<T> {
fn encode(&self, out: &mut Vec<u8>) {
u32::try_from(self.len())
.expect("fewer values than a word counts")
.encode(out);
for value in self {
value.encode(out);
}
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
let count = u32::decode(bytes)?;
let mut values = Vec::new();
for _ in 0..count {
values.push(T::decode(bytes)?);
}
Ok(values)
}
}
impl Wire for String {
fn encode(&self, out: &mut Vec<u8>) {
self.as_bytes().to_vec().encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
Self::from_utf8(Vec::<u8>::decode(bytes)?).map_err(|error| WireError::Invalid {
what: "text valid past this byte",
value: error.utf8_error().valid_up_to() as u64,
})
}
}
impl Wire for Field {
fn encode(&self, out: &mut Vec<u8>) {
self.name.to_string().encode(out);
self.unit.to_string().encode(out);
self.width.encode(out);
self.signed.encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
let name = String::decode(bytes)?.into();
let unit = String::decode(bytes)?.into();
let width = checked_width(u8::decode(bytes)?)?;
Ok(Self {
name,
unit,
width,
signed: Wire::decode(bytes)?,
})
}
}
pub(crate) fn checked_width(width: u8) -> Result<u8, WireError> {
match width {
1 | 2 | 4 | 8 => Ok(width),
_ => Err(WireError::Invalid {
what: "field width",
value: u64::from(width),
}),
}
}
impl Wire for StopReason {
fn encode(&self, out: &mut Vec<u8>) {
let code: u8 = match self {
Self::Deadline => 0,
Self::Idle => 1,
Self::Reset => 2,
};
code.encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
match u8::decode(bytes)? {
0 => Ok(Self::Deadline),
1 => Ok(Self::Idle),
2 => Ok(Self::Reset),
code => Err(WireError::Invalid {
what: "stop reason",
value: u64::from(code),
}),
}
}
}
impl Wire for Stop {
fn encode(&self, out: &mut Vec<u8>) {
self.at.encode(out);
self.instruction_count.encode(out);
self.reason.encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
Ok(Self {
at: Wire::decode(bytes)?,
instruction_count: Wire::decode(bytes)?,
reason: Wire::decode(bytes)?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[non_exhaustive]
pub struct Report {
pub resets: Vec<VirtualNanos>,
pub unmapped: u64,
pub refused: u64,
pub unimplemented: u64,
pub unrouted: u64,
pub told: u64,
}
impl Wire for Report {
fn encode(&self, out: &mut Vec<u8>) {
self.resets.encode(out);
self.unmapped.encode(out);
self.refused.encode(out);
self.unimplemented.encode(out);
self.unrouted.encode(out);
self.told.encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
Ok(Self {
resets: Wire::decode(bytes)?,
unmapped: Wire::decode(bytes)?,
refused: Wire::decode(bytes)?,
unimplemented: Wire::decode(bytes)?,
unrouted: Wire::decode(bytes)?,
told: Wire::decode(bytes)?,
})
}
}
pub fn values_of(fields: &[Field], bytes: &[u8]) -> Result<Vec<i64>, WireError> {
let mut rest = bytes;
let mut values = Vec::with_capacity(fields.len());
for field in fields {
let width = checked_width(field.width)?;
let raw = take(&mut rest, usize::from(width))?;
let mut word = [0_u8; 8];
word[..raw.len()].copy_from_slice(raw);
let shift = 64 - 8 * u32::from(width);
let value = u64::from_le_bytes(word).cast_signed();
if !field.signed && value < 0 {
return Err(WireError::Invalid {
what: "unsigned value an i64 holds",
value: value.cast_unsigned(),
});
}
values.push(if field.signed {
(value << shift) >> shift
} else {
value
});
}
if !rest.is_empty() {
return Err(WireError::Trailing(rest.len()));
}
Ok(values)
}
fn fits(field: &Field, value: i64) -> Result<bool, WireError> {
let bits = 8 * u32::from(checked_width(field.width)?);
Ok(match (bits, field.signed) {
(64, true) => true,
(64, false) => value >= 0,
(bits, true) => (-(1_i64 << (bits - 1))..(1_i64 << (bits - 1))).contains(&value),
(bits, false) => (0..(1_i64 << bits)).contains(&value),
})
}
pub fn bytes_of(fields: &[Field], values: &[i64]) -> Result<Vec<u8>, LinkError> {
if fields.len() != values.len() {
return Err(LinkError::Misfit(format!(
"has {} values for {} fields",
values.len(),
fields.len()
)));
}
let mut bytes = Vec::new();
for (field, &value) in fields.iter().zip(values) {
let width = checked_width(field.width)?;
if !fits(field, value)? {
return Err(LinkError::Misfit(format!(
"puts {value} in {}, which is {} bytes {}",
field.name,
width,
if field.signed { "signed" } else { "unsigned" }
)));
}
bytes.extend_from_slice(&value.to_le_bytes()[..usize::from(width)]);
}
Ok(bytes)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Open {
pub version: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Manifest {
pub version: u16,
pub machine: String,
pub runtime: String,
pub snapshot: Option<[u8; 32]>,
pub inputs: Vec<Field>,
pub rest: Vec<i64>,
pub outputs: Vec<Field>,
pub ports: Vec<String>,
pub conditions: Vec<Field>,
pub instances: Vec<Instance>,
pub chips: Vec<String>,
pub lines: u16,
}
impl Manifest {
#[must_use]
pub fn new(machine: String, runtime: String) -> Self {
Self {
version: VERSION,
machine,
runtime,
snapshot: None,
inputs: Vec::new(),
rest: Vec::new(),
outputs: Vec::new(),
ports: Vec::new(),
conditions: Vec::new(),
instances: Vec::new(),
chips: Vec::new(),
lines: 0,
}
}
}
impl Wire for Manifest {
fn encode(&self, out: &mut Vec<u8>) {
self.version.encode(out);
self.machine.encode(out);
self.runtime.encode(out);
self.snapshot.encode(out);
self.inputs.encode(out);
self.rest.encode(out);
self.outputs.encode(out);
self.ports.encode(out);
self.conditions.encode(out);
self.instances.encode(out);
self.chips.encode(out);
self.lines.encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
Ok(Self {
version: Wire::decode(bytes)?,
machine: Wire::decode(bytes)?,
runtime: Wire::decode(bytes)?,
snapshot: Wire::decode(bytes)?,
inputs: Wire::decode(bytes)?,
rest: Wire::decode(bytes)?,
outputs: Wire::decode(bytes)?,
ports: Wire::decode(bytes)?,
conditions: Wire::decode(bytes)?,
instances: Wire::decode(bytes)?,
chips: Wire::decode(bytes)?,
lines: Wire::decode(bytes)?,
})
.and_then(|manifest: Self| {
if manifest.rest.len() != manifest.inputs.len() {
return Err(WireError::Invalid {
what: "count of rest values for the inputs",
value: manifest.rest.len() as u64,
});
}
for (index, (input, &rest)) in manifest.inputs.iter().zip(&manifest.rest).enumerate() {
if !fits(input, rest)? {
return Err(WireError::Invalid {
what: "input whose rest value fits it",
value: index as u64,
});
}
}
let named = |name: &str| {
manifest
.inputs
.iter()
.chain(&manifest.outputs)
.any(|field| field.name == name)
};
if let Some(index) = manifest
.conditions
.iter()
.position(|condition| named(&condition.name))
{
return Err(WireError::Invalid {
what: "condition named apart from every input and output",
value: index as u64,
});
}
Ok(manifest)
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Gap {
Unmapped {
address: u64,
program_counter: u64,
},
Refused {
address: u64,
why: String,
},
Unimplemented {
instance: String,
register: String,
address: u64,
program_counter: u64,
},
Unrouted {
instance: String,
line: u16,
},
}
impl Wire for Gap {
fn encode(&self, out: &mut Vec<u8>) {
match self {
Self::Unmapped {
address,
program_counter,
} => {
0_u8.encode(out);
address.encode(out);
program_counter.encode(out);
}
Self::Refused { address, why } => {
1_u8.encode(out);
address.encode(out);
why.encode(out);
}
Self::Unimplemented {
instance,
register,
address,
program_counter,
} => {
2_u8.encode(out);
instance.encode(out);
register.encode(out);
address.encode(out);
program_counter.encode(out);
}
Self::Unrouted { instance, line } => {
3_u8.encode(out);
instance.encode(out);
line.encode(out);
}
}
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
match u8::decode(bytes)? {
0 => Ok(Self::Unmapped {
address: Wire::decode(bytes)?,
program_counter: Wire::decode(bytes)?,
}),
1 => Ok(Self::Refused {
address: Wire::decode(bytes)?,
why: Wire::decode(bytes)?,
}),
2 => Ok(Self::Unimplemented {
instance: Wire::decode(bytes)?,
register: Wire::decode(bytes)?,
address: Wire::decode(bytes)?,
program_counter: Wire::decode(bytes)?,
}),
3 => Ok(Self::Unrouted {
instance: Wire::decode(bytes)?,
line: Wire::decode(bytes)?,
}),
code => Err(WireError::Invalid {
what: "gap",
value: u64::from(code),
}),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct Told {
pub instance: String,
pub register: String,
pub reason: String,
pub at: VirtualNanos,
}
impl Told {
#[must_use]
pub fn new(instance: String, register: String, reason: String, at: VirtualNanos) -> Self {
Self {
instance,
register,
reason,
at,
}
}
}
impl Wire for Told {
fn encode(&self, out: &mut Vec<u8>) {
self.instance.encode(out);
self.register.encode(out);
self.reason.encode(out);
self.at.encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
Ok(Self {
instance: Wire::decode(bytes)?,
register: Wire::decode(bytes)?,
reason: Wire::decode(bytes)?,
at: Wire::decode(bytes)?,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Set {
pub index: u16,
pub value: i64,
}
impl Wire for Set {
fn encode(&self, out: &mut Vec<u8>) {
self.index.encode(out);
self.value.encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
Ok(Self {
index: Wire::decode(bytes)?,
value: Wire::decode(bytes)?,
})
}
}
impl Wire for AccessKind {
fn encode(&self, out: &mut Vec<u8>) {
match self {
Self::Read => 0_u8,
Self::Write => 1,
}
.encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
match u8::decode(bytes)? {
0 => Ok(Self::Read),
1 => Ok(Self::Write),
code => Err(WireError::Invalid {
what: "access kind",
value: u64::from(code),
}),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Instance {
pub name: String,
pub registers: Vec<String>,
}
impl Instance {
#[must_use]
pub fn new(name: String, registers: Vec<String>) -> Self {
Self { name, registers }
}
}
impl Wire for Instance {
fn encode(&self, out: &mut Vec<u8>) {
self.name.encode(out);
self.registers.encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
Ok(Self {
name: Wire::decode(bytes)?,
registers: Wire::decode(bytes)?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Armed {
pub id: u32,
pub links: Vec<Link>,
pub fault: Fault,
}
impl Armed {
#[must_use]
pub fn new(id: u32, links: Vec<Link>, fault: Fault) -> Self {
Self { id, links, fault }
}
}
impl Wire for Armed {
fn encode(&self, out: &mut Vec<u8>) {
self.id.encode(out);
self.links.encode(out);
self.fault.encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
let armed = Self {
id: Wire::decode(bytes)?,
links: Wire::decode(bytes)?,
fault: Wire::decode(bytes)?,
};
if armed.links.is_empty() {
return Err(WireError::Invalid {
what: "trigger with a link",
value: 0,
});
}
Ok(armed)
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum Link {
After(VirtualNanos),
Access {
instance: u16,
register: u16,
kind: AccessKind,
mask: u64,
value: u64,
count: u32,
},
Transaction {
chip: u16,
count: u32,
},
Execute {
code: Code,
count: u32,
},
Write {
address: u64,
length: NonZeroU64,
count: u32,
},
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum Code {
Address(u64),
Symbol(String),
}
impl Wire for Code {
fn encode(&self, out: &mut Vec<u8>) {
match self {
Self::Address(address) => {
0_u8.encode(out);
address.encode(out);
}
Self::Symbol(name) => {
1_u8.encode(out);
name.encode(out);
}
}
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
match u8::decode(bytes)? {
0 => Ok(Self::Address(Wire::decode(bytes)?)),
1 => Ok(Self::Symbol(Wire::decode(bytes)?)),
code => Err(WireError::Invalid {
what: "code",
value: u64::from(code),
}),
}
}
}
fn count(bytes: &mut &[u8]) -> Result<u32, WireError> {
match u32::decode(bytes)? {
0 => Err(WireError::Invalid {
what: "count of at least one",
value: 0,
}),
count => Ok(count),
}
}
impl Wire for Link {
fn encode(&self, out: &mut Vec<u8>) {
match self {
Self::After(length) => {
0_u8.encode(out);
length.encode(out);
}
Self::Access {
instance,
register,
kind,
mask,
value,
count,
} => {
1_u8.encode(out);
instance.encode(out);
register.encode(out);
kind.encode(out);
mask.encode(out);
value.encode(out);
count.encode(out);
}
Self::Transaction { chip, count } => {
2_u8.encode(out);
chip.encode(out);
count.encode(out);
}
Self::Execute { code, count } => {
3_u8.encode(out);
code.encode(out);
count.encode(out);
}
Self::Write {
address,
length,
count,
} => {
4_u8.encode(out);
address.encode(out);
length.get().encode(out);
count.encode(out);
}
}
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
match u8::decode(bytes)? {
0 => Ok(Self::After(Wire::decode(bytes)?)),
1 => Ok(Self::Access {
instance: Wire::decode(bytes)?,
register: Wire::decode(bytes)?,
kind: Wire::decode(bytes)?,
mask: Wire::decode(bytes)?,
value: Wire::decode(bytes)?,
count: count(bytes)?,
}),
2 => Ok(Self::Transaction {
chip: Wire::decode(bytes)?,
count: count(bytes)?,
}),
3 => Ok(Self::Execute {
code: Wire::decode(bytes)?,
count: count(bytes)?,
}),
4 => Ok(Self::Write {
address: Wire::decode(bytes)?,
length: NonZeroU64::new(Wire::decode(bytes)?).ok_or(WireError::Invalid {
what: "length of at least one",
value: 0,
})?,
count: count(bytes)?,
}),
code => Err(WireError::Invalid {
what: "link",
value: u64::from(code),
}),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum Fault {
Conditions(Vec<Set>),
Flip {
chip: u16,
mask: u64,
},
Drop {
chip: u16,
},
Interrupt {
line: u16,
},
Reset,
BusError {
instance: u16,
register: u16,
},
}
impl Wire for Fault {
fn encode(&self, out: &mut Vec<u8>) {
match self {
Self::Conditions(sets) => {
0_u8.encode(out);
sets.encode(out);
}
Self::Flip { chip, mask } => {
1_u8.encode(out);
chip.encode(out);
mask.encode(out);
}
Self::Drop { chip } => {
2_u8.encode(out);
chip.encode(out);
}
Self::Interrupt { line } => {
3_u8.encode(out);
line.encode(out);
}
Self::Reset => 4_u8.encode(out),
Self::BusError { instance, register } => {
5_u8.encode(out);
instance.encode(out);
register.encode(out);
}
}
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
match u8::decode(bytes)? {
0 => Ok(Self::Conditions(Wire::decode(bytes)?)),
1 => Ok(Self::Flip {
chip: Wire::decode(bytes)?,
mask: Wire::decode(bytes)?,
}),
2 => Ok(Self::Drop {
chip: Wire::decode(bytes)?,
}),
3 => Ok(Self::Interrupt {
line: Wire::decode(bytes)?,
}),
4 => Ok(Self::Reset),
5 => Ok(Self::BusError {
instance: Wire::decode(bytes)?,
register: Wire::decode(bytes)?,
}),
code => Err(WireError::Invalid {
what: "fault",
value: u64::from(code),
}),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Fired {
pub id: u32,
pub at: VirtualNanos,
pub instruction_count: u64,
pub program_counter: Option<u64>,
pub conditions: Vec<Set>,
}
impl Fired {
#[must_use]
pub fn new(id: u32, at: VirtualNanos, instruction_count: u64) -> Self {
Self {
id,
at,
instruction_count,
program_counter: None,
conditions: Vec::new(),
}
}
}
impl Wire for Fired {
fn encode(&self, out: &mut Vec<u8>) {
self.id.encode(out);
self.at.encode(out);
self.instruction_count.encode(out);
self.program_counter.encode(out);
self.conditions.encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
Ok(Self {
id: Wire::decode(bytes)?,
at: Wire::decode(bytes)?,
instruction_count: Wire::decode(bytes)?,
program_counter: Wire::decode(bytes)?,
conditions: Wire::decode(bytes)?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Advance {
pub until: VirtualNanos,
pub inputs: Vec<Set>,
pub ports: Vec<Vec<u8>>,
pub conditions: Vec<Set>,
pub armed: Vec<Armed>,
}
impl Advance {
#[must_use]
pub fn new(until: VirtualNanos) -> Self {
Self {
until,
inputs: Vec::new(),
ports: Vec::new(),
conditions: Vec::new(),
armed: Vec::new(),
}
}
}
impl Wire for Advance {
fn encode(&self, out: &mut Vec<u8>) {
self.until.encode(out);
self.inputs.encode(out);
self.ports.encode(out);
self.conditions.encode(out);
self.armed.encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
Ok(Self {
until: Wire::decode(bytes)?,
inputs: Wire::decode(bytes)?,
ports: Wire::decode(bytes)?,
conditions: Wire::decode(bytes)?,
armed: Wire::decode(bytes)?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Reached {
pub stop: Stop,
pub outputs: Vec<i64>,
pub ports: Vec<Vec<u8>>,
pub resets: Vec<VirtualNanos>,
pub gaps: Vec<Gap>,
pub told: Vec<Told>,
pub fired: Vec<Fired>,
}
impl Reached {
#[must_use]
pub fn new(stop: Stop) -> Self {
Self {
stop,
outputs: Vec::new(),
ports: Vec::new(),
resets: Vec::new(),
gaps: Vec::new(),
told: Vec::new(),
fired: Vec::new(),
}
}
}
impl Wire for Reached {
fn encode(&self, out: &mut Vec<u8>) {
self.stop.encode(out);
self.outputs.encode(out);
self.ports.encode(out);
self.resets.encode(out);
self.gaps.encode(out);
self.told.encode(out);
self.fired.encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
Ok(Self {
stop: Wire::decode(bytes)?,
outputs: Wire::decode(bytes)?,
ports: Wire::decode(bytes)?,
resets: Wire::decode(bytes)?,
gaps: Wire::decode(bytes)?,
told: Wire::decode(bytes)?,
fired: Wire::decode(bytes)?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Snapshot {
pub path: String,
}
impl Snapshot {
#[must_use]
pub fn new(path: String) -> Self {
Self { path }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Snapshotted {
pub hash: [u8; 32],
}
impl Snapshotted {
#[must_use]
pub fn new(hash: [u8; 32]) -> Self {
Self { hash }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Refused {
pub why: String,
}
impl Refused {
#[must_use]
pub fn new(why: String) -> Self {
Self { why }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Frame {
Open(Open),
Manifest(Manifest),
Advance(Advance),
Reached(Reached),
Shutdown,
Report(Report),
Snapshot(Snapshot),
Snapshotted(Snapshotted),
Refused(Refused),
}
impl Frame {
fn kind(&self) -> &'static str {
match self {
Self::Open(_) => "Open",
Self::Manifest(_) => "Manifest",
Self::Advance(_) => "Advance",
Self::Reached(_) => "Reached",
Self::Shutdown => "Shutdown",
Self::Report(_) => "Report",
Self::Snapshot(_) => "Snapshot",
Self::Snapshotted(_) => "Snapshotted",
Self::Refused(_) => "Refused",
}
}
pub fn write_to(&self, output: &mut impl Write) -> io::Result<()> {
let mut body = Vec::new();
match self {
Self::Open(open) => {
KIND_OPEN.encode(&mut body);
open.version.encode(&mut body);
}
Self::Manifest(manifest) => {
KIND_MANIFEST.encode(&mut body);
manifest.encode(&mut body);
}
Self::Advance(advance) => {
KIND_ADVANCE.encode(&mut body);
advance.encode(&mut body);
}
Self::Reached(reached) => {
KIND_REACHED.encode(&mut body);
reached.encode(&mut body);
}
Self::Shutdown => KIND_SHUTDOWN.encode(&mut body),
Self::Report(report) => {
KIND_REPORT.encode(&mut body);
report.encode(&mut body);
}
Self::Snapshot(snapshot) => {
KIND_SNAPSHOT.encode(&mut body);
snapshot.path.encode(&mut body);
}
Self::Snapshotted(snapshotted) => {
KIND_SNAPSHOTTED.encode(&mut body);
snapshotted.hash.encode(&mut body);
}
Self::Refused(refused) => {
KIND_REFUSED.encode(&mut body);
refused.why.encode(&mut body);
}
}
let length = u32::try_from(body.len()).expect("a frame is shorter than a word counts");
output.write_all(&length.to_le_bytes())?;
output.write_all(&body)?;
output.flush()
}
pub fn read_from(input: &mut impl Read) -> Result<Self, LinkError> {
let mut length = [0; 4];
match input.read_exact(&mut length) {
Ok(()) => {}
Err(error) if error.kind() == io::ErrorKind::UnexpectedEof => {
return Err(LinkError::Closed);
}
Err(error) => return Err(LinkError::Io(error)),
}
let length = usize::try_from(u32::from_le_bytes(length)).expect("a word fits");
if length > LARGEST_FRAME {
return Err(LinkError::Wire(WireError::Invalid {
what: "frame length",
value: length as u64,
}));
}
let mut body = vec![0; length];
input.read_exact(&mut body)?;
let mut bytes = &body[..];
let frame = match u8::decode(&mut bytes)? {
KIND_OPEN => Self::Open(Open {
version: Wire::decode(&mut bytes)?,
}),
KIND_MANIFEST => {
let mut peek = bytes;
let version = u16::decode(&mut peek)?;
if version != VERSION {
return Err(LinkError::Version { theirs: version });
}
Self::Manifest(Wire::decode(&mut bytes)?)
}
KIND_ADVANCE => Self::Advance(Wire::decode(&mut bytes)?),
KIND_REACHED => Self::Reached(Wire::decode(&mut bytes)?),
KIND_SHUTDOWN => Self::Shutdown,
KIND_REPORT => Self::Report(Wire::decode(&mut bytes)?),
KIND_SNAPSHOT => Self::Snapshot(Snapshot {
path: Wire::decode(&mut bytes)?,
}),
KIND_SNAPSHOTTED => Self::Snapshotted(Snapshotted {
hash: Wire::decode(&mut bytes)?,
}),
KIND_REFUSED => Self::Refused(Refused {
why: Wire::decode(&mut bytes)?,
}),
kind => {
return Err(LinkError::Wire(WireError::Invalid {
what: "frame kind",
value: u64::from(kind),
}));
}
};
if !bytes.is_empty() {
return Err(LinkError::Wire(WireError::Trailing(bytes.len())));
}
Ok(frame)
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum LinkError {
#[error("the other side of the link closed it")]
Closed,
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Wire(#[from] WireError),
#[error("the link carried {got} where {wanted} was expected")]
Unexpected {
got: &'static str,
wanted: &'static str,
},
#[error(
"the other side speaks the step protocol version {theirs} and this side speaks {VERSION}"
)]
Version {
theirs: u16,
},
#[error("the engine {0}")]
Misfit(String),
#[error("the board {0}")]
Board(String),
#[error("starting {binary}: {source}", binary = binary.display())]
Spawn {
binary: PathBuf,
#[source]
source: io::Error,
},
#[error("the target exited with {0}")]
Exited(ExitStatus),
}
#[derive(Debug)]
pub struct Target {
child: Child,
input: ChildStdout,
output: ChildStdin,
manifest: Manifest,
}
impl Target {
pub fn spawn(
binary: &Path,
machine: &str,
firmware: &Path,
arguments: &[impl AsRef<OsStr>],
) -> Result<Self, LinkError> {
let mut child = Command::new(binary)
.args(["run", "--link", "--machine", machine])
.args(arguments)
.arg(firmware)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.map_err(|source| LinkError::Spawn {
binary: binary.to_path_buf(),
source,
})?;
let mut output = child.stdin.take().expect("piped");
let mut input = child.stdout.take().expect("piped");
let opened = Frame::Open(Open { version: VERSION })
.write_to(&mut output)
.map_err(LinkError::from)
.and_then(|()| match Frame::read_from(&mut input)? {
Frame::Manifest(manifest) => Ok(manifest),
other => Err(LinkError::Unexpected {
got: other.kind(),
wanted: "Manifest",
}),
});
let manifest = match opened {
Ok(manifest) => manifest,
Err(error) => {
child.kill().ok();
child.wait().ok();
return Err(error);
}
};
Ok(Self {
child,
input,
output,
manifest,
})
}
#[must_use]
pub fn manifest(&self) -> &Manifest {
&self.manifest
}
pub fn advance(&mut self, advance: Advance) -> Result<Reached, LinkError> {
Frame::Advance(advance).write_to(&mut self.output)?;
match Frame::read_from(&mut self.input)? {
Frame::Reached(reached) => Ok(reached),
other => Err(LinkError::Unexpected {
got: other.kind(),
wanted: "Reached",
}),
}
}
pub fn snapshot(&mut self, path: &Path) -> Result<Result<Snapshotted, Refused>, LinkError> {
let path = path.to_str().ok_or_else(|| {
LinkError::Io(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"{} isn't text, which a frame carries a path as",
path.display()
),
))
})?;
Frame::Snapshot(Snapshot {
path: path.to_owned(),
})
.write_to(&mut self.output)?;
match Frame::read_from(&mut self.input)? {
Frame::Snapshotted(snapshotted) => Ok(Ok(snapshotted)),
Frame::Refused(refused) => Ok(Err(refused)),
other => Err(LinkError::Unexpected {
got: other.kind(),
wanted: "Snapshotted or Refused",
}),
}
}
pub fn shutdown(mut self) -> Result<Report, LinkError> {
Frame::Shutdown.write_to(&mut self.output)?;
let report = match Frame::read_from(&mut self.input)? {
Frame::Report(report) => report,
other => {
return Err(LinkError::Unexpected {
got: other.kind(),
wanted: "Report",
});
}
};
let status = self.child.wait()?;
if !status.success() {
return Err(LinkError::Exited(status));
}
Ok(report)
}
}
impl Drop for Target {
fn drop(&mut self) {
if matches!(self.child.try_wait(), Ok(None)) {
self.child.kill().ok();
self.child.wait().ok();
}
}
}
#[derive(Debug)]
pub struct Served<R: Read, W: Write> {
input: R,
output: W,
}
impl Served<io::Stdin, io::Stdout> {
#[must_use]
pub fn stdio() -> Self {
Self {
input: io::stdin(),
output: io::stdout(),
}
}
}
impl<R: Read, W: Write> Served<R, W> {
pub fn over(input: R, output: W) -> Self {
Self { input, output }
}
pub fn open(&mut self, manifest: &Manifest) -> Result<(), LinkError> {
let open = match Frame::read_from(&mut self.input)? {
Frame::Open(open) => open,
other => {
return Err(LinkError::Unexpected {
got: other.kind(),
wanted: "Open",
});
}
};
Frame::Manifest(manifest.clone()).write_to(&mut self.output)?;
if open.version == VERSION {
Ok(())
} else {
Err(LinkError::Version {
theirs: open.version,
})
}
}
pub fn receive(&mut self) -> Result<Frame, LinkError> {
match Frame::read_from(&mut self.input)? {
frame @ (Frame::Advance(_) | Frame::Shutdown | Frame::Snapshot(_)) => Ok(frame),
other => Err(LinkError::Unexpected {
got: other.kind(),
wanted: "Advance, Snapshot or Shutdown",
}),
}
}
pub fn snapshotted(&mut self, hash: [u8; 32]) -> Result<(), LinkError> {
Ok(Frame::Snapshotted(Snapshotted { hash }).write_to(&mut self.output)?)
}
pub fn refused(&mut self, why: String) -> Result<(), LinkError> {
Ok(Frame::Refused(Refused { why }).write_to(&mut self.output)?)
}
pub fn reached(&mut self, reached: Reached) -> Result<(), LinkError> {
Ok(Frame::Reached(reached).write_to(&mut self.output)?)
}
pub fn report(&mut self, report: Report) -> Result<(), LinkError> {
Ok(Frame::Report(report).write_to(&mut self.output)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn round_trip(frame: &Frame) -> Frame {
let mut bytes = Vec::new();
frame.write_to(&mut bytes).unwrap();
Frame::read_from(&mut &bytes[..]).unwrap()
}
fn armed() -> Vec<Armed> {
vec![
Armed {
id: 1,
links: vec![
Link::Access {
instance: 0,
register: 1,
kind: AccessKind::Write,
mask: 0x1,
value: 0x1,
count: 2,
},
Link::Transaction { chip: 0, count: 3 },
Link::After(VirtualNanos(250_000)),
],
fault: Fault::Conditions(vec![Set { index: 0, value: 1 }]),
},
Armed {
id: 2,
links: vec![Link::After(VirtualNanos(0))],
fault: Fault::Flip {
chip: 0,
mask: 0x80,
},
},
Armed {
id: 3,
links: vec![Link::Transaction { chip: 0, count: 1 }],
fault: Fault::Drop { chip: 0 },
},
Armed {
id: 4,
links: vec![Link::After(VirtualNanos(7))],
fault: Fault::Interrupt { line: 90 },
},
Armed {
id: 5,
links: vec![Link::After(VirtualNanos(8))],
fault: Fault::Reset,
},
Armed {
id: 6,
links: vec![
Link::Execute {
code: Code::Symbol("_ZN4Main4loopEv".to_owned()),
count: 3,
},
Link::Write {
address: 0x1000,
length: NonZeroU64::new(4).unwrap(),
count: 1,
},
],
fault: Fault::BusError {
instance: 1,
register: 0,
},
},
]
}
#[test]
fn every_frame_reads_back_as_it_was_written() {
let stop = Stop {
at: VirtualNanos(5_000_000),
instruction_count: 1234,
reason: StopReason::Idle,
};
let frames = [
Frame::Open(Open { version: VERSION }),
Frame::Manifest(Manifest {
version: VERSION,
machine: "bench".to_owned(),
runtime: "0.1.0".to_owned(),
snapshot: Some([0xab; 32]),
inputs: vec![
Field::new("rate.x", "mdeg/s", 4, true),
Field::new("pressure", "Pa", 4, false),
],
rest: vec![0, 101_325],
outputs: vec![
Field::new("valve", "", 1, false),
Field::new("heater.duty", "1e-3", 2, false),
],
ports: vec!["wire1".to_owned()],
conditions: vec![
Field::new("gauge.silent", "flag", 1, false),
Field::new("gauge.pressure.bias", "Pa", 4, true),
],
instances: vec![Instance {
name: "TIMER".to_owned(),
registers: vec!["CONTROL".to_owned(), "COUNT".to_owned()],
}],
chips: vec!["gauge".to_owned()],
lines: 91,
}),
Frame::Advance(Advance {
until: VirtualNanos(5_000_000),
inputs: Vec::new(),
ports: vec![Vec::new()],
conditions: Vec::new(),
armed: Vec::new(),
}),
Frame::Snapshot(Snapshot {
path: "snapshots/hovering".to_owned(),
}),
Frame::Snapshotted(Snapshotted { hash: [0x5c; 32] }),
Frame::Refused(Refused {
why: "a model asked for a reset".to_owned(),
}),
Frame::Advance(Advance {
until: VirtualNanos(10_000_000),
inputs: vec![Set {
index: 1,
value: 90_000,
}],
ports: vec![b"hello".to_vec()],
conditions: vec![Set { index: 0, value: 1 }],
armed: armed(),
}),
Frame::Reached(Reached {
stop,
outputs: vec![1_500_000, 2_500_000, 500],
ports: vec![vec![0xfd, 0x09]],
resets: vec![VirtualNanos(4_000_000)],
gaps: vec![
Gap::Unmapped {
address: 0x1000,
program_counter: 0x20,
},
Gap::Refused {
address: 0x2000,
why: "gated".to_owned(),
},
Gap::Unimplemented {
instance: "TIMER".to_owned(),
register: "CONTROL".to_owned(),
address: 0x3000,
program_counter: 0x30,
},
Gap::Unrouted {
instance: "TIMER".to_owned(),
line: 7,
},
],
told: vec![Told {
instance: "TIMER".to_owned(),
register: "CONTROL".to_owned(),
reason: "the model says no".to_owned(),
at: VirtualNanos(4_500_000),
}],
fired: vec![
Fired {
id: 1,
at: VirtualNanos(4_750_000),
instruction_count: 1_187_500,
program_counter: Some(0x0800_1234),
conditions: vec![Set { index: 0, value: 1 }],
},
Fired {
id: 4,
at: VirtualNanos(4_800_000),
instruction_count: 1_200_000,
program_counter: None,
conditions: Vec::new(),
},
],
}),
Frame::Shutdown,
Frame::Report(Report {
resets: vec![VirtualNanos(7)],
unmapped: 1,
refused: 2,
unimplemented: 3,
unrouted: 4,
told: 5,
}),
];
for frame in &frames {
assert_eq!(&round_trip(frame), frame);
}
}
#[test]
fn a_short_frame_an_unknown_kind_and_a_trailing_byte_are_refused_and_a_closed_link_is_named() {
assert!(matches!(
Frame::read_from(&mut &[][..]),
Err(LinkError::Closed)
));
let short = [3u8, 0, 0, 0, KIND_ADVANCE, 1, 2];
assert!(matches!(
Frame::read_from(&mut &short[..]),
Err(LinkError::Wire(WireError::Short { .. }))
));
let unknown = [1u8, 0, 0, 0, 10];
assert!(matches!(
Frame::read_from(&mut &unknown[..]),
Err(LinkError::Wire(WireError::Invalid {
what: "frame kind",
value: 10
}))
));
let trailing = [2u8, 0, 0, 0, KIND_SHUTDOWN, 0];
assert!(matches!(
Frame::read_from(&mut &trailing[..]),
Err(LinkError::Wire(WireError::Trailing(1)))
));
}
#[test]
fn open_is_kind_six_and_its_version_and_the_manifest_starts_with_its_version() {
let mut bytes = Vec::new();
Frame::Open(Open { version: 0x0102 })
.write_to(&mut bytes)
.unwrap();
assert_eq!(
bytes,
[3, 0, 0, 0, 6, 0x02, 0x01],
"frozen for every version"
);
let mut manifest = Vec::new();
Frame::Manifest(Manifest {
version: VERSION,
machine: String::new(),
runtime: String::new(),
snapshot: None,
inputs: Vec::new(),
rest: Vec::new(),
outputs: Vec::new(),
ports: Vec::new(),
conditions: Vec::new(),
instances: Vec::new(),
chips: Vec::new(),
lines: 0,
})
.write_to(&mut manifest)
.unwrap();
assert_eq!(manifest[4], KIND_MANIFEST);
assert_eq!(
&manifest[5..7],
VERSION.to_le_bytes(),
"frozen as the first field"
);
}
#[test]
fn the_target_answers_an_engine_of_another_version_with_its_manifest_and_then_refuses() {
let mut from_engine = Vec::new();
Frame::Open(Open {
version: VERSION + 1,
})
.write_to(&mut from_engine)
.unwrap();
let manifest = Manifest {
version: VERSION,
machine: "bench".to_owned(),
runtime: "0.1.0".to_owned(),
snapshot: None,
inputs: Vec::new(),
rest: Vec::new(),
outputs: Vec::new(),
ports: Vec::new(),
conditions: Vec::new(),
instances: Vec::new(),
chips: Vec::new(),
lines: 0,
};
let mut served = Served::over(&from_engine[..], Vec::new());
assert!(matches!(
served.open(&manifest),
Err(LinkError::Version { theirs }) if theirs == VERSION + 1
));
assert_eq!(
Frame::read_from(&mut &served.output[..]).unwrap(),
Frame::Manifest(manifest),
"the engine learns which version the target speaks"
);
}
#[test]
fn a_manifest_whose_rest_values_do_not_match_its_inputs_is_refused() {
let manifest = Manifest {
version: VERSION,
machine: String::new(),
runtime: String::new(),
snapshot: None,
inputs: vec![Field::new("a", "", 4, true)],
rest: Vec::new(),
outputs: Vec::new(),
ports: Vec::new(),
conditions: Vec::new(),
instances: Vec::new(),
chips: Vec::new(),
lines: 0,
};
let mut bytes = Vec::new();
manifest.encode(&mut bytes);
assert!(Manifest::decode(&mut &bytes[..]).is_err());
}
#[test]
fn a_manifest_from_another_version_is_refused_by_its_first_field() {
let mut body = vec![KIND_MANIFEST];
(VERSION + 1).encode(&mut body);
body.extend_from_slice(&[0xff; 3]);
let mut bytes = u32::try_from(body.len()).unwrap().to_le_bytes().to_vec();
bytes.extend_from_slice(&body);
assert!(matches!(
Frame::read_from(&mut &bytes[..]),
Err(LinkError::Version { theirs }) if theirs == VERSION + 1
));
}
#[test]
fn values_cross_as_their_fields_lay_them_out_and_one_that_does_not_fit_is_refused() {
let fields = [
Field::new("a", "", 1, true),
Field::new("b", "", 2, false),
Field::new("c", "", 4, true),
Field::new("d", "", 8, false),
];
let values = [-3, 65_535, -2_000_000_000, 1 << 40];
let bytes = bytes_of(&fields, &values).unwrap();
assert_eq!(bytes.len(), 15);
assert_eq!(values_of(&fields, &bytes).unwrap(), values);
assert!(bytes_of(&fields[..1], &[128]).is_err(), "past an i8");
assert!(bytes_of(&fields[1..2], &[-1]).is_err(), "below a u16");
assert!(bytes_of(&fields[3..], &[-1]).is_err(), "below a u64");
assert!(
bytes_of(&fields, &[1]).is_err(),
"one value for four fields"
);
assert!(matches!(
values_of(&fields[..1], &[1, 2]),
Err(WireError::Trailing(1))
));
}
#[test]
fn text_that_is_not_utf8_and_a_gap_that_names_nothing_are_refused() {
let mut bytes = Vec::new();
vec![0xff_u8, 0xfe].encode(&mut bytes);
assert!(matches!(
String::decode(&mut &bytes[..]),
Err(WireError::Invalid {
what: "text valid past this byte",
value: 0
})
));
assert!(matches!(
Gap::decode(&mut &[9_u8][..]),
Err(WireError::Invalid {
what: "gap",
value: 9
})
));
let mut wide = Vec::new();
Field::new("a", "", 3, false).encode(&mut wide);
assert!(matches!(
Field::decode(&mut &wide[..]),
Err(WireError::Invalid {
what: "field width",
value: 3
})
));
}
#[test]
fn every_stop_reason_reads_back_and_a_code_past_the_last_is_refused() {
for reason in [StopReason::Deadline, StopReason::Idle, StopReason::Reset] {
let mut bytes = Vec::new();
reason.encode(&mut bytes);
assert_eq!(StopReason::decode(&mut &bytes[..]).unwrap(), reason);
}
assert!(matches!(
StopReason::decode(&mut &[3_u8][..]),
Err(WireError::Invalid {
what: "stop reason",
value: 3
})
));
}
fn le(value: u64, bytes: usize) -> Vec<u8> {
value.to_le_bytes()[..bytes].to_vec()
}
fn text(value: &str) -> Vec<u8> {
[le(value.len() as u64, 4), value.as_bytes().to_vec()].concat()
}
fn framed(body: &[Vec<u8>]) -> Vec<u8> {
let body = body.concat();
[le(body.len() as u64, 4), body].concat()
}
fn is_written_as(frame: &Frame, bytes: &[u8]) {
let mut written = Vec::new();
frame.write_to(&mut written).unwrap();
assert_eq!(written, bytes, "{frame:?} as it's written");
assert_eq!(
&Frame::read_from(&mut &bytes[..]).unwrap(),
frame,
"{frame:?} as it's read"
);
}
#[test]
fn the_manifest_and_report_are_the_bytes_the_protocol_names() {
let manifest = Frame::Manifest(Manifest {
version: 5,
machine: "m".to_owned(),
runtime: "r".to_owned(),
snapshot: Some([0x5c; 32]),
inputs: vec![Field::new("a", "u", 2, true)],
rest: vec![-1],
outputs: vec![Field::new("b", "", 1, false)],
ports: vec!["p".to_owned()],
conditions: vec![Field::new("c", "flag", 1, false)],
instances: vec![Instance {
name: "i".to_owned(),
registers: vec!["r".to_owned()],
}],
chips: vec!["k".to_owned()],
lines: 0x0201,
});
is_written_as(
&manifest,
&framed(&[
le(5, 1),
le(5, 2),
text("m"),
text("r"),
le(1, 1),
vec![0x5c; 32],
le(1, 4),
text("a"),
text("u"),
le(2, 1),
le(1, 1),
le(1, 4),
le(u64::MAX, 8),
le(1, 4),
text("b"),
text(""),
le(1, 1),
le(0, 1),
le(1, 4),
text("p"),
le(1, 4),
text("c"),
text("flag"),
le(1, 1),
le(0, 1),
le(1, 4),
text("i"),
le(1, 4),
text("r"),
le(1, 4),
text("k"),
le(0x0201, 2),
]),
);
let report = Frame::Report(Report {
resets: vec![VirtualNanos(5)],
unmapped: 1,
refused: 2,
unimplemented: 3,
unrouted: 4,
told: 5,
});
is_written_as(
&report,
&framed(&[
le(4, 1),
le(1, 4),
le(5, 8),
le(1, 8),
le(2, 8),
le(3, 8),
le(4, 8),
le(5, 8),
]),
);
}
#[test]
fn open_advance_and_shutdown_are_the_bytes_the_protocol_names() {
is_written_as(
&Frame::Open(Open { version: 0x0201 }),
&framed(&[le(6, 1), le(0x0201, 2)]),
);
let advance = Frame::Advance(Advance {
until: VirtualNanos(0x1122_3344_5566_7788),
inputs: vec![Set {
index: 0x0201,
value: -2,
}],
ports: vec![b"ab".to_vec()],
conditions: vec![Set {
index: 0x0403,
value: 1,
}],
armed: vec![
Armed {
id: 0x0a0b_0c0d,
links: vec![
Link::After(VirtualNanos(0x10)),
Link::Access {
instance: 0x0201,
register: 0x0403,
kind: AccessKind::Write,
mask: 0xf0,
value: 0x30,
count: 5,
},
Link::Transaction {
chip: 0x0605,
count: 6,
},
],
fault: Fault::Conditions(vec![Set {
index: 0x0403,
value: -1,
}]),
},
Armed {
id: 1,
links: vec![Link::After(VirtualNanos(1))],
fault: Fault::Flip {
chip: 2,
mask: 0x0102_0304,
},
},
Armed {
id: 2,
links: vec![Link::After(VirtualNanos(1))],
fault: Fault::Drop { chip: 3 },
},
Armed {
id: 3,
links: vec![Link::After(VirtualNanos(1))],
fault: Fault::Interrupt { line: 0x0807 },
},
Armed {
id: 4,
links: vec![Link::After(VirtualNanos(1))],
fault: Fault::Reset,
},
],
});
let after = |length: u64| [le(0, 1), le(length, 8)].concat();
is_written_as(
&advance,
&framed(&[
le(1, 1),
le(0x1122_3344_5566_7788, 8),
le(1, 4),
le(0x0201, 2),
le((-2_i64).cast_unsigned(), 8),
le(1, 4),
le(2, 4),
b"ab".to_vec(),
le(1, 4),
le(0x0403, 2),
le(1, 8),
le(5, 4),
le(0x0a0b_0c0d, 4),
le(3, 4),
after(0x10),
[
le(1, 1),
le(0x0201, 2),
le(0x0403, 2),
le(1, 1),
le(0xf0, 8),
le(0x30, 8),
le(5, 4),
]
.concat(),
[le(2, 1), le(0x0605, 2), le(6, 4)].concat(),
[le(0, 1), le(1, 4), le(0x0403, 2), le(u64::MAX, 8)].concat(),
[
le(1, 4),
le(1, 4),
after(1),
le(1, 1),
le(2, 2),
le(0x0102_0304, 8),
]
.concat(),
[le(2, 4), le(1, 4), after(1), le(2, 1), le(3, 2)].concat(),
[le(3, 4), le(1, 4), after(1), le(3, 1), le(0x0807, 2)].concat(),
[le(4, 4), le(1, 4), after(1), le(4, 1)].concat(),
]),
);
is_written_as(&Frame::Shutdown, &framed(&[le(3, 1)]));
}
#[test]
fn an_instruction_a_write_and_a_bus_error_are_the_bytes_the_protocol_names() {
let armed = Armed {
id: 5,
links: vec![
Link::Execute {
code: Code::Address(0x0800_1235),
count: 7,
},
Link::Execute {
code: Code::Symbol("f".to_owned()),
count: 1,
},
Link::Write {
address: 0x1010,
length: NonZeroU64::new(4).unwrap(),
count: 8,
},
],
fault: Fault::BusError {
instance: 0x0201,
register: 0x0403,
},
};
let mut written = Vec::new();
armed.encode(&mut written);
let bytes = [
le(5, 4),
le(3, 4),
[le(3, 1), le(0, 1), le(0x0800_1235, 8), le(7, 4)].concat(),
[le(3, 1), le(1, 1), text("f"), le(1, 4)].concat(),
[le(4, 1), le(0x1010, 8), le(4, 8), le(8, 4)].concat(),
[le(5, 1), le(0x0201, 2), le(0x0403, 2)].concat(),
]
.concat();
assert_eq!(written, bytes);
assert_eq!(Armed::decode(&mut &bytes[..]).unwrap(), armed);
}
#[test]
fn snapshot_snapshotted_and_refused_are_the_bytes_the_protocol_names() {
is_written_as(
&Frame::Snapshot(Snapshot {
path: "s".to_owned(),
}),
&framed(&[le(7, 1), text("s")]),
);
is_written_as(
&Frame::Snapshotted(Snapshotted { hash: [0xa7; 32] }),
&framed(&[le(8, 1), vec![0xa7; 32]]),
);
is_written_as(
&Frame::Refused(Refused {
why: "r".to_owned(),
}),
&framed(&[le(9, 1), text("r")]),
);
}
#[test]
fn reached_and_each_gap_tag_are_the_bytes_the_protocol_names() {
let reached = Frame::Reached(Reached {
stop: Stop {
at: VirtualNanos(10),
instruction_count: 11,
reason: StopReason::Reset,
},
outputs: vec![-1],
ports: vec![b"z".to_vec()],
resets: vec![VirtualNanos(7)],
gaps: vec![
Gap::Unmapped {
address: 0x1000,
program_counter: 0x20,
},
Gap::Refused {
address: 0x2000,
why: "g".to_owned(),
},
Gap::Unimplemented {
instance: "T".to_owned(),
register: "R".to_owned(),
address: 0x3000,
program_counter: 0x30,
},
Gap::Unrouted {
instance: "T".to_owned(),
line: 7,
},
],
told: vec![Told {
instance: "T".to_owned(),
register: "R".to_owned(),
reason: "why".to_owned(),
at: VirtualNanos(9),
}],
fired: vec![Fired {
id: 0x0a0b_0c0d,
at: VirtualNanos(12),
instruction_count: 13,
program_counter: Some(0x1000),
conditions: vec![Set { index: 1, value: 2 }],
}],
});
let stop = [le(10, 8), le(11, 8), le(2, 1)].concat();
let gaps = [
[le(0, 1), le(0x1000, 8), le(0x20, 8)].concat(),
[le(1, 1), le(0x2000, 8), text("g")].concat(),
[le(2, 1), text("T"), text("R"), le(0x3000, 8), le(0x30, 8)].concat(),
[le(3, 1), text("T"), le(7, 2)].concat(),
]
.concat();
is_written_as(
&reached,
&framed(&[
le(2, 1),
stop,
le(1, 4),
le(u64::MAX, 8),
le(1, 4),
le(1, 4),
b"z".to_vec(),
le(1, 4),
le(7, 8),
le(4, 4),
gaps,
le(1, 4),
text("T"),
text("R"),
text("why"),
le(9, 8),
le(1, 4),
le(0x0a0b_0c0d, 4),
le(12, 8),
le(13, 8),
le(1, 1),
le(0x1000, 8),
le(1, 4),
le(1, 2),
le(2, 8),
]),
);
}
#[test]
fn a_count_of_zero_a_trigger_with_no_link_a_write_of_nothing_and_an_unknown_link_or_fault_are_refused()
{
let armed = |links: Vec<u8>, fault: Vec<u8>| [le(7, 4), links, fault].concat();
let reset = le(4, 1);
let refused = |bytes: Vec<u8>| Armed::decode(&mut &bytes[..]).unwrap_err();
assert_eq!(
refused(armed(
[le(1, 4), le(2, 1), le(0, 2), le(0, 4)].concat(),
reset.clone()
)),
WireError::Invalid {
what: "count of at least one",
value: 0
}
);
assert_eq!(
refused(armed(le(0, 4), reset.clone())),
WireError::Invalid {
what: "trigger with a link",
value: 0
}
);
assert_eq!(
refused(armed([le(1, 4), le(5, 1)].concat(), reset.clone())),
WireError::Invalid {
what: "link",
value: 5
}
);
assert_eq!(
refused(armed([le(1, 4), le(0, 1), le(1, 8)].concat(), le(6, 1))),
WireError::Invalid {
what: "fault",
value: 6
}
);
assert_eq!(
refused(armed(
[le(1, 4), le(4, 1), le(0x10, 8), le(0, 8), le(1, 4)].concat(),
reset
)),
WireError::Invalid {
what: "length of at least one",
value: 0
}
);
}
#[test]
fn a_signed_one_or_two_byte_value_crosses_in_its_own_width() {
let mut bytes = Vec::new();
(-2_i8).encode(&mut bytes);
(-300_i16).encode(&mut bytes);
assert_eq!(bytes, [0xFE, 0xD4, 0xFE]);
let mut rest = bytes.as_slice();
assert_eq!(i8::decode(&mut rest), Ok(-2));
assert_eq!(i16::decode(&mut rest), Ok(-300));
assert!(rest.is_empty());
}
#[test]
fn a_frame_longer_than_a_mebibyte_is_refused_before_its_body_is_read() {
let length = u32::try_from(LARGEST_FRAME + 1).unwrap().to_le_bytes();
assert!(matches!(
Frame::read_from(&mut &length[..]),
Err(LinkError::Wire(WireError::Invalid {
what: "frame length",
..
}))
));
}
#[test]
fn a_condition_named_like_an_input_or_an_output_is_refused() {
for name in ["a", "b"] {
let manifest = Manifest {
version: VERSION,
machine: String::new(),
runtime: String::new(),
snapshot: None,
inputs: vec![Field::new("a", "", 1, false)],
rest: vec![0],
outputs: vec![Field::new("b", "", 1, false)],
ports: Vec::new(),
conditions: vec![
Field::new("c", "flag", 1, false),
Field::new(name, "flag", 1, false),
],
instances: Vec::new(),
chips: Vec::new(),
lines: 0,
};
let mut bytes = Vec::new();
manifest.encode(&mut bytes);
assert!(
matches!(
Manifest::decode(&mut &bytes[..]),
Err(WireError::Invalid {
what: "condition named apart from every input and output",
value: 1,
})
),
"a condition called {name}"
);
}
}
#[test]
fn a_manifest_whose_rest_value_does_not_fit_its_input_is_refused() {
let manifest = Manifest {
version: VERSION,
machine: String::new(),
runtime: String::new(),
snapshot: None,
inputs: vec![Field::new("a", "", 1, false)],
rest: vec![300],
outputs: Vec::new(),
ports: Vec::new(),
conditions: Vec::new(),
instances: Vec::new(),
chips: Vec::new(),
lines: 0,
};
let mut bytes = Vec::new();
manifest.encode(&mut bytes);
assert!(matches!(
Manifest::decode(&mut &bytes[..]),
Err(WireError::Invalid {
what: "input whose rest value fits it",
value: 0
})
));
}
#[test]
fn an_unsigned_eight_byte_value_past_what_an_i64_holds_is_refused_rather_than_turned_negative()
{
let field = [Field::new("wide", "", 8, false)];
assert!(matches!(
values_of(&field, &u64::MAX.to_le_bytes()),
Err(WireError::Invalid {
what: "unsigned value an i64 holds",
..
})
));
assert_eq!(
values_of(&field, &i64::MAX.to_le_bytes()).unwrap(),
[i64::MAX]
);
}
#[test]
fn the_served_side_takes_only_what_the_engine_may_send() {
let answers = [
Frame::Report(Report::default()),
Frame::Snapshotted(Snapshotted { hash: [1; 32] }),
Frame::Refused(Refused {
why: "no room".to_owned(),
}),
];
for answer in answers {
let mut bytes = Vec::new();
answer.write_to(&mut bytes).unwrap();
let mut served = Served::over(&bytes[..], Vec::new());
assert!(
matches!(
served.receive(),
Err(LinkError::Unexpected { got, .. }) if got == answer.kind()
),
"{}",
answer.kind()
);
}
}
}