use std::io::{self, Read, Write};
pub const RESPONSE_FLAG: u8 = 0x80;
pub const ERROR_FLAG: u8 = 0x40;
pub const TYPE_MASK: u8 = 0x3F;
pub const MAX_MESSAGE: u32 = 16 * 1024 * 1024;
pub const HEADER_LEN: usize = 5;
pub const fn strip_flags(raw: u8) -> u8 {
raw & TYPE_MASK
}
pub const fn is_response(raw: u8) -> bool {
raw & RESPONSE_FLAG != 0
}
pub const fn is_error(raw: u8) -> bool {
raw & ERROR_FLAG != 0
}
#[derive(Debug, Default, Clone)]
pub struct Writer {
buf: Vec<u8>,
}
impl Writer {
pub fn new() -> Self {
Writer { buf: Vec::new() }
}
pub fn with_capacity(n: usize) -> Self {
Writer {
buf: Vec::with_capacity(n),
}
}
pub fn u8(&mut self, v: u8) -> &mut Self {
self.buf.push(v);
self
}
pub fn u32(&mut self, v: u32) -> &mut Self {
self.buf.extend_from_slice(&v.to_be_bytes());
self
}
pub fn u64(&mut self, v: u64) -> &mut Self {
self.buf.extend_from_slice(&v.to_be_bytes());
self
}
pub fn bytes(&mut self, b: &[u8]) -> &mut Self {
self.u32(b.len() as u32);
self.buf.extend_from_slice(b);
self
}
pub fn take(self) -> Vec<u8> {
self.buf
}
pub fn as_slice(&self) -> &[u8] {
&self.buf
}
}
#[derive(Debug, Clone)]
pub struct Reader<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> Reader<'a> {
pub fn new(data: &'a [u8]) -> Self {
Reader { data, pos: 0 }
}
pub fn remaining(&self) -> usize {
self.data.len() - self.pos
}
pub fn u8(&mut self) -> Option<u8> {
let v = *self.data.get(self.pos)?;
self.pos += 1;
Some(v)
}
pub fn u32(&mut self) -> Option<u32> {
let s: [u8; 4] = self.data.get(self.pos..self.pos + 4)?.try_into().ok()?;
self.pos += 4;
Some(u32::from_be_bytes(s))
}
pub fn u64(&mut self) -> Option<u64> {
let s: [u8; 8] = self.data.get(self.pos..self.pos + 8)?.try_into().ok()?;
self.pos += 8;
Some(u64::from_be_bytes(s))
}
pub fn bytes(&mut self) -> Option<&'a [u8]> {
let n = self.u32()? as usize;
let end = self.pos.checked_add(n)?;
let s = self.data.get(self.pos..end)?;
self.pos = end;
Some(s)
}
}
pub fn write_frame<W: Write>(w: &mut W, msg_type: u8, payload: &[u8]) -> io::Result<()> {
if payload.len() as u64 > MAX_MESSAGE as u64 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"zap: frame too large",
));
}
let mut frame = Vec::with_capacity(HEADER_LEN + payload.len());
frame.extend_from_slice(&(payload.len() as u32).to_be_bytes());
frame.push(msg_type);
frame.extend_from_slice(payload);
w.write_all(&frame)
}
pub fn read_frame<R: Read>(r: &mut R, max: u32) -> io::Result<(u8, Vec<u8>)> {
let mut header = [0u8; HEADER_LEN];
r.read_exact(&mut header)?;
let len = u32::from_be_bytes([header[0], header[1], header[2], header[3]]);
if len > max.min(MAX_MESSAGE) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"zap: frame too large",
));
}
let mut payload = vec![0u8; len as usize];
r.read_exact(&mut payload)?;
Ok((header[4], payload))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_frame_is_a_be_length_then_a_type_then_the_payload() {
let mut out = Vec::new();
write_frame(&mut out, 0x2a, &[1, 2, 3]).expect("write");
assert_eq!(out, vec![0, 0, 0, 3, 0x2a, 1, 2, 3]);
}
#[test]
fn a_written_frame_reads_back() {
let mut out = Vec::new();
write_frame(&mut out, 62, b"hello").expect("write");
let (t, p) = read_frame(&mut out.as_slice(), 1024).expect("read");
assert_eq!(t, 62);
assert_eq!(p, b"hello");
}
#[test]
fn a_field_is_a_be_length_then_the_bytes() {
let mut w = Writer::new();
w.bytes(&[0xaa, 0xbb]);
assert_eq!(w.as_slice(), &[0, 0, 0, 2, 0xaa, 0xbb]);
}
#[test]
fn a_field_round_trips() {
let mut w = Writer::new();
w.bytes(b"one").bytes(b"").bytes(b"three");
let buf = w.take();
let mut r = Reader::new(&buf);
assert_eq!(r.bytes(), Some(&b"one"[..]));
assert_eq!(r.bytes(), Some(&b""[..]));
assert_eq!(r.bytes(), Some(&b"three"[..]));
assert_eq!(r.remaining(), 0);
}
#[test]
fn an_over_long_length_is_refused_not_allocated() {
let hostile = [0xffu8, 0xff, 0xff, 0xff, 0x01, 0x02];
let mut r = Reader::new(&hostile);
assert_eq!(r.bytes(), None);
}
#[test]
fn a_length_that_would_wrap_the_cursor_is_refused() {
for announced in [u32::MAX, u32::MAX - 1, 1 << 31, 1 << 24] {
let mut hostile = announced.to_be_bytes().to_vec();
hostile.extend_from_slice(b"short");
let mut r = Reader::new(&hostile);
assert_eq!(r.bytes(), None, "announced {announced}");
assert!(r.remaining() <= hostile.len());
}
}
#[test]
fn a_frame_over_the_links_limit_is_refused() {
let mut framed = Vec::new();
write_frame(&mut framed, 62, &vec![0u8; 300]).expect("write");
let err = read_frame(&mut framed.as_slice(), 188).expect_err("must refuse");
assert_eq!(err.kind(), io::ErrorKind::InvalidData);
}
#[test]
fn a_truncated_frame_is_an_error_not_a_short_payload() {
let framed = [0u8, 0, 0, 8, 62, 1, 2, 3];
let err = read_frame(&mut framed.as_slice(), 1024).expect_err("must refuse");
assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof);
}
#[test]
fn the_constants_are_the_ones_go_writes_and_cpp_reads() {
assert_eq!(RESPONSE_FLAG, 0x80);
assert_eq!(ERROR_FLAG, 0x40);
assert_eq!(TYPE_MASK, 0x3F);
assert_eq!(MAX_MESSAGE, 16 * 1024 * 1024);
assert_eq!(HEADER_LEN, 5);
}
#[test]
fn strip_flags_is_gos_own_expression() {
for raw in 0..=u8::MAX {
assert_eq!(
strip_flags(raw),
raw & !(RESPONSE_FLAG | ERROR_FLAG),
"raw {raw}"
);
}
}
#[test]
fn the_header_is_big_endian_length_then_type() {
let mut out = Vec::new();
write_frame(&mut out, VOTE_LIKE, &[0xde, 0xad]).expect("write");
assert_eq!(
&out[..4],
&[0x00, 0x00, 0x00, 0x02],
"length is 4-byte big-endian"
);
assert_eq!(out[4], VOTE_LIKE, "then the type byte");
assert_eq!(&out[5..], &[0xde, 0xad]);
let mut out = Vec::new();
write_frame(&mut out, VOTE_LIKE, &vec![0u8; 258]).expect("write");
assert_eq!(&out[..4], &[0x00, 0x00, 0x01, 0x02]);
}
#[test]
fn a_field_is_big_endian_too() {
let mut w = Writer::new();
w.bytes(&vec![0u8; 300]);
assert_eq!(&w.as_slice()[..4], &[0x00, 0x00, 0x01, 0x2c]);
}
const VOTE_LIKE: u8 = 62;
#[test]
fn flags_ride_above_the_id_space() {
assert_eq!(strip_flags(62 | RESPONSE_FLAG | ERROR_FLAG), 62);
assert!(is_response(62 | RESPONSE_FLAG));
assert!(is_error(62 | ERROR_FLAG));
assert!(!is_response(62));
}
}