use crate::error::{Error, Result};
pub const MAGIC: u8 = 0x24;
pub const HEADER_LEN: usize = 4;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct InterleavedFrame {
pub channel: u8,
pub payload: Vec<u8>,
}
impl InterleavedFrame {
pub fn new(channel: u8, payload: impl Into<Vec<u8>>) -> Self {
InterleavedFrame {
channel,
payload: payload.into(),
}
}
pub fn serialized_len(&self) -> usize {
HEADER_LEN + self.payload.len()
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(self.serialized_len());
self.serialize_into(&mut out)?;
Ok(out)
}
pub fn serialize_into(&self, out: &mut Vec<u8>) -> Result<()> {
let len: u16 = u16::try_from(self.payload.len()).map_err(|_| {
Error::InterleavedFrame(format!(
"payload of {} bytes exceeds 16-bit length field",
self.payload.len()
))
})?;
out.push(MAGIC);
out.push(self.channel);
out.extend_from_slice(&len.to_be_bytes());
out.extend_from_slice(&self.payload);
Ok(())
}
pub fn parse(buf: &[u8]) -> Result<Option<(InterleavedFrame, usize)>> {
if buf.len() < HEADER_LEN {
return Ok(None);
}
if buf[0] != MAGIC {
return Err(Error::InterleavedFrame(format!(
"expected '$' (0x{MAGIC:02X}), found 0x{:02X}",
buf[0]
)));
}
let channel = buf[1];
let len = u16::from_be_bytes([buf[2], buf[3]]) as usize;
let total = HEADER_LEN + len;
if buf.len() < total {
return Ok(None);
}
let frame = InterleavedFrame {
channel,
payload: buf[HEADER_LEN..total].to_vec(),
};
Ok(Some((frame, total)))
}
}
pub fn parse_frames(buf: &[u8]) -> Result<(Vec<InterleavedFrame>, usize)> {
let mut frames = Vec::new();
let mut offset = 0usize;
while offset < buf.len() {
match InterleavedFrame::parse(&buf[offset..])? {
Some((frame, consumed)) => {
frames.push(frame);
offset += consumed;
}
None => break, }
}
Ok((frames, buf.len() - offset))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_frame_round_trip() {
let payload: Vec<u8> = (0u8..17).collect();
let frame = InterleavedFrame::new(0, payload.clone());
let bytes = frame.to_bytes().unwrap();
assert_eq!(bytes[0], MAGIC);
assert_eq!(bytes[1], 0);
assert_eq!(&bytes[2..4], &(payload.len() as u16).to_be_bytes());
let (parsed, consumed) = InterleavedFrame::parse(&bytes).unwrap().unwrap();
assert_eq!(parsed, frame);
assert_eq!(consumed, bytes.len());
}
#[test]
fn frame_channel_byte_reflects_channel() {
let f = InterleavedFrame::new(1u8, vec![9u8, 8, 7]);
let bytes = f.to_bytes().unwrap();
assert_eq!(bytes[0], MAGIC);
assert_eq!(bytes[1], 1, "channel byte must reflect channel 1");
assert_eq!(u16::from_be_bytes([bytes[2], bytes[3]]), 3);
let (parsed, consumed) = InterleavedFrame::parse(&bytes).unwrap().unwrap();
assert_eq!(consumed, bytes.len());
assert_eq!(parsed.channel, 1);
assert_eq!(parsed.payload, vec![9, 8, 7]);
}
#[test]
fn two_frames_plus_partial_returns_two_and_remainder() {
let f0 = InterleavedFrame::new(0, vec![0xAA; 10]);
let f1 = InterleavedFrame::new(1, vec![0xBB; 4]);
let mut buf = Vec::new();
buf.extend_from_slice(&f0.to_bytes().unwrap());
buf.extend_from_slice(&f1.to_bytes().unwrap());
let partial = [MAGIC, 0x00, 0x00, 0x08, 1, 2, 3];
buf.extend_from_slice(&partial);
let (frames, remainder) = parse_frames(&buf).unwrap();
assert_eq!(frames.len(), 2);
assert_eq!(frames[0], f0);
assert_eq!(frames[1], f1);
assert_eq!(remainder, partial.len());
assert_eq!(&buf[buf.len() - remainder..], &partial);
}
#[test]
fn header_only_partial_is_remainder() {
let buf = [MAGIC, 0x00];
let (frames, remainder) = parse_frames(&buf).unwrap();
assert!(frames.is_empty());
assert_eq!(remainder, 2);
}
#[test]
fn wrong_magic_bites() {
let buf = [b'R', 0, 0, 1, 9];
assert!(InterleavedFrame::parse(&buf).is_err());
}
}