use std::fmt;
use std::io::{self, Read, Write};
use std::num::NonZeroU32;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Fault {
TooLarge { length: u32, limit: u32 },
Empty,
Cut,
}
impl Fault {
#[must_use]
pub fn of(error: &io::Error) -> Option<Self> {
error
.get_ref()
.and_then(|inner| inner.downcast_ref::<Self>())
.copied()
}
}
impl fmt::Display for Fault {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TooLarge { length, limit } => {
write!(
formatter,
"frame of {length} bytes exceeds its {limit}-byte limit"
)
}
Self::Empty => formatter.write_str("frame length is zero"),
Self::Cut => formatter.write_str("input ended inside a frame"),
}
}
}
impl std::error::Error for Fault {}
fn exact(input: &mut impl Read, bytes: &mut [u8]) -> io::Result<()> {
input.read_exact(bytes).map_err(|error| {
if error.kind() == io::ErrorKind::UnexpectedEof {
io::Error::new(io::ErrorKind::UnexpectedEof, Fault::Cut)
} else {
error
}
})
}
pub fn read(input: &mut impl Read, limit: NonZeroU32) -> io::Result<Option<Vec<u8>>> {
let mut header = [0_u8; 4];
loop {
match input.read(&mut header[..1]) {
Ok(0) => return Ok(None),
Ok(_) => break,
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
Err(error) => return Err(error),
}
}
exact(input, &mut header[1..])?;
let length = u32::from_be_bytes(header);
if length == 0 {
return Err(io::Error::new(io::ErrorKind::InvalidData, Fault::Empty));
}
if length > limit.get() {
let limit = limit.get();
return Err(io::Error::new(
io::ErrorKind::InvalidData,
Fault::TooLarge { length, limit },
));
}
let size = usize::try_from(length).map_err(io::Error::other)?;
let mut body = Vec::new();
body.try_reserve_exact(size).map_err(io::Error::other)?;
body.resize(size, 0);
exact(input, &mut body)?;
Ok(Some(body))
}
pub fn write(
output: &mut impl Write,
tag: u8,
payload: &[u8],
limit: NonZeroU32,
) -> io::Result<()> {
let size = payload
.len()
.checked_add(1)
.ok_or_else(|| io::Error::other("frame length overflow"))?;
let length = u32::try_from(size).map_err(io::Error::other)?;
if length > limit.get() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"frame exceeds its limit",
));
}
output.write_all(&length.to_be_bytes())?;
output.write_all(&[tag])?;
output.write_all(payload)?;
output.flush()
}