use crate::buf::WriteBuf;
use crate::error::{Error, Result};
use crate::framing::{Frame, Framer};
#[derive(Debug, Clone, Copy)]
pub struct Delimited<'d> {
delimiter: &'d [u8],
max_payload: usize,
}
impl<'d> Delimited<'d> {
#[inline]
pub const fn new(delimiter: &'d [u8]) -> Self {
assert!(!delimiter.is_empty(), "delimiter must be non-empty");
Self {
delimiter,
max_payload: usize::MAX,
}
}
#[inline]
#[must_use]
pub const fn with_max_payload(mut self, max: usize) -> Self {
self.max_payload = max;
self
}
#[inline]
pub const fn delimiter(&self) -> &'d [u8] {
self.delimiter
}
#[inline]
pub const fn max_payload(&self) -> usize {
self.max_payload
}
fn find_delimiter(&self, haystack: &[u8]) -> Option<usize> {
let needle = self.delimiter;
if haystack.len() < needle.len() {
return None;
}
let last = haystack.len() - needle.len();
let mut i = 0;
while i <= last {
if &haystack[i..i + needle.len()] == needle {
return Some(i);
}
i += 1;
}
None
}
}
impl<'d> Framer for Delimited<'d> {
fn next_frame<'a>(&self, input: &'a [u8]) -> Result<Option<Frame<'a>>> {
let window_cap = self.max_payload.saturating_add(self.delimiter.len());
let scan_window = if input.len() > window_cap {
&input[..window_cap]
} else {
input
};
match self.find_delimiter(scan_window) {
Some(pos) => {
if pos > self.max_payload {
return Err(Error::FrameTooLarge {
len: pos,
limit: self.max_payload,
});
}
let consumed = pos + self.delimiter.len();
Ok(Some(Frame::new(&input[..pos], consumed)))
}
None => {
if input.len() > window_cap {
return Err(Error::FrameTooLarge {
len: input.len(),
limit: self.max_payload,
});
}
Ok(None)
}
}
}
fn write_frame(&self, payload: &[u8], out: &mut WriteBuf<'_>) -> Result<()> {
if payload.len() > self.max_payload {
return Err(Error::FrameTooLarge {
len: payload.len(),
limit: self.max_payload,
});
}
out.write_bytes(payload)?;
out.write_bytes(self.delimiter)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn newline_round_trip() {
let framer = Delimited::new(b"\n");
let mut storage = [0u8; 32];
let mut buf = WriteBuf::new(&mut storage);
framer.write_frame(b"hello", &mut buf).unwrap();
let n = buf.position();
assert_eq!(&storage[..n], b"hello\n");
let frame = framer.next_frame(&storage[..n]).unwrap().unwrap();
assert_eq!(frame.payload(), b"hello");
assert_eq!(frame.consumed(), 6);
}
#[test]
fn multi_byte_delimiter() {
let framer = Delimited::new(b"\r\n");
let frame = framer.next_frame(b"GET /\r\nrest").unwrap().unwrap();
assert_eq!(frame.payload(), b"GET /");
assert_eq!(frame.consumed(), 7);
}
#[test]
fn no_delimiter_returns_none() {
let framer = Delimited::new(b"\n");
assert_eq!(framer.next_frame(b"no terminator here").unwrap(), None);
}
#[test]
fn exceeds_max_payload_short_circuits_search() {
let framer = Delimited::new(b"\n").with_max_payload(3);
let result = framer.next_frame(b"abcde\n");
assert!(matches!(result, Err(Error::FrameTooLarge { limit: 3, .. })));
}
#[test]
fn empty_payload_is_valid() {
let framer = Delimited::new(b"\n");
let frame = framer.next_frame(b"\nrest").unwrap().unwrap();
assert_eq!(frame.payload(), b"");
assert_eq!(frame.consumed(), 1);
}
}