use alloc::vec::Vec;
use crate::sndu::{
is_end_indicator, BASE_HEADER_LEN, D_BIT_MASK, END_INDICATOR_LENGTH, LENGTH_MASK, PADDING_BYTE,
};
pub const TS_PAYLOAD_LEN: usize = 184;
#[derive(Debug, Default, Clone)]
pub struct UleReceiver {
partial: Vec<u8>,
expected: usize,
started: bool,
}
impl UleReceiver {
pub fn new() -> Self {
Self::default()
}
pub fn reset(&mut self) {
self.partial.clear();
self.expected = 0;
self.started = false;
}
pub fn in_reassembly(&self) -> bool {
self.started && !self.partial.is_empty()
}
pub fn push(&mut self, payload: &[u8], pusi: bool) -> Vec<Vec<u8>> {
let mut out = Vec::new();
if pusi {
if payload.is_empty() {
return out;
}
let pp = payload[0] as usize;
let pp_region = &payload[1..];
if pp > pp_region.len() {
self.reset();
return out;
}
if self.in_reassembly() {
if self.expected != 0 {
let remaining = self.expected.saturating_sub(self.partial.len());
if remaining != 0 && pp != remaining {
self.reset();
} else {
self.feed_continuation(&pp_region[..pp], &mut out);
}
} else {
self.feed_continuation(&pp_region[..pp], &mut out);
}
} else {
self.partial.clear();
self.expected = 0;
}
self.started = true;
self.walk_new_sndus(&pp_region[pp..], &mut out);
} else {
if self.in_reassembly() {
self.feed_continuation(payload, &mut out);
}
}
out
}
fn feed_continuation(&mut self, chunk: &[u8], out: &mut Vec<Vec<u8>>) {
self.partial.extend_from_slice(chunk);
self.maybe_finish(out);
}
fn walk_new_sndus(&mut self, mut region: &[u8], out: &mut Vec<Vec<u8>>) {
loop {
if region.is_empty() {
return;
}
if region[0] == PADDING_BYTE {
if is_end_indicator(region) {
}
return;
}
if region.len() < BASE_HEADER_LEN {
self.partial.clear();
self.partial.extend_from_slice(region);
self.expected = 0;
return;
}
let first = u16::from_be_bytes([region[0], region[1]]);
let length = (first & LENGTH_MASK) as usize;
if (first & LENGTH_MASK) == END_INDICATOR_LENGTH && (first & D_BIT_MASK) != 0 {
return;
}
let total = BASE_HEADER_LEN + length;
if region.len() >= total {
out.push(region[..total].to_vec());
region = ®ion[total..];
} else {
self.partial.clear();
self.partial.extend_from_slice(region);
self.expected = total;
return;
}
}
}
fn maybe_finish(&mut self, out: &mut Vec<Vec<u8>>) {
if self.expected == 0 && self.partial.len() >= BASE_HEADER_LEN {
let first = u16::from_be_bytes([self.partial[0], self.partial[1]]);
let length = (first & LENGTH_MASK) as usize;
self.expected = BASE_HEADER_LEN + length;
}
if self.expected != 0 && self.partial.len() >= self.expected {
let total = self.expected;
out.push(self.partial[..total].to_vec());
let rest: Vec<u8> = self.partial[total..].to_vec();
self.partial.clear();
self.expected = 0;
if !rest.is_empty() {
self.walk_new_sndus(&rest, out);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sndu::Sndu;
use crate::type_field::TypeField;
fn make_sndu(pdu: &[u8]) -> Vec<u8> {
let s = Sndu::new(TypeField::EtherType(0x0800), None, pdu);
let mut b = alloc::vec![0u8; s.serialized_len()];
s.serialize_into(&mut b).unwrap();
b
}
#[test]
fn fragmented_across_two_packets() {
let pdu: Vec<u8> = (0u8..40).collect();
let sndu = make_sndu(&pdu);
assert!(sndu.len() > 20);
let mut rx = UleReceiver::new();
let mut p1 = alloc::vec![0x00u8];
p1.extend_from_slice(&sndu[..20]);
let done = rx.push(&p1, true);
assert!(done.is_empty(), "still reassembling");
assert!(rx.in_reassembly());
let mut p2 = sndu[20..].to_vec();
p2.extend_from_slice(&[0xFF, 0xFF]);
let done = rx.push(&p2, false);
assert_eq!(done.len(), 1);
assert_eq!(done[0], sndu);
assert_eq!(Sndu::parse(&done[0]).unwrap().pdu(), &pdu[..]);
}
#[test]
fn two_packed_sndus_one_packet() {
let a = make_sndu(&[0xAA; 5]);
let b = make_sndu(&[0xBB; 7]);
let mut payload = alloc::vec![0x00u8]; payload.extend_from_slice(&a);
payload.extend_from_slice(&b);
payload.extend_from_slice(&[0xFF, 0xFF, 0xFF]);
let mut rx = UleReceiver::new();
let done = rx.push(&payload, true);
assert_eq!(done.len(), 2, "both packed SNDUs extracted");
assert_eq!(done[0], a);
assert_eq!(done[1], b);
}
#[test]
fn continuation_then_packed_with_pp() {
let a = make_sndu(&[0x11; 30]); let b = make_sndu(&[0x22; 4]);
let mut rx = UleReceiver::new();
let mut p1 = alloc::vec![0x00u8];
p1.extend_from_slice(&a[..15]);
assert!(rx.push(&p1, true).is_empty());
let rest_a = a.len() - 15;
let mut p2 = alloc::vec![rest_a as u8];
p2.extend_from_slice(&a[15..]); p2.extend_from_slice(&b); p2.push(0xFF);
let done = rx.push(&p2, true);
assert_eq!(done.len(), 2);
assert_eq!(done[0], a);
assert_eq!(done[1], b);
}
#[test]
fn idle_until_pusi() {
let mut rx = UleReceiver::new();
let done = rx.push(&[0x01, 0x02, 0x03], false);
assert!(done.is_empty());
assert!(!rx.in_reassembly());
}
#[test]
fn bad_payload_pointer_resets() {
let mut rx = UleReceiver::new();
let done = rx.push(&[200u8, 0x00, 0x10, 0x08, 0x00], true);
assert!(done.is_empty());
assert!(!rx.in_reassembly());
}
#[test]
fn pusi_mid_reassembly_wrong_pp_discards_partial() {
let pdu: Vec<u8> = (0u8..46).collect();
let sndu = make_sndu(&pdu);
assert_eq!(
sndu.len(),
54,
"expected 54 bytes: 4 header + 46 PDU + 4 CRC"
);
let mut rx = UleReceiver::new();
let mut p1 = alloc::vec![0x00u8]; p1.extend_from_slice(&sndu[..20]);
let done = rx.push(&p1, true);
assert!(done.is_empty());
assert!(rx.in_reassembly(), "should be mid-reassembly after p1");
const WRONG_PP: usize = 5;
let mut p2 = alloc::vec![WRONG_PP as u8];
p2.extend_from_slice(&[0xDEu8; WRONG_PP]);
p2.extend_from_slice(&[0xFF, 0xFF]);
let done2 = rx.push(&p2, true);
assert!(done2.is_empty(), "no SNDUs should complete in p2");
let tail = &sndu[20..]; let done3 = rx.push(tail, false);
for emitted in &done3 {
assert!(
crate::sndu::Sndu::parse(emitted).is_ok(),
"emitted SNDU has invalid CRC — corrupt partial was not discarded"
);
}
assert!(
done3.is_empty(),
"post-fix: idle receiver must discard the continuation (partial was reset)"
);
}
}