#![cfg_attr(not(feature = "std"), no_std)]
#![deny(missing_docs, unsafe_op_in_unsafe_fn, rust_2018_idioms)]
#![forbid(clippy::dbg_macro, clippy::print_stdout)]
#[cfg(test)]
extern crate std;
#[cfg(feature = "crypto")]
pub mod crypto;
pub mod crc32c;
pub mod util;
pub use util::{ct_eq, decode_hex_32, HexDecodeError};
#[cfg(kani)]
pub mod proofs;
pub const MAGIC: [u8; 2] = [0x56, 0x41];
pub const VERSION: u8 = 0x02;
#[cfg(not(target_endian = "little"))]
compile_error!(
"VLP frame protocol requires little-endian host (see book/src/architecture/vlp-frame.md)"
);
pub const NONCE_TERMINAL: u64 = u64::MAX;
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum Status {
Ok = 0,
Degraded = 1,
Critical = 2,
Stall = 3,
}
impl Status {
pub fn try_from_u8(byte: u8) -> Result<Self, DecodeError> {
match byte {
0 => Ok(Status::Ok),
1 => Ok(Status::Degraded),
2 => Ok(Status::Critical),
3 => Ok(Status::Stall),
other => Err(DecodeError::BadStatus(other)),
}
}
}
#[non_exhaustive]
#[repr(C, align(8))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Frame {
pub magic: [u8; 2],
pub version: u8,
pub status: Status,
pub pid: u32,
pub timestamp: u64,
pub nonce: u64,
pub payload: u32,
}
const _: () = assert!(core::mem::size_of::<Frame>() == 32);
const _: () = assert!(core::mem::align_of::<Frame>() == 8);
const _: () = assert!(core::mem::offset_of!(Frame, magic) == 0);
const _: () = assert!(core::mem::offset_of!(Frame, version) == 2);
const _: () = assert!(core::mem::offset_of!(Frame, status) == 3);
const _: () = assert!(core::mem::offset_of!(Frame, pid) == 4);
const _: () = assert!(core::mem::offset_of!(Frame, timestamp) == 8);
const _: () = assert!(core::mem::offset_of!(Frame, nonce) == 16);
const _: () = assert!(core::mem::offset_of!(Frame, payload) == 24);
impl Frame {
pub const fn new(status: Status, pid: u32, timestamp: u64, nonce: u64, payload: u32) -> Frame {
Frame {
magic: MAGIC,
version: VERSION,
status,
pid,
timestamp,
nonce,
payload,
}
}
pub fn encode(&self, out: &mut [u8; 32]) {
out[0..2].copy_from_slice(&self.magic);
out[2] = self.version;
out[3] = self.status as u8;
out[4..8].copy_from_slice(&self.pid.to_le_bytes());
out[8..16].copy_from_slice(&self.timestamp.to_le_bytes());
out[16..24].copy_from_slice(&self.nonce.to_le_bytes());
out[24..28].copy_from_slice(&self.payload.to_le_bytes());
let crc = crc32c::compute(&out[0..28]);
out[28..32].copy_from_slice(&crc.to_le_bytes());
}
pub fn decode(bytes: &[u8; 32]) -> Result<Frame, DecodeError> {
let magic = [bytes[0], bytes[1]];
if magic != MAGIC {
return Err(DecodeError::BadMagic);
}
let version = bytes[2];
if version != VERSION {
return Err(DecodeError::BadVersion);
}
let stored_crc = u32::from_le_bytes([bytes[28], bytes[29], bytes[30], bytes[31]]);
let computed_crc = crc32c::compute(&bytes[0..28]);
if stored_crc != computed_crc {
return Err(DecodeError::BadCrc {
expected: computed_crc,
actual: stored_crc,
});
}
let status = Status::try_from_u8(bytes[3])?;
if status == Status::Stall {
return Err(DecodeError::StallOnWire);
}
let pid = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
let timestamp = u64::from_le_bytes([
bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
]);
let nonce = u64::from_le_bytes([
bytes[16], bytes[17], bytes[18], bytes[19], bytes[20], bytes[21], bytes[22], bytes[23],
]);
let payload = u32::from_le_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]);
if pid == 0 || pid == 1 {
return Err(DecodeError::BadPid(pid));
}
if timestamp == u64::MAX {
return Err(DecodeError::BadTimestamp(timestamp));
}
if nonce == NONCE_TERMINAL && status != Status::Critical {
return Err(DecodeError::BadNonce { nonce, status });
}
Ok(Frame {
magic,
version,
status,
pid,
timestamp,
nonce,
payload,
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DecodeError {
BadMagic,
BadVersion,
BadCrc {
expected: u32,
actual: u32,
},
BadStatus(u8),
StallOnWire,
BadPid(u32),
BadTimestamp(u64),
BadNonce {
nonce: u64,
status: Status,
},
}
impl core::fmt::Display for DecodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
DecodeError::BadMagic => f.write_str("varta-vlp: bad magic prefix"),
DecodeError::BadVersion => f.write_str("varta-vlp: bad version byte"),
DecodeError::BadCrc { expected, actual } => {
write!(
f,
"varta-vlp: bad CRC-32C trailer (expected {expected:#010x}, actual {actual:#010x})"
)
}
DecodeError::BadStatus(byte) => {
write!(f, "varta-vlp: bad status byte {byte:#04x}")
}
DecodeError::StallOnWire => {
f.write_str("varta-vlp: Status::Stall is observer-only and forbidden on the wire")
}
DecodeError::BadPid(pid) => {
write!(f, "varta-vlp: reserved pid {pid}")
}
DecodeError::BadTimestamp(ts) => {
write!(f, "varta-vlp: reserved timestamp sentinel {ts:#x}")
}
DecodeError::BadNonce { nonce, status } => {
write!(
f,
"varta-vlp: terminal nonce {nonce:#x} requires Status::Critical, got {status:?}"
)
}
}
}
}
impl core::error::Error for DecodeError {}