use crate::{CARRIAGE_RETURN, DEFAULT_LIMIT, END_BLOCK, Error, START_BLOCK};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Tolerance {
Strict,
Lenient,
}
impl Tolerance {
#[must_use]
pub fn default_tolerance() -> Tolerance {
if cfg!(feature = "noncompliance") {
Tolerance::Lenient
} else {
Tolerance::Strict
}
}
#[must_use]
pub fn strict() -> Tolerance {
Tolerance::Strict
}
#[must_use]
pub fn lenient() -> Tolerance {
Tolerance::Lenient
}
#[must_use]
pub fn allows_missing_carriage_return(self) -> bool {
self == Tolerance::Lenient
}
#[must_use]
pub fn allows_leading_bytes(self) -> bool {
self == Tolerance::Lenient
}
}
impl Default for Tolerance {
fn default() -> Tolerance {
Tolerance::default_tolerance()
}
}
#[derive(Debug, Clone)]
pub struct Framer {
buffer: Vec<u8>,
limit: usize,
tolerance: Tolerance,
}
impl Framer {
#[must_use]
pub fn new() -> Framer {
Framer {
buffer: Vec::new(),
limit: DEFAULT_LIMIT,
tolerance: Tolerance::default(),
}
}
#[must_use]
pub fn with_limit(mut self, limit: usize) -> Framer {
self.limit = limit;
self
}
#[must_use]
pub fn with_tolerance(mut self, tolerance: Tolerance) -> Framer {
self.tolerance = tolerance;
self
}
#[must_use]
pub fn tolerance(&self) -> Tolerance {
self.tolerance
}
#[must_use]
pub fn buffered(&self) -> usize {
self.buffer.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
pub fn reset(&mut self) {
self.buffer.clear();
}
pub fn push(&mut self, bytes: &[u8]) {
self.buffer.extend_from_slice(bytes);
}
pub fn next_frame(&mut self) -> Result<Option<Vec<u8>>, Error> {
let Some(start) = self.buffer.iter().position(|&byte| byte == START_BLOCK) else {
if self.buffer.is_empty() {
return Ok(None);
}
if self.tolerance.allows_leading_bytes() {
self.buffer.clear();
return Ok(None);
}
return Err(self.fail(Error::LeadingBytes(self.buffer.len())));
};
if start > 0 {
if !self.tolerance.allows_leading_bytes() {
return Err(self.fail(Error::LeadingBytes(start)));
}
self.buffer.drain(..start);
}
let body = &self.buffer[1..];
let Some(end) = body.iter().position(|&byte| byte == END_BLOCK) else {
if body.contains(&START_BLOCK) {
return Err(self.fail(Error::EmbeddedStartBlock));
}
self.check_limit()?;
return Ok(None);
};
if body[..end].contains(&START_BLOCK) {
return Err(self.fail(Error::EmbeddedStartBlock));
}
let trailer = end + 2;
match body.get(end + 1) {
Some(&CARRIAGE_RETURN) => {
let payload = body[..end].to_vec();
self.buffer.drain(..=trailer);
Ok(Some(payload))
}
Some(_) if self.tolerance.allows_missing_carriage_return() => {
let payload = body[..end].to_vec();
self.buffer.drain(..trailer);
Ok(Some(payload))
}
Some(_) => Err(self.fail(Error::NoCarriageReturn)),
None => {
self.check_limit()?;
Ok(None)
}
}
}
pub fn frames(&mut self) -> Result<Vec<Vec<u8>>, Error> {
let mut frames = Vec::new();
while let Some(frame) = self.next_frame()? {
frames.push(frame);
}
Ok(frames)
}
fn fail(&mut self, error: Error) -> Error {
self.buffer.clear();
error
}
fn check_limit(&mut self) -> Result<(), Error> {
if self.buffer.len() > self.limit {
let error = Error::TooLarge {
buffered: self.buffer.len(),
limit: self.limit,
};
return Err(self.fail(error));
}
Ok(())
}
}
impl Default for Framer {
fn default() -> Framer {
Framer::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn strict() -> Framer {
Framer::new().with_tolerance(Tolerance::Strict)
}
#[test]
fn reassembles_a_frame_split_across_reads() {
let mut framer = strict();
for chunk in [&b"\x0bMSH|"[..], b"^~\\&|LAB", b"\x1c\r"] {
framer.push(chunk);
}
assert_eq!(framer.next_frame().unwrap().unwrap(), b"MSH|^~\\&|LAB");
assert_eq!(framer.next_frame().unwrap(), None);
assert!(framer.is_empty());
}
#[test]
fn splits_several_frames_from_one_read() {
let mut framer = strict();
framer.push(b"\x0bone\x1c\r\x0btwo\x1c\r\x0bthree\x1c\r");
let pulled = framer.frames().unwrap();
assert_eq!(
pulled,
[b"one".to_vec(), b"two".to_vec(), b"three".to_vec()]
);
assert!(framer.is_empty());
}
#[test]
fn holds_a_partial_frame_without_reporting_it_as_an_error() {
let mut framer = strict();
framer.push(b"\x0bMSH|");
assert_eq!(framer.next_frame().unwrap(), None, "not an error, a wait");
assert_eq!(framer.buffered(), 5);
framer.push(b"\x1c");
assert_eq!(framer.next_frame().unwrap(), None);
framer.push(b"\r");
assert_eq!(framer.next_frame().unwrap().unwrap(), b"MSH|");
}
#[test]
fn keeps_the_second_frame_while_yielding_the_first() {
let mut framer = strict();
framer.push(b"\x0bone\x1c\r\x0btw");
assert_eq!(framer.next_frame().unwrap().unwrap(), b"one");
assert_eq!(framer.next_frame().unwrap(), None);
framer.push(b"o\x1c\r");
assert_eq!(framer.next_frame().unwrap().unwrap(), b"two");
}
#[test]
fn strict_mode_reports_what_lenient_mode_forgives() {
let mut framer = strict();
framer.push(b"garbage\x0bMSH|\x1c\r");
assert_eq!(framer.next_frame(), Err(Error::LeadingBytes(7)));
let mut framer = strict();
framer.push(b"\x0bMSH|\x1cX");
assert_eq!(framer.next_frame(), Err(Error::NoCarriageReturn));
let mut framer = Framer::new().with_tolerance(Tolerance::Lenient);
framer.push(b"garbage\x0bMSH|\x1cX");
assert_eq!(framer.next_frame().unwrap().unwrap(), b"MSH|");
}
#[test]
fn a_second_start_block_is_an_unfinished_frame_not_a_payload() {
let mut framer = strict();
framer.push(b"\x0bfirst\x0bsecond\x1c\r");
assert_eq!(framer.next_frame(), Err(Error::EmbeddedStartBlock));
let mut framer = strict();
framer.push(b"\x0bfirst\x0bsecond");
assert_eq!(framer.next_frame(), Err(Error::EmbeddedStartBlock));
}
#[test]
fn a_peer_that_never_ends_a_frame_cannot_exhaust_memory() {
let mut framer = Framer::new().with_limit(64);
framer.push(b"\x0b");
framer.push(&[b'x'; 100]);
assert_eq!(
framer.next_frame(),
Err(Error::TooLarge {
buffered: 101,
limit: 64
})
);
assert!(framer.is_empty());
}
#[test]
fn an_empty_payload_is_a_frame() {
let mut framer = strict();
framer.push(b"\x0b\x1c\r");
assert_eq!(framer.next_frame().unwrap().unwrap(), b"");
}
#[test]
fn a_reset_discards_a_half_read_message() {
let mut framer = strict();
framer.push(b"\x0bhalf");
assert!(!framer.is_empty());
framer.reset();
assert!(framer.is_empty());
assert_eq!(framer.next_frame().unwrap(), None);
}
}