use std::io::{self, Read, Write};
use std::num::NonZeroU32;
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..], "header")?;
let length = u32::from_be_bytes(header);
if length == 0 || length > limit.get() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("frame length {length} is outside 1..={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, "body")?;
Ok(Some(body))
}
fn exact(input: &mut impl Read, bytes: &mut [u8], part: &str) -> io::Result<()> {
input.read_exact(bytes).map_err(|error| {
if error.kind() == io::ErrorKind::UnexpectedEof {
io::Error::new(
io::ErrorKind::UnexpectedEof,
format!("frame cut: the input ended inside its {part}"),
)
} else {
error
}
})
}
pub fn write(output: &mut impl Write, body: &[u8], limit: NonZeroU32) -> io::Result<()> {
let length = u32::try_from(body.len()).map_err(io::Error::other)?;
if length == 0 || length > limit.get() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("frame length {length} is outside 1..={limit}"),
));
}
output.write_all(&length.to_be_bytes())?;
output.write_all(body)?;
output.flush()
}