use std::io::{self, Read};
pub const OPEN: u8 = 1;
pub const DATA: u8 = 2;
pub const CLOSE: u8 = 3;
pub const HEADER: usize = 9;
pub const MAX_DATA: usize = 1 << 20;
pub fn encode(id: u32, kind: u8, data: &[u8]) -> Vec<u8> {
let mut frame = Vec::with_capacity(HEADER + data.len());
frame.extend_from_slice(&id.to_be_bytes());
frame.push(kind);
frame.extend_from_slice(&(data.len() as u32).to_be_bytes());
frame.extend_from_slice(data);
frame
}
pub fn header(bytes: &[u8; HEADER]) -> io::Result<(u32, u8, usize)> {
let id = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let len = u32::from_be_bytes([bytes[5], bytes[6], bytes[7], bytes[8]]) as usize;
if !(OPEN..=CLOSE).contains(&bytes[4]) || len > MAX_DATA {
return Err(io::Error::new(io::ErrorKind::InvalidData, "bad frame"));
}
Ok((id, bytes[4], len))
}
pub fn read(input: &mut impl Read) -> io::Result<Option<(u32, u8, Vec<u8>)>> {
let mut bytes = [0u8; HEADER];
let mut got = 0;
while got < HEADER {
match input.read(&mut bytes[got..])? {
0 if got == 0 => return Ok(None),
0 => return Err(io::ErrorKind::UnexpectedEof.into()),
n => got += n,
}
}
let (id, kind, len) = header(&bytes)?;
let mut data = vec![0u8; len];
input.read_exact(&mut data)?;
Ok(Some((id, kind, data)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn frames_round_trip() {
let mut stream = encode(7, OPEN, b"");
stream.extend(encode(7, DATA, b"GET /_ping HTTP/1.1\r\n\r\n"));
stream.extend(encode(u32::MAX, CLOSE, b""));
let mut input = stream.as_slice();
assert_eq!(read(&mut input).unwrap(), Some((7, OPEN, vec![])));
assert_eq!(
read(&mut input).unwrap(),
Some((7, DATA, b"GET /_ping HTTP/1.1\r\n\r\n".to_vec()))
);
assert_eq!(read(&mut input).unwrap(), Some((u32::MAX, CLOSE, vec![])));
assert_eq!(read(&mut input).unwrap(), None);
assert!(read(&mut &encode(1, DATA, b"abc")[..10]).is_err());
assert!(read(&mut &[0, 0, 0, 1, 9, 0, 0, 0, 0][..]).is_err());
}
struct Trickle<'a>(&'a [u8]);
impl Read for Trickle<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let Some((first, rest)) = self.0.split_first() else {
return Ok(0);
};
buf[0] = *first;
self.0 = rest;
Ok(1)
}
}
#[test]
fn reads_frames_that_arrive_a_byte_at_a_time() {
let mut stream = encode(3, DATA, b"hello");
stream.extend(encode(4, CLOSE, b""));
let mut input = Trickle(&stream);
assert_eq!(
read(&mut input).unwrap(),
Some((3, DATA, b"hello".to_vec()))
);
assert_eq!(read(&mut input).unwrap(), Some((4, CLOSE, vec![])));
assert_eq!(read(&mut input).unwrap(), None);
}
#[test]
fn caps_the_frame_size() {
let mut at_most = [0u8; HEADER];
at_most.copy_from_slice(&encode(1, DATA, b"")[..HEADER]);
at_most[5..].copy_from_slice(&(MAX_DATA as u32).to_be_bytes());
assert_eq!(header(&at_most).unwrap(), (1, DATA, MAX_DATA));
let mut over = at_most;
over[5..].copy_from_slice(&(MAX_DATA as u32 + 1).to_be_bytes());
assert!(header(&over).is_err());
assert!(read(&mut &over[..]).is_err());
}
#[test]
fn a_stream_cut_mid_header_is_an_error() {
let frame = encode(9, OPEN, b"");
for cut in 1..HEADER {
let error = read(&mut &frame[..cut]).unwrap_err();
assert_eq!(error.kind(), io::ErrorKind::UnexpectedEof);
}
}
}