const SEGMENT_DATA_SIZE: usize = 19;
pub fn segment(data: &[u8]) -> Vec<Vec<u8>> {
if data.is_empty() {
return vec![vec![0x00]];
}
let full_segments = data.len() / SEGMENT_DATA_SIZE;
let remainder = data.len() % SEGMENT_DATA_SIZE;
let total_segments = full_segments + if remainder > 0 { 1 } else { 0 };
let max_index = (total_segments - 1) as u8;
let mut segments = Vec::with_capacity(total_segments);
let mut offset = 0;
for i in 0..total_segments {
let chunk_size = if i < full_segments {
SEGMENT_DATA_SIZE
} else {
remainder
};
let mut seg = Vec::with_capacity(1 + chunk_size);
seg.push((max_index << 4) | (i as u8));
seg.extend_from_slice(&data[offset..offset + chunk_size]);
segments.push(seg);
offset += chunk_size;
}
segments
}
pub fn is_last_segment(segment: &[u8]) -> bool {
if segment.is_empty() {
return true;
}
let header = segment[0];
header == 0 || ((header >> 4) & 0x0F) == (header & 0x0F)
}
#[derive(Debug, Default)]
pub struct Reassembler {
segments: Vec<Vec<u8>>,
expected_count: Option<usize>,
}
impl Reassembler {
pub fn new() -> Self {
Self::default()
}
pub fn feed(&mut self, segment: &[u8]) -> Result<Option<Vec<u8>>, SegmentError> {
if segment.is_empty() {
return Err(SegmentError::EmptySegment);
}
let header = segment[0];
let max_index = ((header >> 4) & 0x0F) as usize;
let current_index = (header & 0x0F) as usize;
let expected_count = max_index + 1;
if header == 0x00 {
let data = segment[1..].to_vec();
self.reset();
return Ok(Some(data));
}
if let Some(prev) = self.expected_count {
if prev != expected_count {
self.reset();
return Err(SegmentError::InconsistentMaxIndex);
}
}
self.expected_count = Some(expected_count);
if current_index > max_index {
self.reset();
return Err(SegmentError::IndexOutOfRange {
current: current_index,
max: max_index,
});
}
if self.segments.is_empty() {
self.segments.resize(expected_count, Vec::new());
}
self.segments[current_index] = segment[1..].to_vec();
if is_last_segment(segment)
&& self.segments.iter().all(|s| !s.is_empty())
{
let data: Vec<u8> = self.segments.iter().flat_map(|s| s.iter().copied()).collect();
self.reset();
Ok(Some(data))
} else {
Ok(None)
}
}
fn reset(&mut self) {
self.segments.clear();
self.expected_count = None;
}
}
#[derive(Debug, thiserror::Error)]
pub enum SegmentError {
#[error("empty segment")]
EmptySegment,
#[error("inconsistent max segment index across segments")]
InconsistentMaxIndex,
#[error("segment index {current} exceeds max {max}")]
IndexOutOfRange { current: usize, max: usize },
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_small_packet() {
let data = vec![0x01, 0x05, 0x02, 0x02, 0x05, 0x01]; let segments = segment(&data);
assert_eq!(segments.len(), 1);
assert_eq!(segments[0][0], 0x00); assert_eq!(&segments[0][1..], &data);
}
#[test]
fn exact_19_bytes() {
let data = vec![0xAA; 19];
let segments = segment(&data);
assert_eq!(segments.len(), 1);
assert_eq!(segments[0].len(), 20);
assert_eq!(segments[0][0], 0x00);
}
#[test]
fn two_segments() {
let data = vec![0xBB; 25]; let segments = segment(&data);
assert_eq!(segments.len(), 2);
assert_eq!(segments[0][0], 0x10); assert_eq!(segments[0].len(), 20);
assert_eq!(segments[1][0], 0x11); assert_eq!(segments[1].len(), 7); }
#[test]
fn three_segments() {
let data = vec![0xCC; 40]; let segments = segment(&data);
assert_eq!(segments.len(), 3);
assert_eq!(segments[0][0], 0x20); assert_eq!(segments[1][0], 0x21); assert_eq!(segments[2][0], 0x22); assert_eq!(segments[2].len(), 3); }
#[test]
fn reassemble_single() {
let data = vec![0x01, 0x02, 0x03];
let segments = segment(&data);
let mut r = Reassembler::new();
let result = r.feed(&segments[0]).unwrap();
assert_eq!(result, Some(data));
}
#[test]
fn reassemble_multi() {
let data = vec![0xDD; 30]; let segments = segment(&data);
assert_eq!(segments.len(), 2);
let mut r = Reassembler::new();
assert_eq!(r.feed(&segments[0]).unwrap(), None);
let result = r.feed(&segments[1]).unwrap().unwrap();
assert_eq!(result, data);
}
#[test]
fn roundtrip_large() {
let data: Vec<u8> = (0..=255).collect(); let segments = segment(&data);
assert_eq!(segments.len(), 14);
let mut r = Reassembler::new();
for (i, seg) in segments.iter().enumerate() {
let result = r.feed(seg).unwrap();
if i < segments.len() - 1 {
assert_eq!(result, None);
} else {
assert_eq!(result, Some(data.clone()));
}
}
}
#[test]
fn is_last_segment_check() {
assert!(is_last_segment(&[0x00, 0x01, 0x02])); assert!(is_last_segment(&[0x11, 0x01, 0x02])); assert!(!is_last_segment(&[0x10, 0x01, 0x02])); assert!(is_last_segment(&[0x22, 0x01])); }
}