#[derive(Debug, Clone)]
pub struct AisPayload {
pub payload: String,
pub fill_bits: u8,
pub channel: char,
}
#[derive(Debug, Default)]
struct FragmentSlot {
total: u8,
received: u8,
payload: String,
}
const MAX_PAYLOAD_SIZE: usize = 256;
const MAX_FRAGMENTS: u8 = 5;
pub struct FragmentCollector {
slots: [Option<FragmentSlot>; 10],
}
impl FragmentCollector {
pub fn new() -> Self {
Self {
slots: Default::default(),
}
}
pub fn process(&mut self, fields: &[&str]) -> Option<AisPayload> {
if fields.len() < 6 {
return None;
}
let total: u8 = fields[0].parse().ok()?;
let frag_num: u8 = fields[1].parse().ok()?;
let msg_id_str = fields[2];
let channel = fields[3].chars().next().unwrap_or('A');
let payload = fields[4];
let fill_bits: u8 = fields[5].parse().unwrap_or(0);
if total == 0 || frag_num == 0 || frag_num > total || total > MAX_FRAGMENTS {
return None;
}
if total == 1 {
return Some(AisPayload {
payload: payload.to_string(),
fill_bits,
channel,
});
}
let msg_id: usize = msg_id_str.parse().ok()?;
if msg_id > 9 {
return None;
}
if frag_num == 1 {
if payload.len() > MAX_PAYLOAD_SIZE {
return None;
}
self.slots[msg_id] = Some(FragmentSlot {
total,
received: 1,
payload: payload.to_string(),
});
None
} else {
let slot = self.slots[msg_id].as_mut()?;
if slot.total != total || slot.received + 1 != frag_num {
self.slots[msg_id] = None;
return None;
}
if slot.payload.len() + payload.len() > MAX_PAYLOAD_SIZE {
self.slots[msg_id] = None;
return None;
}
slot.payload.push_str(payload);
slot.received = frag_num;
if frag_num == total {
let completed = self.slots[msg_id].take()?;
Some(AisPayload {
payload: completed.payload,
fill_bits,
channel,
})
} else {
None
}
}
}
}
impl Default for FragmentCollector {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn multi_fragment() {
let mut c = FragmentCollector::new();
let r1 = c.process(&[
"2",
"1",
"0",
"A",
"53brRt4000010SG700iE@LE8@Tp4000000000153P615t0Ht0SCkjH4jC1C",
"0",
]);
assert!(r1.is_none());
let r2 = c.process(&["2", "2", "0", "A", "`0000000001", "2"]);
assert!(r2.is_some());
let p = r2.expect("valid");
assert!(p.payload.starts_with("53brRt"));
assert!(p.payload.ends_with("`0000000001"));
assert_eq!(p.fill_bits, 2);
}
#[test]
fn out_of_sequence_discards() {
let mut c = FragmentCollector::new();
let _ = c.process(&["3", "1", "1", "A", "AAAA", "0"]);
let r = c.process(&["3", "3", "1", "A", "CCCC", "0"]);
assert!(r.is_none());
assert!(c.slots[1].is_none());
}
#[test]
fn single_fragment() {
let mut c = FragmentCollector::new();
let result = c.process(&["1", "1", "", "A", "13u@Dt002s000000000000000000", "0"]);
assert!(result.is_some());
let p = result.expect("valid");
assert_eq!(p.payload, "13u@Dt002s000000000000000000");
assert_eq!(p.fill_bits, 0);
assert_eq!(p.channel, 'A');
}
#[test]
fn total_count_mismatch_discards() {
let mut c = FragmentCollector::new();
let _ = c.process(&["2", "1", "0", "A", "AAAA", "0"]);
let r = c.process(&["3", "2", "0", "A", "BBBB", "0"]);
assert!(r.is_none());
assert!(c.slots[0].is_none());
}
#[test]
fn too_few_fields() {
let mut c = FragmentCollector::new();
assert!(c.process(&["1", "1"]).is_none());
}
#[test]
fn zero_total_rejected() {
let mut c = FragmentCollector::new();
assert!(c.process(&["0", "1", "", "A", "payload", "0"]).is_none());
}
}