use std::io::{self, Read, Write};
pub const MAX_PAYLOAD: u32 = 64 * 1024 * 1024;
pub const T_HELLO: u8 = 0x01;
pub const T_MANIFEST_CHUNK: u8 = 0x02;
pub const T_MANIFEST_DONE: u8 = 0x03;
pub const T_PLAN_LOCAL: u8 = 0x04;
pub const T_PLAN_REMOTE: u8 = 0x05;
pub const T_PLAN_DONE: u8 = 0x06;
pub const T_DATA: u8 = 0x07;
pub const T_VERIFY: u8 = 0x08;
pub const T_COMMIT_ACK: u8 = 0x09;
pub const T_NOT_MODIFIED: u8 = 0x0A;
pub const T_ERROR: u8 = 0x0B;
pub const T_TREE_BEGIN: u8 = 0x0C;
pub const T_TREE_END: u8 = 0x0D;
pub const T_FILE_SYNC: u8 = 0x0E;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Frame {
pub ftype: u8,
pub payload: Vec<u8>,
}
impl Frame {
pub fn new(ftype: u8, payload: Vec<u8>) -> Self {
Self { ftype, payload }
}
pub fn encode(&self, out: &mut impl Write) -> io::Result<()> {
if self.payload.len() as u64 > MAX_PAYLOAD as u64 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"payload of {} bytes exceeds {}",
self.payload.len(),
MAX_PAYLOAD
),
));
}
out.write_all(&[self.ftype])?;
out.write_all(&(self.payload.len() as u32).to_le_bytes())?;
out.write_all(&self.payload)
}
pub fn encoded_len(&self) -> usize {
1 + 4 + self.payload.len()
}
}
#[derive(Debug)]
pub enum FrameError {
Eof,
Truncated,
Io(io::Error),
}
impl std::fmt::Display for FrameError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FrameError::Eof => write!(f, "connection closed"),
FrameError::Truncated => write!(f, "connection closed mid-frame"),
FrameError::Io(e) => write!(f, "io error: {}", e),
}
}
}
impl From<io::Error> for FrameError {
fn from(e: io::Error) -> Self {
if e.kind() == io::ErrorKind::UnexpectedEof {
FrameError::Truncated
} else {
FrameError::Io(e)
}
}
}
pub fn read_frame(inp: &mut impl Read) -> Result<Frame, FrameError> {
let mut first = [0u8; 1];
match inp.read_exact(&mut first) {
Ok(()) => {}
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Err(FrameError::Eof),
Err(e) => return Err(FrameError::Io(e)),
}
let mut rest = [0u8; 4];
if let Err(e) = inp.read_exact(&mut rest) {
return Err(FrameError::from(e));
}
let ftype = first[0];
let len = u32::from_le_bytes(rest);
if len > MAX_PAYLOAD {
return Err(FrameError::Io(io::Error::new(
io::ErrorKind::InvalidData,
format!("frame payload of {} bytes exceeds {}", len, MAX_PAYLOAD),
)));
}
let mut payload = vec![0u8; len as usize];
if let Err(e) = inp.read_exact(&mut payload) {
return Err(FrameError::from(e));
}
Ok(Frame { ftype, payload })
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn round_trip() {
for payload_len in [0usize, 1, 5, 44, 1000, 3 * 1024 * 1024] {
let frame = Frame::new(T_DATA, vec![0xABu8; payload_len]);
let mut buf = Vec::new();
frame.encode(&mut buf).unwrap();
assert_eq!(buf.len(), frame.encoded_len());
let mut cursor = Cursor::new(buf);
let decoded = read_frame(&mut cursor).unwrap();
assert_eq!(decoded, frame);
}
}
#[test]
fn multiple_frames_sequential() {
let frames = vec![
Frame::new(T_HELLO, vec![1, 2, 3]),
Frame::new(T_MANIFEST_DONE, vec![]),
Frame::new(T_PLAN_REMOTE, vec![0x55; 44]),
Frame::new(T_COMMIT_ACK, vec![]),
];
let mut buf = Vec::new();
for f in &frames {
f.encode(&mut buf).unwrap();
}
let mut cursor = Cursor::new(buf);
for expected in &frames {
assert_eq!(read_frame(&mut cursor).unwrap(), *expected);
}
assert!(matches!(read_frame(&mut cursor), Err(FrameError::Eof)));
}
#[test]
fn oversized_length_rejected() {
let mut buf = Vec::new();
buf.push(T_DATA);
buf.extend_from_slice(&(MAX_PAYLOAD + 1).to_le_bytes());
let mut cursor = Cursor::new(buf);
let err = read_frame(&mut cursor).unwrap_err();
assert!(matches!(err, FrameError::Io(_)));
}
#[test]
fn truncated_payload_detected() {
let frame = Frame::new(T_DATA, vec![0x11; 100]);
let mut buf = Vec::new();
frame.encode(&mut buf).unwrap();
buf.truncate(5 + 40); let mut cursor = Cursor::new(buf);
assert!(matches!(
read_frame(&mut cursor),
Err(FrameError::Truncated)
));
}
#[test]
fn empty_frame_round_trip() {
let frame = Frame::new(T_NOT_MODIFIED, vec![]);
let mut buf = Vec::new();
frame.encode(&mut buf).unwrap();
assert_eq!(buf.len(), 5);
let mut cursor = Cursor::new(buf);
assert_eq!(read_frame(&mut cursor).unwrap(), frame);
}
#[test]
fn truncated_header_detected() {
let buf = [T_DATA, 0x01]; let mut cursor = Cursor::new(buf);
assert!(matches!(
read_frame(&mut cursor),
Err(FrameError::Truncated)
));
}
#[test]
fn encode_rejects_oversized_payload() {
let payload = vec![0u8; (MAX_PAYLOAD + 1) as usize];
let frame = Frame::new(T_DATA, payload);
let mut buf = Vec::new();
assert!(frame.encode(&mut buf).is_err());
}
}