use super::sentence::{Channel, Sentence};
use crate::error::FragmentError;
const MAX_FRAGMENTS: u8 = 9;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CompleteMessage<'a> {
pub armored: &'a [u8],
pub fill_bits: u8,
pub channel: Channel,
}
#[derive(Debug)]
pub struct FragmentAssembler<const N: usize> {
buf: [u8; N],
len: usize,
expected_fragments: u8,
next_fragment: u8,
seq_id: Option<u8>,
channel: Channel,
fill_bits: u8,
}
impl<const N: usize> Default for FragmentAssembler<N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> FragmentAssembler<N> {
#[must_use]
pub const fn new() -> Self {
Self {
buf: [0u8; N],
len: 0,
expected_fragments: 0,
next_fragment: 0,
seq_id: None,
channel: Channel::Other(0),
fill_bits: 0,
}
}
pub fn reset(&mut self) {
self.len = 0;
self.expected_fragments = 0;
self.next_fragment = 0;
self.seq_id = None;
}
pub fn push<'a>(
&'a mut self,
sentence: &Sentence<'_>,
) -> Result<Option<CompleteMessage<'a>>, FragmentError> {
if sentence.fragment_count > MAX_FRAGMENTS {
return Err(FragmentError::TooManyFragments);
}
if sentence.fragment_number == 1 {
self.reset();
self.expected_fragments = sentence.fragment_count;
self.next_fragment = 1;
self.seq_id = sentence.seq_id;
self.channel = sentence.channel;
} else {
if sentence.fragment_number != self.next_fragment {
return Err(FragmentError::OutOfOrder);
}
if sentence.seq_id != self.seq_id {
return Err(FragmentError::SequenceIdMismatch);
}
if sentence.channel != self.channel {
return Err(FragmentError::ChannelMismatch);
}
}
let new_len = self.len + sentence.payload.len();
if new_len > N {
return Err(FragmentError::BufferOverflow);
}
self.buf[self.len..new_len].copy_from_slice(sentence.payload);
self.len = new_len;
self.fill_bits = sentence.fill_bits;
self.next_fragment += 1;
if sentence.fragment_number == sentence.fragment_count {
Ok(Some(CompleteMessage {
armored: &self.buf[..self.len],
fill_bits: self.fill_bits,
channel: self.channel,
}))
} else {
Ok(None)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn frag(line: &str) -> Sentence<'_> {
Sentence::parse(line).unwrap()
}
#[test]
fn single_part_completes_immediately() {
let mut a = FragmentAssembler::<64>::new();
let s = frag("!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C");
let complete = a.push(&s).unwrap().unwrap();
assert_eq!(complete.armored, b"15M67FC000G?ufbE`FepT@3n00Sa");
}
#[test]
fn two_part_reassembles_in_order() {
let p1 = "!AIVDM,2,1,7,B,15M67FC000,0*6C";
let p2 = "!AIVDM,2,2,7,B,0G?ufbE`FepT@3n00Sa,0*26";
let mut a = FragmentAssembler::<64>::new();
assert!(a.push(&frag(p1)).unwrap().is_none());
let complete = a.push(&frag(p2)).unwrap().unwrap();
assert_eq!(complete.armored, b"15M67FC0000G?ufbE`FepT@3n00Sa");
}
#[test]
fn out_of_order_fragment_is_rejected() {
let p1 = "!AIVDM,2,1,7,B,15M67FC000,0*6C";
let p2 = "!AIVDM,2,2,7,B,0G?ufbE`FepT@3n00Sa,0*26";
let mut a = FragmentAssembler::<64>::new();
assert_eq!(a.push(&frag(p2)).unwrap_err(), FragmentError::OutOfOrder);
assert!(a.push(&frag(p1)).unwrap().is_none());
}
#[test]
fn buffer_overflow_is_reported() {
let p1 = "!AIVDM,2,1,7,B,15M67FC000,0*6C";
let p2 = "!AIVDM,2,2,7,B,0G?ufbE`FepT@3n00Sa,0*26";
let mut a = FragmentAssembler::<15>::new();
assert!(a.push(&frag(p1)).unwrap().is_none());
assert_eq!(
a.push(&frag(p2)).unwrap_err(),
FragmentError::BufferOverflow
);
}
}