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());
}
}