use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MarshalError {
ShortBuffer { expected: usize, got: usize },
InvalidEncoding,
}
impl fmt::Display for MarshalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ShortBuffer { expected, got } => {
write!(f, "Marshal source buffer too short: expected {expected} bytes, got {got}")
}
Self::InvalidEncoding => write!(f, "Marshal source bytes do not encode a valid value"),
}
}
}
impl std::error::Error for MarshalError {}
pub unsafe trait Marshal: Sized {
const PAYLOAD_BYTES: usize;
fn marshal(&self, dst: &mut [u8]);
fn unmarshal(src: &[u8]) -> Result<Self, MarshalError>;
}
macro_rules! impl_marshal_for_primitive {
($t:ty, $bytes:expr) => {
unsafe impl Marshal for $t {
const PAYLOAD_BYTES: usize = $bytes;
fn marshal(&self, dst: &mut [u8]) {
let bytes = self.to_le_bytes();
dst[..$bytes].copy_from_slice(&bytes);
}
fn unmarshal(src: &[u8]) -> Result<Self, MarshalError> {
if src.len() < $bytes {
return Err(MarshalError::ShortBuffer {
expected: $bytes,
got: src.len(),
});
}
let mut buf = [0u8; $bytes];
buf.copy_from_slice(&src[..$bytes]);
Ok(<$t>::from_le_bytes(buf))
}
}
};
}
impl_marshal_for_primitive!(u8, 1);
impl_marshal_for_primitive!(u16, 2);
impl_marshal_for_primitive!(u32, 4);
impl_marshal_for_primitive!(u64, 8);
impl_marshal_for_primitive!(u128, 16);
impl_marshal_for_primitive!(i8, 1);
impl_marshal_for_primitive!(i16, 2);
impl_marshal_for_primitive!(i32, 4);
impl_marshal_for_primitive!(i64, 8);
impl_marshal_for_primitive!(i128, 16);
impl_marshal_for_primitive!(f32, 4);
impl_marshal_for_primitive!(f64, 8);
unsafe impl Marshal for bool {
const PAYLOAD_BYTES: usize = 1;
fn marshal(&self, dst: &mut [u8]) {
dst[0] = u8::from(*self);
}
fn unmarshal(src: &[u8]) -> Result<Self, MarshalError> {
if src.is_empty() {
return Err(MarshalError::ShortBuffer { expected: 1, got: 0 });
}
match src[0] {
0 => Ok(false),
1 => Ok(true),
_ => Err(MarshalError::InvalidEncoding),
}
}
}
unsafe impl Marshal for () {
const PAYLOAD_BYTES: usize = 0;
fn marshal(&self, _dst: &mut [u8]) {}
fn unmarshal(_src: &[u8]) -> Result<Self, MarshalError> { Ok(()) }
}
unsafe impl<T: Marshal + Copy + Default, const N: usize> Marshal for [T; N] {
const PAYLOAD_BYTES: usize = T::PAYLOAD_BYTES * N;
fn marshal(&self, dst: &mut [u8]) {
for (i, item) in self.iter().enumerate() {
let off = i * T::PAYLOAD_BYTES;
item.marshal(&mut dst[off..off + T::PAYLOAD_BYTES]);
}
}
fn unmarshal(src: &[u8]) -> Result<Self, MarshalError> {
let need = T::PAYLOAD_BYTES * N;
if src.len() < need {
return Err(MarshalError::ShortBuffer { expected: need, got: src.len() });
}
let mut out = [T::default(); N];
for (i, slot) in out.iter_mut().enumerate() {
let off = i * T::PAYLOAD_BYTES;
*slot = T::unmarshal(&src[off..off + T::PAYLOAD_BYTES])?;
}
Ok(out)
}
}
unsafe impl<A: Marshal, B: Marshal> Marshal for (A, B) {
const PAYLOAD_BYTES: usize = A::PAYLOAD_BYTES + B::PAYLOAD_BYTES;
fn marshal(&self, dst: &mut [u8]) {
self.0.marshal(&mut dst[..A::PAYLOAD_BYTES]);
self.1.marshal(&mut dst[A::PAYLOAD_BYTES..A::PAYLOAD_BYTES + B::PAYLOAD_BYTES]);
}
fn unmarshal(src: &[u8]) -> Result<Self, MarshalError> {
let need = A::PAYLOAD_BYTES + B::PAYLOAD_BYTES;
if src.len() < need {
return Err(MarshalError::ShortBuffer { expected: need, got: src.len() });
}
let a = A::unmarshal(&src[..A::PAYLOAD_BYTES])?;
let b = B::unmarshal(&src[A::PAYLOAD_BYTES..need])?;
Ok((a, b))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn round_trip<T: Marshal + PartialEq + std::fmt::Debug>(v: T) {
let mut buf = vec![0u8; T::PAYLOAD_BYTES];
v.marshal(&mut buf);
let back = T::unmarshal(&buf).unwrap();
assert_eq!(v, back);
}
#[test] fn u8_round_trip() { round_trip(0u8); round_trip(255u8); }
#[test] fn u32_round_trip() { round_trip(0u32); round_trip(u32::MAX); round_trip(0xDEAD_BEEFu32); }
#[test] fn u64_round_trip() { round_trip(0u64); round_trip(u64::MAX); round_trip(0xCAFEBABE_DEADBEEFu64); }
#[test] fn i64_round_trip() { round_trip(i64::MIN); round_trip(0i64); round_trip(i64::MAX); }
#[test] fn f64_round_trip() { round_trip(0.0_f64); round_trip(-1.5_f64); round_trip(f64::INFINITY); }
#[test] fn bool_round_trip() { round_trip(true); round_trip(false); }
#[test] fn unit_round_trip() { round_trip(()); }
#[test]
fn array_round_trip() {
round_trip([1u8, 2, 3, 4]);
round_trip([0u64; 8]);
round_trip([0xDEAD_BEEF_CAFE_BABE_u64, 0x1234_5678_9ABC_DEF0]);
}
#[test]
fn tuple_round_trip() {
round_trip((42u32, 7u64));
round_trip((true, 99i32));
}
#[test]
fn nested_array_in_tuple() {
let v: (u32, [u8; 16]) = (0xCAFEBABE, [9; 16]);
round_trip(v);
}
#[test]
fn bool_rejects_invalid_byte() {
match bool::unmarshal(&[42u8]) {
Err(MarshalError::InvalidEncoding) => {}
other => panic!("expected InvalidEncoding, got {other:?}"),
}
}
#[test]
fn short_buffer_rejected() {
match u64::unmarshal(&[0u8; 3]) {
Err(MarshalError::ShortBuffer { expected: 8, got: 3 }) => {}
other => panic!("expected ShortBuffer{{expected:8,got:3}}, got {other:?}"),
}
}
#[test]
fn payload_bytes_constants_match_sizes() {
assert_eq!(u8::PAYLOAD_BYTES, 1);
assert_eq!(u32::PAYLOAD_BYTES, 4);
assert_eq!(u64::PAYLOAD_BYTES, 8);
assert_eq!(u128::PAYLOAD_BYTES, 16);
assert_eq!(<[u64; 8]>::PAYLOAD_BYTES, 64);
assert_eq!(<(u32, u64)>::PAYLOAD_BYTES, 12);
}
}