use crate::error::{DecodeError, DecodeResult};
pub const DEFAULT_MAX_MESSAGE_BYTES: usize = 268_435_456;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SegmentOutcome {
Complete(Vec<u8>),
Pending,
Control(Vec<u8>),
}
struct Pending {
header: [u8; 8],
payloads: Vec<Vec<u8>>,
total: usize,
}
pub struct SegmentReassembler {
max_bytes: usize,
pending: Option<Pending>,
}
impl SegmentReassembler {
pub fn new() -> Self {
Self::with_max_bytes(DEFAULT_MAX_MESSAGE_BYTES)
}
pub fn with_max_bytes(max_bytes: usize) -> Self {
Self { max_bytes, pending: None }
}
pub fn pending_bytes(&self) -> usize {
self.pending.as_ref().map_or(0, |p| p.total)
}
pub fn reset(&mut self) {
self.pending = None;
}
pub fn push(&mut self, header: [u8; 8], payload: Vec<u8>) -> DecodeResult<SegmentOutcome> {
let flags = header[2];
if (flags & 0x01) != 0 {
let mut out = Vec::with_capacity(8 + payload.len());
out.extend_from_slice(&header);
out.extend_from_slice(&payload);
return Ok(SegmentOutcome::Control(out));
}
let command = header[3];
match (flags & 0x30) >> 4 {
0 => {
if let Some(p) = self.pending.take() {
return Err(DecodeError::SegmentInterrupted {
expected: p.header[3],
got: command,
});
}
let mut out = Vec::with_capacity(8 + payload.len());
out.extend_from_slice(&header);
out.extend_from_slice(&payload);
Ok(SegmentOutcome::Complete(out))
}
1 => {
if self.pending.take().is_some() {
return Err(DecodeError::UnexpectedSegment { flags });
}
if payload.len() > self.max_bytes {
return Err(DecodeError::MessageTooLarge {
total: payload.len(),
limit: self.max_bytes,
});
}
self.pending = Some(Pending {
header,
total: payload.len(),
payloads: vec![payload],
});
Ok(SegmentOutcome::Pending)
}
code => {
let mut p = match self.pending.take() {
Some(p) => p,
None => return Err(DecodeError::UnexpectedSegment { flags }),
};
if p.header[3] != command || (p.header[2] & 0x40) != (flags & 0x40) {
return Err(DecodeError::SegmentCommandMismatch {
expected: p.header[3],
got: command,
});
}
let total = p.total + payload.len();
if total > self.max_bytes {
return Err(DecodeError::MessageTooLarge { total, limit: self.max_bytes });
}
p.total = total;
p.payloads.push(payload);
if code == 2 {
Ok(SegmentOutcome::Complete(finish(p)))
} else {
self.pending = Some(p);
Ok(SegmentOutcome::Pending)
}
}
}
}
}
impl Default for SegmentReassembler {
fn default() -> Self {
Self::new()
}
}
fn finish(p: Pending) -> Vec<u8> {
let mut header = p.header;
let is_be = (header[2] & 0x80) != 0;
header[2] &= !0x30;
let total = p.total as u32;
let len = if is_be { total.to_be_bytes() } else { total.to_le_bytes() };
header[4..8].copy_from_slice(&len);
let mut out = Vec::with_capacity(8 + p.total);
out.extend_from_slice(&header);
for payload in p.payloads {
out.extend_from_slice(&payload);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::DecodeError;
fn hdr(command: u8, seg: u8, payload_len: u32, is_be: bool) -> [u8; 8] {
let mut flags = 0x40u8; if is_be {
flags |= 0x80;
}
flags |= seg << 4;
let mut h = [0u8; 8];
h[0] = 0xCA;
h[1] = 2;
h[2] = flags;
h[3] = command;
let l = if is_be { payload_len.to_be_bytes() } else { payload_len.to_le_bytes() };
h[4..8].copy_from_slice(&l);
h
}
fn control_hdr() -> [u8; 8] {
let mut h = hdr(3, 0, 0, false);
h[2] |= 0x01;
h
}
#[test]
fn three_segments_reassemble_into_one_message() {
let mut r = SegmentReassembler::new();
assert_eq!(r.push(hdr(13, 1, 2, false), vec![1, 2]).unwrap(), SegmentOutcome::Pending);
assert_eq!(r.push(hdr(13, 3, 2, false), vec![3, 4]).unwrap(), SegmentOutcome::Pending);
let out = match r.push(hdr(13, 2, 2, false), vec![5, 6]).unwrap() {
SegmentOutcome::Complete(b) => b,
other => panic!("expected Complete, got {other:?}"),
};
assert_eq!(&out[8..], &[1, 2, 3, 4, 5, 6]);
assert_eq!(out[0], 0xCA, "magic byte preserved");
assert_eq!(out[1], 2, "version byte preserved");
assert_eq!(out[2] & 0x30, 0, "segment bits must be cleared");
assert_eq!(u32::from_le_bytes(out[4..8].try_into().unwrap()), 6);
assert_eq!(out[3], 13, "command byte preserved");
assert_eq!(r.pending_bytes(), 0);
}
#[test]
fn big_endian_length_is_rewritten_big_endian() {
let mut r = SegmentReassembler::new();
r.push(hdr(13, 1, 2, true), vec![1, 2]).unwrap();
let out = match r.push(hdr(13, 2, 1, true), vec![3]).unwrap() {
SegmentOutcome::Complete(b) => b,
other => panic!("expected Complete, got {other:?}"),
};
assert_eq!(u32::from_be_bytes(out[4..8].try_into().unwrap()), 3);
}
#[test]
fn unsegmented_message_passes_straight_through() {
let mut r = SegmentReassembler::new();
let out = match r.push(hdr(11, 0, 3, false), vec![7, 8, 9]).unwrap() {
SegmentOutcome::Complete(b) => b,
other => panic!("expected Complete, got {other:?}"),
};
assert_eq!(&out[8..], &[7, 8, 9]);
}
#[test]
fn control_frame_between_segments_does_not_disturb_reassembly() {
let mut r = SegmentReassembler::new();
r.push(hdr(13, 1, 2, false), vec![1, 2]).unwrap();
match r.push(control_hdr(), vec![]).unwrap() {
SegmentOutcome::Control(b) => assert_eq!(b.len(), 8),
other => panic!("expected Control, got {other:?}"),
}
assert_eq!(r.pending_bytes(), 2, "pending state survives the control frame");
let out = match r.push(hdr(13, 2, 2, false), vec![3, 4]).unwrap() {
SegmentOutcome::Complete(b) => b,
other => panic!("expected Complete, got {other:?}"),
};
assert_eq!(&out[8..], &[1, 2, 3, 4]);
}
#[test]
fn unsegmented_message_mid_reassembly_is_an_error() {
let mut r = SegmentReassembler::new();
r.push(hdr(13, 1, 2, false), vec![1, 2]).unwrap();
assert_eq!(
r.push(hdr(11, 0, 1, false), vec![9]).unwrap_err(),
DecodeError::SegmentInterrupted { expected: 13, got: 11 }
);
assert_eq!(r.pending_bytes(), 0, "state is reset after the error");
}
#[test]
fn orphan_middle_and_last_segments_are_errors() {
let mut r = SegmentReassembler::new();
assert!(matches!(
r.push(hdr(13, 3, 1, false), vec![1]).unwrap_err(),
DecodeError::UnexpectedSegment { .. }
));
assert!(matches!(
r.push(hdr(13, 2, 1, false), vec![1]).unwrap_err(),
DecodeError::UnexpectedSegment { .. }
));
}
#[test]
fn second_first_segment_while_pending_is_an_error() {
let mut r = SegmentReassembler::new();
r.push(hdr(13, 1, 1, false), vec![1]).unwrap();
assert!(matches!(
r.push(hdr(13, 1, 1, false), vec![2]).unwrap_err(),
DecodeError::UnexpectedSegment { .. }
));
assert_eq!(r.pending_bytes(), 0);
}
#[test]
fn command_mismatch_across_segments_is_an_error() {
let mut r = SegmentReassembler::new();
r.push(hdr(13, 1, 1, false), vec![1]).unwrap();
assert_eq!(
r.push(hdr(11, 3, 1, false), vec![2]).unwrap_err(),
DecodeError::SegmentCommandMismatch { expected: 13, got: 11 }
);
assert_eq!(r.pending_bytes(), 0);
}
#[test]
fn exceeding_the_cap_errors_and_leaves_the_reassembler_reusable() {
let mut r = SegmentReassembler::with_max_bytes(4);
r.push(hdr(13, 1, 3, false), vec![1, 2, 3]).unwrap();
assert_eq!(
r.push(hdr(13, 3, 3, false), vec![4, 5, 6]).unwrap_err(),
DecodeError::MessageTooLarge { total: 6, limit: 4 }
);
assert_eq!(r.pending_bytes(), 0);
let out = match r.push(hdr(11, 0, 1, false), vec![9]).unwrap() {
SegmentOutcome::Complete(b) => b,
other => panic!("expected Complete, got {other:?}"),
};
assert_eq!(&out[8..], &[9]);
}
#[test]
fn reset_discards_pending_state() {
let mut r = SegmentReassembler::new();
r.push(hdr(13, 1, 2, false), vec![1, 2]).unwrap();
assert_eq!(r.pending_bytes(), 2);
r.reset();
assert_eq!(r.pending_bytes(), 0);
}
}