#[cfg(test)]
mod tests;
use crate::{Error, Limits, Wire};
use alloc::vec::Vec;
use core::marker::PhantomData;
use postcard::ser_flavors::Flavor;
pub(crate) const HEADER: usize = 11;
pub trait Method {
type Request: Wire;
type Response: Wire;
const ID: u64;
const NAME: &'static str;
const REQUEST_LIMITS: Limits;
const RESPONSE_LIMITS: Limits;
}
macro_rules! retained {
($name:ident) => {
pub struct $name<M: Method> {
bytes: Vec<u8>,
method: PhantomData<fn() -> M>,
}
impl<M: Method> $name<M> {
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
pub fn into_bytes(self) -> Vec<u8> {
self.bytes
}
}
};
}
retained!(RequestBytes);
retained!(ResponseBytes);
pub(crate) fn admit<T: Wire>(limits: Limits) -> Result<usize, Error> {
let shape = T::SHAPE;
if [shape.bytes, shape.heap, shape.nodes, shape.depth].contains(&usize::MAX) {
return Err(Error::SchemaLimit);
}
let size = shape.bytes.checked_add(HEADER).ok_or(Error::SchemaLimit)?;
let allocation = shape.heap.checked_add(size).ok_or(Error::SchemaLimit)?;
if size > limits.bytes
|| allocation > limits.allocation
|| shape.nodes > limits.nodes
|| shape.depth > limits.depth
{
return Err(Error::SchemaLimit);
}
Ok(size)
}
pub(crate) struct Output {
pub(crate) bytes: Vec<u8>,
pub(crate) limit: usize,
}
impl Flavor for Output {
type Output = Vec<u8>;
fn try_push(&mut self, byte: u8) -> postcard::Result<()> {
if self.bytes.len() == self.limit {
return Err(postcard::Error::SerializeBufferFull);
}
self.bytes.push(byte);
Ok(())
}
fn try_extend(&mut self, bytes: &[u8]) -> postcard::Result<()> {
if bytes.len() > self.limit - self.bytes.len() {
return Err(postcard::Error::SerializeBufferFull);
}
self.bytes.extend_from_slice(bytes);
Ok(())
}
fn finalize(self) -> postcard::Result<Self::Output> {
Ok(self.bytes)
}
}
struct Size {
bytes: usize,
limit: usize,
}
impl Flavor for Size {
type Output = usize;
fn try_push(&mut self, _: u8) -> postcard::Result<()> {
self.try_extend(&[0])
}
fn try_extend(&mut self, bytes: &[u8]) -> postcard::Result<()> {
if bytes.len() > self.limit - self.bytes {
return Err(postcard::Error::SerializeBufferFull);
}
self.bytes += bytes.len();
Ok(())
}
fn finalize(self) -> postcard::Result<usize> {
Ok(self.bytes)
}
}
fn encoded_len<T: Wire>(limits: Limits, value: &T) -> Result<usize, Error> {
let limit = admit::<T>(limits)?;
postcard::serialize_with_flavor(
value,
Size {
bytes: HEADER,
limit,
},
)
.map_err(|_| Error::Malformed)
}
pub(crate) struct Compare<'a> {
pub(crate) remaining: &'a [u8],
pub(crate) allowance: usize,
pub(crate) equal: bool,
}
impl Flavor for Compare<'_> {
type Output = bool;
#[inline(always)]
fn try_push(&mut self, byte: u8) -> postcard::Result<()> {
if self.allowance == 0 {
return Err(postcard::Error::SerializeBufferFull);
}
self.allowance -= 1;
if let Some((first, rest)) = self.remaining.split_first() {
self.equal &= *first == byte;
self.remaining = rest;
} else {
self.equal = false;
}
Ok(())
}
fn try_extend(&mut self, bytes: &[u8]) -> postcard::Result<()> {
if let [byte] = bytes {
return self.try_push(*byte);
}
if bytes.len() > self.allowance {
return Err(postcard::Error::SerializeBufferFull);
}
self.allowance -= bytes.len();
if let Some((prefix, rest)) = self.remaining.split_at_checked(bytes.len()) {
self.equal &= if cfg!(any(target_arch = "wasm32", test)) && bytes.len() >= 8 {
let (left, left_tail) = prefix.as_chunks::<8>();
let (right, right_tail) = bytes.as_chunks::<8>();
left.iter()
.zip(right)
.all(|(a, b)| u64::from_ne_bytes(*a) == u64::from_ne_bytes(*b))
&& left_tail == right_tail
} else {
prefix == bytes
};
self.remaining = rest;
} else {
self.equal = false;
self.remaining = &[];
}
Ok(())
}
fn finalize(self) -> postcard::Result<Self::Output> {
Ok(self.equal && self.remaining.is_empty())
}
}
fn encode<T: Wire>(id: u64, direction: u8, limits: Limits, value: &T) -> Result<Vec<u8>, Error> {
let size = admit::<T>(limits)?;
let mut bytes = Vec::new();
bytes
.try_reserve_exact(size)
.map_err(|_| Error::Allocation)?;
bytes.extend_from_slice(&header(id, direction));
postcard::serialize_with_flavor(value, Output { bytes, limit: size })
.map_err(|_| Error::Malformed)
}
fn decode<T: Wire>(id: u64, direction: u8, limits: Limits, bytes: &[u8]) -> Result<T, Error> {
let size = admit::<T>(limits)?;
if bytes.len() > size || bytes.len() > limits.bytes {
return Err(Error::Limit);
}
let payload = payload(id, direction, bytes)?;
let (value, rest) = postcard::take_from_bytes::<T>(payload).map_err(|_| Error::Malformed)?;
if !rest.is_empty() {
return Err(Error::Trailing);
}
let equal = postcard::serialize_with_flavor(
&value,
Compare {
remaining: payload,
allowance: size - HEADER,
equal: true,
},
)
.map_err(|_| Error::Malformed)?;
if !equal {
return Err(Error::Noncanonical);
}
Ok(value)
}
pub fn encode_request<M: Method>(value: &M::Request) -> Result<RequestBytes<M>, Error> {
Ok(RequestBytes {
bytes: encode(M::ID, 0, M::REQUEST_LIMITS, value)?,
method: PhantomData,
})
}
pub fn encoded_request_len<M: Method>(value: &M::Request) -> Result<usize, Error> {
encoded_len(M::REQUEST_LIMITS, value)
}
pub fn encoded_response_len<M: Method>(value: &M::Response) -> Result<usize, Error> {
encoded_len(M::RESPONSE_LIMITS, value)
}
pub fn decode_request<M: Method>(bytes: &[u8]) -> Result<M::Request, Error> {
decode(M::ID, 0, M::REQUEST_LIMITS, bytes)
}
pub fn encode_response<M: Method>(value: &M::Response) -> Result<ResponseBytes<M>, Error> {
Ok(ResponseBytes {
bytes: encode(M::ID, 1, M::RESPONSE_LIMITS, value)?,
method: PhantomData,
})
}
pub fn decode_response<M: Method>(bytes: &[u8]) -> Result<M::Response, Error> {
decode(M::ID, 1, M::RESPONSE_LIMITS, bytes)
}
pub(crate) fn header(id: u64, direction: u8) -> [u8; HEADER] {
let mut header = [0; HEADER];
header[..2].copy_from_slice(b"IW");
header[2..10].copy_from_slice(&id.to_le_bytes());
header[10] = direction;
header
}
pub(crate) fn payload(id: u64, direction: u8, bytes: &[u8]) -> Result<&[u8], Error> {
if bytes.len() < HEADER {
return Err(Error::Malformed);
}
if &bytes[..2] != b"IW" || bytes[2..10] != id.to_le_bytes() || bytes[10] != direction {
return Err(Error::Identity);
}
Ok(&bytes[HEADER..])
}