#![allow(missing_docs)]
pub const MAGIC: [u8; 4] = *b"BRN0";
pub const WIRE_VERSION: u8 = 1;
pub const HEADER_SIZE: usize = 32;
pub const FLAG_EOS: u8 = 0x80;
pub const FLAG_MPL: u8 = 0x40;
pub const FLAG_CMP: u8 = 0x20;
pub const FLAGS_DEFINED_MASK: u8 = FLAG_EOS | FLAG_MPL | FLAG_CMP;
pub const OPCODE_NS_SUBSTRATE: u8 = 0x00;
pub const OPCODE_NS_TYPED_GRAPH: u8 = 0x01;
pub const OFF_MAGIC: usize = 0;
pub const OFF_VERSION: usize = 4;
pub const OFF_OPCODE: usize = 5;
pub const OFF_FLAGS: usize = 7;
pub const OFF_HEADER_CRC: usize = 8;
pub const OFF_STREAM_ID: usize = 12;
pub const OFF_PAYLOAD_LEN: usize = 16;
pub const OFF_RESERVED_A: usize = 19;
pub const OFF_PAYLOAD_CRC: usize = 20;
pub const OFF_RESERVED_B: usize = 24;
pub const MAX_PAYLOAD_BYTES: usize = 0xff_ff_ff;
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum FrameError {
#[error("bad frame magic")]
BadMagic,
#[error("unsupported wire version: got {got}, expected {expected}")]
BadVersion { got: u8, expected: u8 },
#[error("header CRC mismatch")]
BadHeaderCrc,
#[error("payload CRC mismatch")]
BadPayloadCrc,
#[error("payload length {len} exceeds maximum {max}")]
OversizePayload { len: u32, max: u32 },
#[error("reserved field or flag bit was non-zero")]
ReservedNonZero,
#[error("truncated frame: have {have} bytes, need {need}")]
Truncated { have: usize, need: usize },
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Frame {
pub opcode: u16,
pub flags: u8,
pub stream_id: u32,
pub payload: Vec<u8>,
}
fn compute_header_crc(header: &[u8; HEADER_SIZE]) -> u32 {
let mut buf = [0u8; HEADER_SIZE - 4];
buf[..OFF_HEADER_CRC].copy_from_slice(&header[..OFF_HEADER_CRC]);
buf[OFF_HEADER_CRC..].copy_from_slice(&header[OFF_STREAM_ID..]);
crc32c::crc32c(&buf)
}
impl Frame {
#[must_use]
pub fn new(opcode: u16, flags: u8, stream_id: u32, payload: Vec<u8>) -> Self {
Self {
opcode,
flags,
stream_id,
payload,
}
}
pub fn encode(&self) -> Result<Vec<u8>, FrameError> {
if self.payload.len() > MAX_PAYLOAD_BYTES {
return Err(FrameError::OversizePayload {
#[allow(clippy::cast_possible_truncation)]
len: self.payload.len() as u32,
#[allow(clippy::cast_possible_truncation)]
max: MAX_PAYLOAD_BYTES as u32,
});
}
let mut out = vec![0u8; HEADER_SIZE + self.payload.len()];
{
let header = &mut out[..HEADER_SIZE];
header[OFF_MAGIC..OFF_MAGIC + 4].copy_from_slice(&MAGIC);
header[OFF_VERSION] = WIRE_VERSION;
header[OFF_OPCODE..OFF_OPCODE + 2].copy_from_slice(&self.opcode.to_be_bytes());
header[OFF_FLAGS] = self.flags;
header[OFF_STREAM_ID..OFF_STREAM_ID + 4].copy_from_slice(&self.stream_id.to_be_bytes());
#[allow(clippy::cast_possible_truncation)]
let len_be = (self.payload.len() as u32).to_be_bytes();
header[OFF_PAYLOAD_LEN..OFF_PAYLOAD_LEN + 3].copy_from_slice(&len_be[1..]);
let payload_crc = crc32c::crc32c(&self.payload);
header[OFF_PAYLOAD_CRC..OFF_PAYLOAD_CRC + 4]
.copy_from_slice(&payload_crc.to_be_bytes());
}
let header_arr: [u8; HEADER_SIZE] = out[..HEADER_SIZE]
.try_into()
.expect("invariant: out starts with exactly HEADER_SIZE header bytes");
let header_crc = compute_header_crc(&header_arr);
out[OFF_HEADER_CRC..OFF_HEADER_CRC + 4].copy_from_slice(&header_crc.to_be_bytes());
out[HEADER_SIZE..].copy_from_slice(&self.payload);
Ok(out)
}
pub fn decode(bytes: &[u8]) -> Result<(Frame, &[u8]), FrameError> {
if bytes.len() < HEADER_SIZE {
return Err(FrameError::Truncated {
have: bytes.len(),
need: HEADER_SIZE,
});
}
let header: [u8; HEADER_SIZE] = bytes[..HEADER_SIZE]
.try_into()
.expect("invariant: slice is exactly HEADER_SIZE bytes after the length check");
if header[OFF_MAGIC..OFF_MAGIC + 4] != MAGIC {
return Err(FrameError::BadMagic);
}
if header[OFF_VERSION] != WIRE_VERSION {
return Err(FrameError::BadVersion {
got: header[OFF_VERSION],
expected: WIRE_VERSION,
});
}
if header[OFF_RESERVED_A] != 0 || header[OFF_RESERVED_B..HEADER_SIZE] != [0u8; 8] {
return Err(FrameError::ReservedNonZero);
}
let flags = header[OFF_FLAGS];
if flags & !FLAGS_DEFINED_MASK != 0 {
return Err(FrameError::ReservedNonZero);
}
let stored_header_crc = u32::from_be_bytes([
header[OFF_HEADER_CRC],
header[OFF_HEADER_CRC + 1],
header[OFF_HEADER_CRC + 2],
header[OFF_HEADER_CRC + 3],
]);
if stored_header_crc != compute_header_crc(&header) {
return Err(FrameError::BadHeaderCrc);
}
let payload_len = u32::from_be_bytes([
0,
header[OFF_PAYLOAD_LEN],
header[OFF_PAYLOAD_LEN + 1],
header[OFF_PAYLOAD_LEN + 2],
]);
#[allow(clippy::cast_possible_truncation)]
if payload_len as usize > MAX_PAYLOAD_BYTES {
return Err(FrameError::OversizePayload {
len: payload_len,
max: MAX_PAYLOAD_BYTES as u32,
});
}
let need = HEADER_SIZE + payload_len as usize;
if bytes.len() < need {
return Err(FrameError::Truncated {
have: bytes.len(),
need,
});
}
let payload_slice = &bytes[HEADER_SIZE..need];
let stored_payload_crc = u32::from_be_bytes([
header[OFF_PAYLOAD_CRC],
header[OFF_PAYLOAD_CRC + 1],
header[OFF_PAYLOAD_CRC + 2],
header[OFF_PAYLOAD_CRC + 3],
]);
if stored_payload_crc != crc32c::crc32c(payload_slice) {
return Err(FrameError::BadPayloadCrc);
}
let frame = Frame {
opcode: u16::from_be_bytes([header[OFF_OPCODE], header[OFF_OPCODE + 1]]),
flags,
stream_id: u32::from_be_bytes([
header[OFF_STREAM_ID],
header[OFF_STREAM_ID + 1],
header[OFF_STREAM_ID + 2],
header[OFF_STREAM_ID + 3],
]),
payload: payload_slice.to_vec(),
};
Ok((frame, &bytes[need..]))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> Frame {
Frame::new(0x0021, FLAG_EOS, 7, b"hello brain".to_vec())
}
#[test]
fn encode_decode_round_trip() {
let original = sample();
let bytes = original.encode().expect("frame fits");
assert_eq!(bytes.len(), HEADER_SIZE + original.payload.len());
let (decoded, rest) = Frame::decode(&bytes).expect("decode");
assert_eq!(rest, &[] as &[u8]);
assert_eq!(decoded, original);
}
#[test]
fn empty_payload_has_zero_payload_crc() {
let frame = Frame::new(0x0010, 0, 0, Vec::new());
let bytes = frame.encode().expect("frame fits");
assert_eq!(bytes.len(), HEADER_SIZE);
assert_eq!(&bytes[OFF_PAYLOAD_CRC..OFF_PAYLOAD_CRC + 4], &[0u8; 4]);
let (decoded, rest) = Frame::decode(&bytes).expect("decode empty");
assert_eq!(rest, &[] as &[u8]);
assert_eq!(decoded.payload, Vec::<u8>::new());
}
#[test]
fn decode_returns_unconsumed_tail() {
let mut bytes = sample().encode().expect("frame fits");
bytes.extend_from_slice(b"TAIL");
let (_frame, rest) = Frame::decode(&bytes).expect("decode");
assert_eq!(rest, b"TAIL");
}
#[test]
fn rejects_bad_magic() {
let mut bytes = sample().encode().expect("frame fits");
bytes[0] = b'X';
assert_eq!(Frame::decode(&bytes), Err(FrameError::BadMagic));
}
#[test]
fn rejects_bad_version() {
let mut bytes = sample().encode().expect("frame fits");
bytes[OFF_VERSION] = 99;
assert_eq!(
Frame::decode(&bytes),
Err(FrameError::BadVersion {
got: 99,
expected: 1
})
);
}
#[test]
fn rejects_bad_header_crc() {
let mut bytes = sample().encode().expect("frame fits");
bytes[OFF_HEADER_CRC] ^= 0xFF;
assert_eq!(Frame::decode(&bytes), Err(FrameError::BadHeaderCrc));
}
#[test]
fn rejects_bad_payload_crc() {
let mut bytes = sample().encode().expect("frame fits");
bytes[HEADER_SIZE] ^= 0xFF;
assert_eq!(Frame::decode(&bytes), Err(FrameError::BadPayloadCrc));
}
#[test]
fn rejects_reserved_a_nonzero() {
let mut bytes = sample().encode().expect("frame fits");
bytes[OFF_RESERVED_A] = 1;
let mut header: [u8; HEADER_SIZE] = bytes[..HEADER_SIZE].try_into().unwrap();
let crc = compute_header_crc(&{
let mut h = header;
h[OFF_HEADER_CRC..OFF_HEADER_CRC + 4].copy_from_slice(&[0; 4]);
h
});
header[OFF_HEADER_CRC..OFF_HEADER_CRC + 4].copy_from_slice(&crc.to_be_bytes());
bytes[..HEADER_SIZE].copy_from_slice(&header);
assert_eq!(Frame::decode(&bytes), Err(FrameError::ReservedNonZero));
}
#[test]
fn rejects_reserved_flag_bit() {
let frame = Frame::new(0x0010, 0x01, 0, Vec::new());
let bytes = frame.encode().expect("frame fits");
assert_eq!(Frame::decode(&bytes), Err(FrameError::ReservedNonZero));
}
#[test]
fn rejects_truncated_header() {
let bytes = sample().encode().expect("frame fits");
let truncated = &bytes[..10];
assert_eq!(
Frame::decode(truncated),
Err(FrameError::Truncated {
have: 10,
need: HEADER_SIZE
})
);
}
#[test]
fn rejects_truncated_payload() {
let bytes = sample().encode().expect("frame fits");
let truncated = &bytes[..bytes.len() - 3];
assert!(matches!(
Frame::decode(truncated),
Err(FrameError::Truncated { .. })
));
}
#[test]
fn crc32c_check_vector() {
assert_eq!(crc32c::crc32c(b"123456789"), 0xE306_9283);
}
}