use std::io::{self, BufReader, BufWriter, Read, Write};
pub const MAX_FRAME_SIZE: usize = 1024;
pub struct BlockingFrameReader<R> {
reader: BufReader<R>,
buf: [u8; MAX_FRAME_SIZE],
frame_len: usize,
}
impl<R: Read> BlockingFrameReader<R> {
pub fn new(reader: R) -> Self {
Self {
reader: BufReader::new(reader),
buf: [0u8; MAX_FRAME_SIZE],
frame_len: 0,
}
}
pub fn read_frame(&mut self) -> io::Result<Option<&[u8]>> {
let mut len_buf = [0u8; 4];
match self.reader.read_exact(&mut len_buf) {
Ok(()) => {}
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
Err(e) => return Err(e),
}
let len = u32::from_le_bytes(len_buf) as usize;
if len > MAX_FRAME_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("frame too large: {len} bytes (max {MAX_FRAME_SIZE})"),
));
}
self.reader.read_exact(&mut self.buf[..len])?;
self.frame_len = len;
Ok(Some(&self.buf[..len]))
}
pub fn frame(&self) -> &[u8] {
&self.buf[..self.frame_len]
}
pub fn get_ref(&self) -> &R {
self.reader.get_ref()
}
}
pub struct BlockingFrameWriter<W: Write> {
writer: BufWriter<W>,
}
impl<W: Write> BlockingFrameWriter<W> {
pub fn new(writer: W) -> Self {
Self {
writer: BufWriter::new(writer),
}
}
pub fn write_frame(&mut self, data: &[u8]) -> io::Result<()> {
self.write_frame_parts(&[data])
}
pub fn write_frame_parts(&mut self, parts: &[&[u8]]) -> io::Result<()> {
let len: usize = parts.iter().map(|part| part.len()).sum();
if len > MAX_FRAME_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("frame too large: {len} bytes (max {MAX_FRAME_SIZE})"),
));
}
self.writer.write_all(&(len as u32).to_le_bytes())?;
for part in parts {
self.writer.write_all(part)?;
}
Ok(())
}
pub fn flush(&mut self) -> io::Result<()> {
self.writer.flush()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{TcpListener, TcpStream};
#[test]
fn frame_round_trip_blocking() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let client = TcpStream::connect(addr).unwrap();
let (server, _) = listener.accept().unwrap();
let mut writer = BlockingFrameWriter::new(client);
let mut reader = BlockingFrameReader::new(server);
let data = b"hello melin";
writer.write_frame(data).unwrap();
writer.flush().unwrap();
let received = reader.read_frame().unwrap().unwrap();
assert_eq!(received, data);
}
#[test]
fn frame_parts_arrive_as_one_frame() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let client = TcpStream::connect(addr).unwrap();
let (server, _) = listener.accept().unwrap();
let mut writer = BlockingFrameWriter::new(client);
let mut reader = BlockingFrameReader::new(server);
writer
.write_frame_parts(&[&7u64.to_le_bytes(), &[0x10], b"body"])
.unwrap();
writer.write_frame_parts(&[]).unwrap();
writer.flush().unwrap();
let expected = [&7u64.to_le_bytes()[..], &[0x10], b"body"].concat();
assert_eq!(reader.read_frame().unwrap().unwrap(), expected);
assert_eq!(reader.read_frame().unwrap().unwrap(), b"");
}
#[test]
fn an_oversized_frame_is_refused_before_anything_is_written() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let client = TcpStream::connect(addr).unwrap();
let (server, _) = listener.accept().unwrap();
let mut writer = BlockingFrameWriter::new(client);
let mut reader = BlockingFrameReader::new(server);
let body = vec![0xAB; MAX_FRAME_SIZE];
let err = writer.write_frame_parts(&[&[0x01], &body]).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
writer.write_frame(&body).unwrap();
writer.flush().unwrap();
assert_eq!(reader.read_frame().unwrap().unwrap(), &body[..]);
}
#[test]
fn clean_disconnect_returns_none() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let client = TcpStream::connect(addr).unwrap();
let (server, _) = listener.accept().unwrap();
drop(client);
let mut reader = BlockingFrameReader::new(server);
let result = reader.read_frame().unwrap();
assert!(result.is_none());
}
#[test]
fn oversized_frame_rejected() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let mut client = TcpStream::connect(addr).unwrap();
let (server, _) = listener.accept().unwrap();
let fake_len = 2_000_000u32;
client.write_all(&fake_len.to_le_bytes()).unwrap();
let mut reader = BlockingFrameReader::new(server);
let result = reader.read_frame();
assert!(result.is_err());
}
#[test]
fn multiple_frames_in_sequence() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let client = TcpStream::connect(addr).unwrap();
let (server, _) = listener.accept().unwrap();
let mut writer = BlockingFrameWriter::new(client);
let mut reader = BlockingFrameReader::new(server);
for i in 0u32..100 {
writer.write_frame(&i.to_le_bytes()).unwrap();
}
writer.flush().unwrap();
for i in 0u32..100 {
let frame = reader.read_frame().unwrap().unwrap();
assert_eq!(frame, i.to_le_bytes());
}
}
}