use std::fmt;
pub const FRAME_HEADER_LENGTH: usize = 4;
pub const DEFAULT_MAX_FRAME_LENGTH: usize = 16 * 1024 * 1024;
pub const PAYLOAD_BLOCK_SIZE: usize = 64 * 1024;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FrameError(pub String);
impl fmt::Display for FrameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for FrameError {}
pub fn encode_frame(payload: &[u8]) -> Result<Vec<u8>, FrameError> {
if payload.len() > u32::MAX as usize {
return Err(FrameError(
"Frame payload exceeds the unsigned 32-bit length limit".into(),
));
}
let mut frame = Vec::with_capacity(FRAME_HEADER_LENGTH + payload.len());
frame.extend_from_slice(&(payload.len() as u32).to_be_bytes());
frame.extend_from_slice(payload);
Ok(frame)
}
pub fn assert_complete_frame(frame: &[u8], max_frame_length: usize) -> Result<(), FrameError> {
if frame.len() < FRAME_HEADER_LENGTH {
return Err(FrameError(
"Frame does not contain a complete length prefix".into(),
));
}
let length = u32::from_be_bytes([frame[0], frame[1], frame[2], frame[3]]) as usize;
if length > max_frame_length {
return Err(FrameError(format!(
"Frame length {length} exceeds configured limit of {max_frame_length}"
)));
}
if frame.len() != FRAME_HEADER_LENGTH + length {
return Err(FrameError(
"Frame must contain exactly one complete payload".into(),
));
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State {
Open,
Ended,
Failed,
}
pub struct FrameDecoder {
header: [u8; FRAME_HEADER_LENGTH],
header_len: usize,
max_frame_length: usize,
payload: Vec<u8>,
expected_payload_len: Option<usize>,
state: State,
}
impl FrameDecoder {
pub fn new(max_frame_length: usize) -> Self {
Self {
header: [0; FRAME_HEADER_LENGTH],
header_len: 0,
max_frame_length,
payload: Vec::new(),
expected_payload_len: None,
state: State::Open,
}
}
pub fn with_default_max() -> Self {
Self::new(DEFAULT_MAX_FRAME_LENGTH)
}
pub fn push(&mut self, chunk: &[u8]) -> Result<Vec<Vec<u8>>, FrameError> {
match self.state {
State::Ended => return Err(FrameError("Frame decoder has ended".into())),
State::Failed => return Err(FrameError("Frame decoder has failed".into())),
State::Open => {}
}
let mut frames = Vec::new();
let mut offset = 0usize;
while offset < chunk.len() {
if self.expected_payload_len.is_none() {
let take = (FRAME_HEADER_LENGTH - self.header_len).min(chunk.len() - offset);
self.header[self.header_len..self.header_len + take]
.copy_from_slice(&chunk[offset..offset + take]);
self.header_len += take;
offset += take;
if self.header_len < FRAME_HEADER_LENGTH {
continue;
}
let frame_length = u32::from_be_bytes(self.header) as usize;
self.header_len = 0;
if frame_length > self.max_frame_length {
self.state = State::Failed;
return Err(FrameError(format!(
"Frame length {frame_length} exceeds configured limit of {}",
self.max_frame_length
)));
}
if frame_length == 0 {
frames.push(Vec::new());
continue;
}
self.expected_payload_len = Some(frame_length);
self.payload = Vec::with_capacity(frame_length.min(PAYLOAD_BLOCK_SIZE));
}
let expected = self.expected_payload_len.unwrap();
let want = expected - self.payload.len();
let take = want.min(chunk.len() - offset);
self.payload
.extend_from_slice(&chunk[offset..offset + take]);
offset += take;
if self.payload.len() == expected {
frames.push(std::mem::take(&mut self.payload));
self.expected_payload_len = None;
}
}
Ok(frames)
}
pub fn end(&mut self) -> Result<(), FrameError> {
if self.header_len != 0 || self.expected_payload_len.is_some() {
self.state = State::Failed;
return Err(FrameError("Frame decoder ended mid-frame".into()));
}
self.state = State::Ended;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_round_trip() {
let frame = encode_frame(b"hello").unwrap();
assert_eq!(&frame[..4], &[0, 0, 0, 5]);
assert_complete_frame(&frame, DEFAULT_MAX_FRAME_LENGTH).unwrap();
}
#[test]
fn decoder_splits_multiple_and_partial_chunks() {
let mut decoder = FrameDecoder::with_default_max();
let a = encode_frame(b"one").unwrap();
let b = encode_frame(b"two").unwrap();
let mut stream = Vec::new();
stream.extend_from_slice(&a);
stream.extend_from_slice(&b);
let out1 = decoder.push(&stream[..2]).unwrap();
assert!(out1.is_empty());
let out2 = decoder.push(&stream[2..5]).unwrap();
assert!(out2.is_empty());
let out3 = decoder.push(&stream[5..7]).unwrap();
assert_eq!(out3, vec![b"one".to_vec()]);
let out4 = decoder.push(&stream[7..]).unwrap();
assert_eq!(out4, vec![b"two".to_vec()]);
decoder.end().unwrap();
}
#[test]
fn empty_frame() {
let frame = encode_frame(b"").unwrap();
let mut decoder = FrameDecoder::with_default_max();
assert_eq!(decoder.push(&frame).unwrap(), vec![Vec::<u8>::new()]);
}
#[test]
fn rejects_oversized() {
let frame = encode_frame(&vec![0u8; 32]).unwrap();
assert!(assert_complete_frame(&frame, 16).is_err());
let mut decoder = FrameDecoder::new(16);
assert!(decoder.push(&frame).is_err());
}
#[test]
fn rejects_trailing_bytes() {
let mut frame = encode_frame(b"x").unwrap();
frame.push(0);
assert!(assert_complete_frame(&frame, DEFAULT_MAX_FRAME_LENGTH).is_err());
}
}