use std::{
fmt,
io::{self, BufRead, Read},
};
#[derive(Debug)]
pub enum FrameReadError {
EndOfStream,
TooLarge(Vec<u8>),
Unterminated(Vec<u8>),
Io(io::Error),
}
impl FrameReadError {
#[must_use]
pub fn rejected_bytes(&self) -> Option<&[u8]> {
match self {
Self::TooLarge(bytes) | Self::Unterminated(bytes) => Some(bytes),
Self::EndOfStream | Self::Io(_) => None,
}
}
}
impl fmt::Display for FrameReadError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EndOfStream => formatter.write_str("frame stream closed"),
Self::TooLarge(_) => formatter.write_str("frame exceeds caller byte limit"),
Self::Unterminated(_) => formatter.write_str("frame is missing its newline"),
Self::Io(_) => formatter.write_str("frame read failed"),
}
}
}
impl std::error::Error for FrameReadError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(error) => Some(error),
_ => None,
}
}
}
pub fn read_line(reader: &mut impl BufRead, maximum: u64) -> Result<Vec<u8>, FrameReadError> {
let mut bytes = Vec::new();
reader
.take(maximum.saturating_add(1))
.read_until(b'\n', &mut bytes)
.map_err(FrameReadError::Io)?;
if bytes.is_empty() {
return Err(FrameReadError::EndOfStream);
}
if bytes.len() as u64 > maximum {
return Err(FrameReadError::TooLarge(bytes));
}
if !bytes.ends_with(b"\n") {
return Err(FrameReadError::Unterminated(bytes));
}
Ok(bytes)
}
pub struct LineFramer {
maximum: u64,
pending: Option<Vec<u8>>,
}
impl LineFramer {
#[must_use]
pub const fn new(maximum: u64) -> Self {
Self {
maximum,
pending: Some(Vec::new()),
}
}
pub fn feed(
&mut self,
chunk: &[u8],
mut emit: impl FnMut(&[u8]) -> io::Result<()>,
) -> Result<(), FrameReadError> {
let mut pending = self.pending.take().ok_or_else(refused)?;
for fragment in chunk.split_inclusive(|byte| *byte == b'\n') {
let remaining = self.maximum.saturating_sub(pending.len() as u64);
if fragment.len() as u64 > remaining {
let prefix = usize::try_from(remaining)
.map_err(|error| FrameReadError::Io(io::Error::other(error)))?
+ 1;
pending
.try_reserve_exact(prefix)
.map_err(|error| FrameReadError::Io(io::Error::other(error)))?;
pending.extend_from_slice(&fragment[..prefix]);
return Err(FrameReadError::TooLarge(pending));
}
pending
.try_reserve_exact(fragment.len())
.map_err(|error| FrameReadError::Io(io::Error::other(error)))?;
pending.extend_from_slice(fragment);
if pending.ends_with(b"\n") {
emit(&pending).map_err(FrameReadError::Io)?;
pending.clear();
}
}
self.pending = Some(pending);
Ok(())
}
pub fn finish(self) -> Result<(), FrameReadError> {
let pending = self.pending.ok_or_else(refused)?;
if pending.is_empty() {
Ok(())
} else {
Err(FrameReadError::Unterminated(pending))
}
}
}
fn refused() -> FrameReadError {
FrameReadError::Io(io::Error::new(
io::ErrorKind::InvalidInput,
"framer previously refused the stream",
))
}
#[cfg(test)]
mod tests;