use std::fmt;
use crate::blocking::MAX_FRAME_SIZE;
use crate::control::RequestId;
use crate::control_codec::{REQUEST_ID_LEN, TAG_LEN, TAG_REQUEST, read_id};
use crate::error::ProtocolError;
pub const PREFIX_LEN: usize = 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Frame<'a> {
pub payload: &'a [u8],
}
impl Frame<'_> {
#[inline]
pub fn consumed(&self) -> usize {
PREFIX_LEN + self.payload.len()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FrameTooLarge {
pub declared: u32,
pub max: usize,
}
impl fmt::Display for FrameTooLarge {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"frame too large: {} bytes (max {})",
self.declared, self.max
)
}
}
impl std::error::Error for FrameTooLarge {}
impl From<FrameTooLarge> for std::io::Error {
fn from(e: FrameTooLarge) -> Self {
std::io::Error::new(std::io::ErrorKind::InvalidData, e)
}
}
#[inline]
pub fn split_frame(buf: &[u8]) -> Result<Option<Frame<'_>>, FrameTooLarge> {
split_frame_limited(buf, MAX_FRAME_SIZE)
}
#[inline]
pub fn split_frame_limited(
buf: &[u8],
max_payload: usize,
) -> Result<Option<Frame<'_>>, FrameTooLarge> {
let Some((prefix, rest)) = buf.split_first_chunk::<PREFIX_LEN>() else {
return Ok(None);
};
let declared = u32::from_le_bytes(*prefix);
let len = match usize::try_from(declared) {
Ok(len) if len <= max_payload => len,
_ => {
return Err(FrameTooLarge {
declared,
max: max_payload,
});
}
};
Ok(rest.get(..len).map(|payload| Frame { payload }))
}
#[derive(Debug)]
pub struct FrameDecoder {
buf: Vec<u8>,
start: usize,
max_payload: usize,
poisoned: Option<FrameTooLarge>,
}
impl Default for FrameDecoder {
fn default() -> Self {
Self::new()
}
}
impl FrameDecoder {
pub fn new() -> Self {
Self::with_max_payload(MAX_FRAME_SIZE)
}
pub fn with_max_payload(max_payload: usize) -> Self {
Self {
buf: Vec::with_capacity(PREFIX_LEN + max_payload.min(MAX_FRAME_SIZE)),
start: 0,
max_payload,
poisoned: None,
}
}
pub fn max_payload(&self) -> usize {
self.max_payload
}
pub fn is_poisoned(&self) -> bool {
self.poisoned.is_some()
}
pub fn pending(&self) -> &[u8] {
&self.buf[self.start..]
}
pub fn push(&mut self, bytes: &[u8]) {
if self.poisoned.is_some() {
return;
}
let remaining = self.buf.len() - self.start;
if self.start > 0 && self.start >= remaining {
self.buf.copy_within(self.start.., 0);
self.buf.truncate(remaining);
self.start = 0;
}
self.buf.extend_from_slice(bytes);
}
#[expect(clippy::should_implement_trait)] pub fn next(&mut self) -> Result<Option<&[u8]>, FrameTooLarge> {
if let Some(e) = self.poisoned {
return Err(e);
}
match split_frame_limited(&self.buf[self.start..], self.max_payload) {
Ok(Some(frame)) => {
let payload_start = self.start + PREFIX_LEN;
self.start += frame.consumed();
Ok(Some(&self.buf[payload_start..self.start]))
}
Ok(None) => Ok(None),
Err(e) => {
self.poisoned = Some(e);
self.buf = Vec::new();
self.start = 0;
Err(e)
}
}
}
}
pub const REQUEST_TAG_AND_ID_LEN: usize = TAG_LEN + REQUEST_ID_LEN;
pub const REQUEST_HEADER_LEN: usize = PREFIX_LEN + REQUEST_TAG_AND_ID_LEN;
pub const MAX_REQUEST_BODY: usize = MAX_FRAME_SIZE - REQUEST_TAG_AND_ID_LEN;
const _: () = assert!(MAX_FRAME_SIZE <= u32::MAX as usize);
#[inline]
pub fn request_tag_and_id(id: RequestId) -> [u8; REQUEST_TAG_AND_ID_LEN] {
let mut out = [0u8; REQUEST_TAG_AND_ID_LEN];
out[0] = TAG_REQUEST;
out[TAG_LEN..].copy_from_slice(&id.0.to_le_bytes());
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NotARequest {
Empty,
Tag(u8),
TooShort { len: usize },
}
impl fmt::Display for NotARequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => f.write_str("an empty frame where a request was expected"),
Self::Tag(tag) => write!(
f,
"a frame with tag {tag:#04x} where a request was expected"
),
Self::TooShort { len } => write!(
f,
"a {len}-byte request frame, too short for its {REQUEST_ID_LEN}-byte ID"
),
}
}
}
impl std::error::Error for NotARequest {}
#[inline]
pub fn split_request(payload: &[u8]) -> Result<(RequestId, &[u8]), NotARequest> {
match payload.first() {
None => Err(NotARequest::Empty),
Some(&TAG_REQUEST) => match read_id(payload) {
Some(id) => Ok((id, &payload[REQUEST_TAG_AND_ID_LEN..])),
None => Err(NotARequest::TooShort { len: payload.len() }),
},
Some(&tag) => Err(NotARequest::Tag(tag)),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RequestFrameError {
RequestTooLarge { len: usize },
BufferTooSmall { needed: usize, available: usize },
}
impl fmt::Display for RequestFrameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::RequestTooLarge { len } => write!(
f,
"request too large: {len} bytes with its tag and ID, the frame limit is {MAX_FRAME_SIZE}"
),
Self::BufferTooSmall { needed, available } => write!(
f,
"request buffer too small: the frame needs {needed} bytes, the buffer holds {available}"
),
}
}
}
impl std::error::Error for RequestFrameError {}
impl From<RequestFrameError> for ProtocolError {
fn from(e: RequestFrameError) -> Self {
match e {
RequestFrameError::RequestTooLarge { len } => ProtocolError::MessageTooLarge(len),
RequestFrameError::BufferTooSmall { .. } => ProtocolError::Truncated,
}
}
}
#[inline]
pub fn request_payload_len(body_len: usize) -> Result<u32, RequestFrameError> {
let len = REQUEST_TAG_AND_ID_LEN.saturating_add(body_len);
if len > MAX_FRAME_SIZE {
return Err(RequestFrameError::RequestTooLarge { len });
}
Ok(len as u32)
}
#[inline]
pub fn request_body(buf: &mut [u8]) -> Result<&mut [u8], RequestFrameError> {
let available = buf.len();
match buf.get_mut(REQUEST_HEADER_LEN..) {
Some(body) => {
let region = body.len().min(MAX_REQUEST_BODY);
Ok(&mut body[..region])
}
None => Err(RequestFrameError::BufferTooSmall {
needed: REQUEST_HEADER_LEN,
available,
}),
}
}
#[inline]
pub fn seal_request(
buf: &mut [u8],
id: RequestId,
body_len: usize,
) -> Result<usize, RequestFrameError> {
let payload_len = request_payload_len(body_len)?;
let frame_len = REQUEST_HEADER_LEN + body_len;
let available = buf.len();
let Some(header) = buf
.get_mut(..frame_len)
.and_then(|frame| frame.first_chunk_mut::<REQUEST_HEADER_LEN>())
else {
return Err(RequestFrameError::BufferTooSmall {
needed: frame_len,
available,
});
};
header[..PREFIX_LEN].copy_from_slice(&payload_len.to_le_bytes());
header[PREFIX_LEN..].copy_from_slice(&request_tag_and_id(id));
Ok(frame_len)
}
#[inline]
pub fn frame_request<E: From<RequestFrameError>>(
buf: &mut [u8],
id: RequestId,
encode: impl FnOnce(&mut [u8]) -> Result<usize, E>,
) -> Result<usize, E> {
let body_len = encode(request_body(buf)?)?;
Ok(seal_request(buf, id, body_len)?)
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
fn encode(frames: &[Vec<u8>]) -> Vec<u8> {
let mut out = Vec::new();
for f in frames {
out.extend_from_slice(&(f.len() as u32).to_le_bytes());
out.extend_from_slice(f);
}
out
}
fn split_all(buf: &[u8], max: usize) -> (Vec<Vec<u8>>, usize) {
let mut cursor = 0;
let mut out = Vec::new();
while let Some(frame) = split_frame_limited(&buf[cursor..], max).unwrap() {
out.push(frame.payload.to_vec());
cursor += frame.consumed();
}
(out, cursor)
}
fn decode_chunked(stream: &[u8], cuts: &[usize], max: usize) -> Vec<Vec<u8>> {
let mut dec = FrameDecoder::with_max_payload(max);
let mut out = Vec::new();
let mut prev = 0;
for &cut in cuts.iter().chain(std::iter::once(&stream.len())) {
dec.push(&stream[prev..cut]);
prev = cut;
while let Some(p) = dec.next().unwrap() {
out.push(p.to_vec());
}
}
assert!(dec.pending().is_empty());
out
}
#[test]
fn empty_and_partial_prefix_need_more() {
assert_eq!(split_frame(&[]), Ok(None));
assert_eq!(split_frame(&[3]), Ok(None));
assert_eq!(split_frame(&[3, 0, 0]), Ok(None));
}
#[test]
fn prefix_without_payload_needs_more() {
assert_eq!(split_frame(&[3, 0, 0, 0]), Ok(None));
assert_eq!(split_frame(&[3, 0, 0, 0, 0xAA, 0xBB]), Ok(None));
}
#[test]
fn complete_frame_ignores_trailing_bytes() {
let buf = [3, 0, 0, 0, 0xAA, 0xBB, 0xCC, 0xFF, 0xFF];
let frame = split_frame(&buf).unwrap().unwrap();
assert_eq!(frame.payload, &[0xAA, 0xBB, 0xCC]);
assert_eq!(frame.consumed(), 7);
}
#[test]
fn zero_length_frame_is_an_empty_payload() {
let buf = [0, 0, 0, 0, 9];
let frame = split_frame(&buf).unwrap().unwrap();
assert!(frame.payload.is_empty());
assert_eq!(frame.consumed(), PREFIX_LEN);
}
#[test]
fn exact_limit_succeeds() {
let stream = encode(&[vec![7u8; MAX_FRAME_SIZE]]);
let frame = split_frame(&stream).unwrap().unwrap();
assert_eq!(frame.payload.len(), MAX_FRAME_SIZE);
assert_eq!(frame.consumed(), stream.len());
}
#[test]
fn oversized_fails_on_the_prefix_alone() {
let declared = MAX_FRAME_SIZE as u32 + 1;
assert_eq!(
split_frame(&declared.to_le_bytes()),
Err(FrameTooLarge {
declared,
max: MAX_FRAME_SIZE
})
);
}
#[test]
fn max_u32_prefix_reports_the_declared_length() {
assert_eq!(
split_frame(&[0xFF; 4]),
Err(FrameTooLarge {
declared: u32::MAX,
max: MAX_FRAME_SIZE
})
);
}
#[test]
fn custom_limit_is_respected() {
let at = encode(&[vec![1u8; 256]]);
assert_eq!(
split_frame_limited(&at, 256)
.unwrap()
.unwrap()
.payload
.len(),
256
);
let over = encode(&[vec![1u8; 257]]);
assert_eq!(
split_frame_limited(&over[..PREFIX_LEN], 256),
Err(FrameTooLarge {
declared: 257,
max: 256
})
);
assert!(split_frame(&over).unwrap().is_some());
}
#[test]
fn zero_limit_admits_only_empty_frames() {
assert!(split_frame_limited(&[0, 0, 0, 0], 0).unwrap().is_some());
assert_eq!(
split_frame_limited(&[1, 0, 0, 0], 0),
Err(FrameTooLarge {
declared: 1,
max: 0
})
);
}
#[test]
fn limit_above_u32_admits_every_prefix() {
assert_eq!(split_frame_limited(&[0xFF; 4], usize::MAX), Ok(None));
}
#[test]
fn error_converts_to_invalid_data() {
let e: std::io::Error = FrameTooLarge {
declared: 2000,
max: 1024,
}
.into();
assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
assert_eq!(e.to_string(), "frame too large: 2000 bytes (max 1024)");
}
#[test]
fn every_truncation_yields_whole_frames_only() {
let frames = vec![vec![], vec![1], vec![2, 3, 4], vec![], vec![5; 40]];
let stream = encode(&frames);
for end in 0..=stream.len() {
let (got, consumed) = split_all(&stream[..end], MAX_FRAME_SIZE);
assert_eq!(got, frames[..got.len()], "end {end}");
assert_eq!(consumed, encode(&got).len(), "end {end}");
if got.len() < frames.len() {
assert!(end - consumed < PREFIX_LEN + frames[got.len()].len());
}
}
}
#[test]
fn decoder_splits_at_every_boundary() {
let frames = vec![vec![], vec![1], vec![2, 3, 4], vec![], vec![5; 40]];
let stream = encode(&frames);
for cut in 0..=stream.len() {
assert_eq!(decode_chunked(&stream, &[cut], MAX_FRAME_SIZE), frames);
}
for a in 0..=stream.len() {
for b in a..=stream.len() {
assert_eq!(decode_chunked(&stream, &[a, b], MAX_FRAME_SIZE), frames);
}
}
}
#[test]
fn decoder_byte_at_a_time() {
let frames = vec![vec![9; 3], vec![], vec![8; MAX_FRAME_SIZE]];
let stream = encode(&frames);
let cuts: Vec<usize> = (1..stream.len()).collect();
assert_eq!(decode_chunked(&stream, &cuts, MAX_FRAME_SIZE), frames);
}
#[test]
fn decoder_keeps_pipelined_bytes_pending() {
let mut dec = FrameDecoder::new();
dec.push(&encode(&[vec![1, 2]]));
dec.push(&[5, 0, 0, 0, 7]);
assert_eq!(dec.next().unwrap(), Some(&[1u8, 2][..]));
assert_eq!(dec.next().unwrap(), None);
assert_eq!(dec.pending(), &[5, 0, 0, 0, 7]);
dec.push(&[8, 9, 10, 11]);
assert_eq!(dec.next().unwrap(), Some(&[7u8, 8, 9, 10, 11][..]));
assert!(dec.pending().is_empty());
}
#[test]
fn decoder_interleaved_push_and_single_next() {
let frames: Vec<Vec<u8>> = (0..64u8).map(|i| vec![i; usize::from(i % 7)]).collect();
let stream = encode(&frames);
let mut dec = FrameDecoder::new();
let mut out = Vec::new();
for chunk in stream.chunks(5) {
let live_before = dec.pending().len();
dec.push(chunk);
assert!(dec.buf.len() <= 2 * live_before + chunk.len());
if let Some(p) = dec.next().unwrap() {
out.push(p.to_vec());
}
}
while let Some(p) = dec.next().unwrap() {
out.push(p.to_vec());
}
assert_eq!(out, frames);
assert!(dec.pending().is_empty());
}
#[test]
fn decoder_poisons_on_oversized_prefix() {
let mut dec = FrameDecoder::with_max_payload(256);
assert_eq!(dec.max_payload(), 256);
dec.push(&encode(&[vec![1]]));
dec.push(&257u32.to_le_bytes());
assert_eq!(dec.next().unwrap(), Some(&[1u8][..]));
let err = FrameTooLarge {
declared: 257,
max: 256,
};
assert_eq!(dec.next(), Err(err));
assert!(dec.is_poisoned());
assert!(dec.pending().is_empty());
dec.push(&encode(&[vec![2]]));
assert!(dec.pending().is_empty());
assert_eq!(dec.next(), Err(err));
}
#[test]
fn decoder_starts_empty() {
let mut dec = FrameDecoder::default();
assert_eq!(dec.max_payload(), MAX_FRAME_SIZE);
assert!(!dec.is_poisoned());
assert_eq!(dec.next(), Ok(None));
dec.push(&[]);
assert_eq!(dec.next(), Ok(None));
}
const ID: RequestId = RequestId(0x0807_0605_0403_0201);
fn request_frame_with(id: RequestId, body: &[u8]) -> Vec<u8> {
let mut frame = ((TAG_LEN + REQUEST_ID_LEN + body.len()) as u32)
.to_le_bytes()
.to_vec();
frame.push(TAG_REQUEST);
frame.extend_from_slice(&id.0.to_le_bytes());
frame.extend_from_slice(body);
frame
}
fn app_frame(body: &[u8]) -> Vec<u8> {
request_frame_with(ID, body)
}
fn frame_body(buf: &mut [u8], body: &[u8]) -> Result<usize, RequestFrameError> {
frame_body_as(buf, ID, body)
}
fn frame_body_as(
buf: &mut [u8],
id: RequestId,
body: &[u8],
) -> Result<usize, RequestFrameError> {
frame_request(buf, id, |out| {
let Some(dst) = out.get_mut(..body.len()) else {
return Err(RequestFrameError::BufferTooSmall {
needed: body.len(),
available: out.len(),
});
};
dst.copy_from_slice(body);
Ok(body.len())
})
}
#[test]
fn request_constants_follow_the_frame_limit() {
assert_eq!(REQUEST_HEADER_LEN, PREFIX_LEN + TAG_LEN + REQUEST_ID_LEN);
assert_eq!(REQUEST_HEADER_LEN, 13);
assert_eq!(MAX_FRAME_SIZE, 1024);
assert_eq!(MAX_REQUEST_BODY, MAX_FRAME_SIZE - TAG_LEN - REQUEST_ID_LEN);
assert_eq!(MAX_REQUEST_BODY, 1015);
assert_eq!(
request_payload_len(MAX_REQUEST_BODY),
Ok(MAX_FRAME_SIZE as u32)
);
assert_eq!(
request_payload_len(MAX_REQUEST_BODY + 1),
Err(RequestFrameError::RequestTooLarge {
len: MAX_FRAME_SIZE + 1
})
);
assert_eq!(
request_payload_len(usize::MAX),
Err(RequestFrameError::RequestTooLarge { len: usize::MAX })
);
}
#[test]
fn request_body_is_the_rest_capped_at_the_widest_body() {
let mut buf = [0u8; 32];
assert_eq!(
request_body(&mut buf).unwrap().len(),
32 - REQUEST_HEADER_LEN
);
let mut wide = vec![0u8; REQUEST_HEADER_LEN + MAX_FRAME_SIZE + 16];
assert_eq!(request_body(&mut wide).unwrap().len(), MAX_REQUEST_BODY);
let mut header_only = [0u8; REQUEST_HEADER_LEN];
assert!(request_body(&mut header_only).unwrap().is_empty());
for len in 0..REQUEST_HEADER_LEN {
assert_eq!(
request_body(&mut vec![0u8; len]),
Err(RequestFrameError::BufferTooSmall {
needed: REQUEST_HEADER_LEN,
available: len
})
);
}
}
#[test]
fn seal_request_writes_the_header_and_nothing_else() {
let mut buf = [0xEE; 32];
buf[REQUEST_HEADER_LEN..REQUEST_HEADER_LEN + 3].copy_from_slice(b"abc");
let n = seal_request(&mut buf, ID, 3).unwrap();
assert_eq!(n, REQUEST_HEADER_LEN + 3);
assert_eq!(&buf[..n], app_frame(b"abc").as_slice());
assert_eq!(buf[PREFIX_LEN], TAG_REQUEST);
assert_eq!(
&buf[PREFIX_LEN + 1..REQUEST_HEADER_LEN],
&ID.0.to_le_bytes()
);
assert!(buf[n..].iter().all(|&b| b == 0xEE));
let mut buf = [0u8; REQUEST_HEADER_LEN];
assert_eq!(seal_request(&mut buf, ID, 0), Ok(REQUEST_HEADER_LEN));
assert_eq!(buf.as_slice(), app_frame(b"").as_slice());
for id in [0, 1, u64::MAX] {
let mut buf = [0u8; REQUEST_HEADER_LEN];
seal_request(&mut buf, RequestId(id), 0).unwrap();
assert_eq!(buf.as_slice(), request_frame_with(RequestId(id), b""));
}
}
#[test]
fn the_tag_and_id_are_laid_out_once() {
let header = request_tag_and_id(ID);
assert_eq!(header.len(), REQUEST_TAG_AND_ID_LEN);
assert_eq!(header, request_frame_with(ID, b"")[PREFIX_LEN..]);
}
#[test]
fn split_request_reads_the_id_and_the_body() {
let frame = app_frame(b"body");
assert_eq!(split_request(&frame[PREFIX_LEN..]), Ok((ID, &b"body"[..])));
let empty = app_frame(b"");
assert_eq!(split_request(&empty[PREFIX_LEN..]), Ok((ID, &b""[..])));
assert_eq!(split_request(&[]), Err(NotARequest::Empty));
for len in 1..REQUEST_TAG_AND_ID_LEN {
assert_eq!(
split_request(&empty[PREFIX_LEN..PREFIX_LEN + len]),
Err(NotARequest::TooShort { len })
);
}
for tag in (0..=u8::MAX).filter(|&t| t != TAG_REQUEST) {
let payload = [&[tag][..], &[0x11; 16]].concat();
assert_eq!(split_request(&payload), Err(NotARequest::Tag(tag)));
assert_eq!(split_request(&[tag]), Err(NotARequest::Tag(tag)));
}
assert!(NotARequest::Empty.to_string().contains("empty"));
assert!(NotARequest::Tag(0x09).to_string().contains("0x09"));
assert!(
NotARequest::TooShort { len: 4 }
.to_string()
.contains("4-byte")
);
}
#[test]
fn seal_request_refuses_without_writing() {
let mut buf = [0x77u8; REQUEST_HEADER_LEN + 4];
assert_eq!(
seal_request(&mut buf, ID, 5),
Err(RequestFrameError::BufferTooSmall {
needed: REQUEST_HEADER_LEN + 5,
available: REQUEST_HEADER_LEN + 4
})
);
for len in 0..REQUEST_HEADER_LEN {
let mut short = vec![0x77u8; len];
assert_eq!(
seal_request(&mut short, ID, 0),
Err(RequestFrameError::BufferTooSmall {
needed: REQUEST_HEADER_LEN,
available: len
})
);
assert!(short.iter().all(|&b| b == 0x77));
}
let mut wide = vec![0x77u8; REQUEST_HEADER_LEN + MAX_FRAME_SIZE + 16];
assert_eq!(
seal_request(&mut wide, ID, MAX_REQUEST_BODY + 1),
Err(RequestFrameError::RequestTooLarge {
len: MAX_FRAME_SIZE + 1
})
);
assert_eq!(
seal_request(&mut buf, ID, usize::MAX),
Err(RequestFrameError::RequestTooLarge { len: usize::MAX })
);
assert!(wide.iter().all(|&b| b == 0x77));
assert!(buf.iter().all(|&b| b == 0x77));
}
#[test]
fn frame_request_writes_the_header_in_front_of_the_body() {
let mut buf = [0xEE; 32];
let n = frame_body(&mut buf, b"abc").unwrap();
assert_eq!(n, REQUEST_HEADER_LEN + 3);
assert_eq!(&buf[..n], app_frame(b"abc").as_slice());
assert!(buf[n..].iter().all(|&b| b == 0xEE));
let n = frame_request(&mut buf, ID, |out| {
assert_eq!(out.len(), 32 - REQUEST_HEADER_LEN);
Ok::<_, RequestFrameError>(0)
})
.unwrap();
assert_eq!(&buf[..n], app_frame(b"").as_slice());
}
#[test]
fn frame_request_takes_the_widest_body_and_refuses_one_more() {
let mut buf = vec![0u8; REQUEST_HEADER_LEN + MAX_FRAME_SIZE + 16];
let widest = vec![0xAB; MAX_REQUEST_BODY];
let n = frame_body(&mut buf, &widest).unwrap();
assert_eq!(n, PREFIX_LEN + MAX_FRAME_SIZE);
let frame = split_frame(&buf[..n]).unwrap().unwrap();
assert_eq!(frame.consumed(), n);
assert_eq!(split_request(frame.payload), Ok((ID, widest.as_slice())));
let mut buf = vec![0x5C; REQUEST_HEADER_LEN + MAX_FRAME_SIZE + 16];
let err = frame_request(&mut buf, ID, |out| {
assert_eq!(out.len(), MAX_REQUEST_BODY);
Ok::<_, RequestFrameError>(MAX_REQUEST_BODY + 1)
})
.unwrap_err();
assert_eq!(
err,
RequestFrameError::RequestTooLarge {
len: MAX_FRAME_SIZE + 1
}
);
assert!(buf[..REQUEST_HEADER_LEN].iter().all(|&b| b == 0x5C));
let err =
frame_request(&mut buf, ID, |_| Ok::<_, RequestFrameError>(usize::MAX)).unwrap_err();
assert_eq!(err, RequestFrameError::RequestTooLarge { len: usize::MAX });
let mut small = [0u8; 16];
let err = frame_request(&mut small, ID, |_| {
Ok::<_, RequestFrameError>(MAX_REQUEST_BODY + 1)
})
.unwrap_err();
assert!(
matches!(err, RequestFrameError::RequestTooLarge { .. }),
"{err:?}"
);
}
#[test]
fn frame_request_refuses_a_buffer_too_small() {
for len in 0..REQUEST_HEADER_LEN {
let mut buf = vec![0u8; len];
let err = frame_request(&mut buf, ID, |_| -> Result<usize, RequestFrameError> {
panic!("the encoder must not run without room for the header")
})
.unwrap_err();
assert_eq!(
err,
RequestFrameError::BufferTooSmall {
needed: REQUEST_HEADER_LEN,
available: len
}
);
assert!(err.to_string().contains("too small"), "{err}");
}
let mut buf = [0u8; REQUEST_HEADER_LEN];
assert_eq!(frame_body(&mut buf, b"").unwrap(), REQUEST_HEADER_LEN);
let mut buf = [0x77u8; REQUEST_HEADER_LEN + 4];
let err = frame_request(&mut buf, ID, |_| Ok::<_, RequestFrameError>(5)).unwrap_err();
assert_eq!(
err,
RequestFrameError::BufferTooSmall {
needed: REQUEST_HEADER_LEN + 5,
available: REQUEST_HEADER_LEN + 4
}
);
assert!(buf[..REQUEST_HEADER_LEN].iter().all(|&b| b == 0x77));
}
#[test]
fn frame_request_passes_the_encoders_error_through() {
#[derive(Debug, PartialEq)]
enum CodecError {
Mine,
Framing(RequestFrameError),
}
impl From<RequestFrameError> for CodecError {
fn from(e: RequestFrameError) -> Self {
CodecError::Framing(e)
}
}
let mut buf = [0x33u8; 24];
assert_eq!(
frame_request(&mut buf, ID, |_| Err(CodecError::Mine)),
Err(CodecError::Mine)
);
assert!(buf.iter().all(|&b| b == 0x33));
assert_eq!(
frame_request(&mut buf[..2], ID, |_| Ok::<_, CodecError>(0)),
Err(CodecError::Framing(RequestFrameError::BufferTooSmall {
needed: REQUEST_HEADER_LEN,
available: 2
}))
);
}
fn encode_request_as_codec(body: &[u8], buf: &mut [u8]) -> Result<usize, ProtocolError> {
let region = request_body(buf)?;
let dst = region
.get_mut(..body.len())
.ok_or(ProtocolError::InvalidField("body"))?;
dst.copy_from_slice(body);
Ok(seal_request(buf, ID, body.len())?)
}
#[test]
fn a_protocol_error_codec_frames_with_question_mark() {
let mut buf = [0u8; 24];
assert_eq!(
encode_request_as_codec(b"abc", &mut buf).unwrap(),
REQUEST_HEADER_LEN + 3
);
assert_eq!(&buf[..REQUEST_HEADER_LEN + 3], app_frame(b"abc").as_slice());
assert!(matches!(
encode_request_as_codec(b"", &mut buf[..2]),
Err(ProtocolError::Truncated)
));
let err = frame_request(&mut [0u8; 16], ID, |_| {
Ok::<_, ProtocolError>(MAX_REQUEST_BODY + 1)
})
.unwrap_err();
assert!(
matches!(err, ProtocolError::MessageTooLarge(len) if len == MAX_FRAME_SIZE + 1),
"{err:?}"
);
assert!(matches!(
ProtocolError::from(RequestFrameError::BufferTooSmall {
needed: 9,
available: 8
}),
ProtocolError::Truncated
));
}
#[test]
fn request_frame_errors_say_what_failed() {
assert_eq!(
RequestFrameError::RequestTooLarge { len: 2000 }.to_string(),
format!(
"request too large: 2000 bytes with its tag and ID, the frame limit is {MAX_FRAME_SIZE}"
)
);
assert_eq!(
RequestFrameError::BufferTooSmall {
needed: 9,
available: 8
}
.to_string(),
"request buffer too small: the frame needs 9 bytes, the buffer holds 8"
);
}
#[test]
fn frame_request_frames_at_an_offset_and_batches_back_to_back() {
let bodies: [&[u8]; 4] = [b"one", b"", &[TAG_REQUEST, 0x00], b"four!"];
let mut buf = [0xEE; 128];
let start = 7;
let mut end = start;
let mut expected = Vec::new();
for (n, body) in bodies.into_iter().enumerate() {
let id = RequestId(n as u64 + 1);
end += frame_body_as(&mut buf[end..], id, body).unwrap();
expected.extend(request_frame_with(id, body));
}
assert!(buf[..start].iter().all(|&b| b == 0xEE));
assert!(buf[end..].iter().all(|&b| b == 0xEE));
assert_eq!(&buf[start..end], expected.as_slice());
}
fn arb_frames(max: usize) -> impl Strategy<Value = Vec<Vec<u8>>> {
prop::collection::vec(prop::collection::vec(any::<u8>(), 0..=max), 0..12)
}
proptest! {
#[test]
fn any_chunking_yields_the_same_frames(
frames in arb_frames(64),
raw_cuts in prop::collection::vec(any::<prop::sample::Index>(), 0..16),
) {
let stream = encode(&frames);
let mut cuts: Vec<usize> =
raw_cuts.iter().map(|i| i.index(stream.len() + 1)).collect();
cuts.sort_unstable();
prop_assert_eq!(decode_chunked(&stream, &cuts, 64), frames.clone());
prop_assert_eq!(split_all(&stream, 64), (frames, stream.len()));
}
#[test]
fn oversized_prefix_fails_on_arrival(
frames in arb_frames(32),
max in 0usize..64,
excess in 1u32..1_000_000,
cut in any::<prop::sample::Index>(),
) {
let good: Vec<Vec<u8>> =
frames.into_iter().map(|mut f| { f.truncate(max); f }).collect();
let declared = max as u32 + excess;
let mut stream = encode(&good);
stream.extend_from_slice(&declared.to_le_bytes());
let cut = cut.index(stream.len() + 1);
let mut dec = FrameDecoder::with_max_payload(max);
let mut got = Vec::new();
let mut result = Ok(());
for chunk in [&stream[..cut], &stream[cut..]] {
dec.push(chunk);
loop {
match dec.next() {
Ok(Some(p)) => got.push(p.to_vec()),
Ok(None) => break,
Err(e) => { result = Err(e); break; }
}
}
}
prop_assert_eq!(got, good);
prop_assert_eq!(result, Err(FrameTooLarge { declared, max }));
}
#[test]
fn framed_requests_round_trip_through_the_decoder(
requests in prop::collection::vec(
(any::<u64>(), prop::collection::vec(any::<u8>(), 0..=64)),
0..8,
),
offset in 0usize..16,
chunk in 1usize..32,
) {
let mut buf = vec![0u8; offset + requests.len() * (REQUEST_HEADER_LEN + 64)];
let mut end = offset;
for (id, body) in &requests {
end += frame_body_as(&mut buf[end..], RequestId(*id), body).unwrap();
}
let mut decoder = FrameDecoder::new();
let mut got = Vec::new();
for piece in buf[offset..end].chunks(chunk) {
decoder.push(piece);
while let Some(payload) = decoder.next().unwrap() {
let (id, body) = split_request(payload).unwrap();
got.push((id.0, body.to_vec()));
}
}
prop_assert_eq!(got, requests);
prop_assert!(decoder.pending().is_empty());
}
}
}