use std::borrow::Cow;
use std::mem::MaybeUninit;
pub const MAX_FRAME_PAYLOAD: usize = 64 << 20;
pub const FRAME_LEN_PREFIX_BYTES: usize = 4;
pub fn frame_len_prefix(len: usize) -> [u8; FRAME_LEN_PREFIX_BYTES] {
debug_assert!((1..=MAX_FRAME_PAYLOAD).contains(&len), "frame payload of {len} bytes");
(len as u32).to_le_bytes()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameLenError {
Zero,
Oversize { len: usize },
Alloc { len: usize },
}
pub struct Deframer<A = ()> {
hdr: [u8; FRAME_LEN_PREFIX_BYTES],
hdr_len: usize,
pending: Option<Pending<A>>,
lent: Option<Lent>,
}
enum Lent {
Carry,
Payload,
}
struct Pending<A> {
buf: Box<[MaybeUninit<u8>]>,
pos: usize,
admitted: A,
}
pub type Frame<'s, A> = (Cow<'s, [u8]>, A);
impl<A> Default for Deframer<A> {
fn default() -> Self {
Deframer {
hdr: [0; FRAME_LEN_PREFIX_BYTES],
hdr_len: 0,
pending: None,
lent: None,
}
}
}
impl<A> Deframer<A> {
pub fn is_mid_frame(&self) -> bool {
self.hdr_len > 0 || self.pending.is_some()
}
pub fn feed<'s, E: From<FrameLenError>>(
&mut self,
src: &mut &'s [u8],
mut admit: impl FnMut(usize) -> Result<A, E>,
) -> Result<Option<Frame<'s, A>>, E> {
self.lent = None;
loop {
if let Some(p) = self.pending.take_if(|p| p.pos == p.buf.len()) {
return Ok(Some((
Cow::Owned(unsafe { p.buf.assume_init() }.into_vec()),
p.admitted,
)));
}
if let Some(Pending { buf, pos, .. }) = &mut self.pending {
let take = (buf.len() - *pos).min(src.len());
if take == 0 {
return Ok(None);
}
buf[*pos..][..take].write_copy_of_slice(&src[..take]);
*pos += take;
*src = &src[take..];
continue;
}
if src.is_empty() {
return Ok(None);
}
let take = (FRAME_LEN_PREFIX_BYTES - self.hdr_len).min(src.len());
self.hdr[self.hdr_len..][..take].copy_from_slice(&src[..take]);
self.hdr_len += take;
*src = &src[take..];
if self.hdr_len == FRAME_LEN_PREFIX_BYTES {
self.hdr_len = 0;
let len = match u32::from_le_bytes(self.hdr) as usize {
0 => return Err(FrameLenError::Zero.into()),
len if len > MAX_FRAME_PAYLOAD => return Err(FrameLenError::Oversize { len }.into()),
len => len,
};
let admitted = admit(len)?;
if let Some((payload, rest)) = src.split_at_checked(len) {
*src = rest;
return Ok(Some((Cow::Borrowed(payload), admitted)));
}
let mut v: Vec<MaybeUninit<u8>> = Vec::new();
v.try_reserve_exact(len).map_err(|_| FrameLenError::Alloc { len })?;
unsafe { v.set_len(len) };
self.pending = Some(Pending {
buf: v.into_boxed_slice(),
pos: 0,
admitted,
});
}
}
}
pub fn window<'a>(&'a mut self, carry: &'a mut [MaybeUninit<u8>]) -> &'a mut [MaybeUninit<u8>] {
let min = carry.len();
let tail = self.pending.as_mut().map(|p| &mut p.buf[p.pos..]);
match tail.filter(|t| t.len() >= min) {
Some(tail) => {
self.lent = Some(Lent::Payload);
tail
}
None => {
self.lent = Some(Lent::Carry);
carry
}
}
}
pub unsafe fn landed<'a>(&mut self, carry: &'a [MaybeUninit<u8>], n: usize) -> &'a [u8] {
match self.lent.take().expect("a read landed with no window out") {
Lent::Payload => {
let p = self.pending.as_mut().expect("a window in the payload");
assert!(n <= p.buf.len() - p.pos, "a read past its window");
p.pos += n;
&[]
}
Lent::Carry => unsafe { carry[..n].assume_init_ref() },
}
}
}
#[cfg(test)]
#[path = "tests/deframe.rs"]
mod tests;