use crate::header::{Bbheader, Mode, TsGs, BBHEADER_LEN};
use crate::packet::{CarryOverExtractor, CarryOverStats, NM_UP_SIZE};
const MAX_PLPS: usize = 256;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct BbframePumpStats {
pub header_parse_failures: u64,
pub non_ts_payloads: u64,
pub carry_over: CarryOverStats,
}
pub struct BbframePump {
extractors: [Option<CarryOverExtractor>; MAX_PLPS],
out: Vec<[u8; NM_UP_SIZE]>,
up_buf: Vec<[u8; NM_UP_SIZE]>,
stats: BbframePumpStats,
}
impl BbframePump {
#[must_use]
pub fn new() -> Self {
Self {
extractors: std::array::from_fn(|_| None),
out: Vec::new(),
up_buf: Vec::new(),
stats: BbframePumpStats::default(),
}
}
pub fn feed(&mut self, plp_id: u8, df_bytes: &[u8]) -> &[[u8; NM_UP_SIZE]] {
self.out.clear();
if df_bytes.len() < BBHEADER_LEN {
self.stats.header_parse_failures += 1;
return &self.out;
}
let hdr = match Bbheader::parse(df_bytes) {
Ok(h) => h,
Err(_) => {
self.stats.header_parse_failures += 1;
return &self.out;
}
};
if hdr.matype.ts_gs != TsGs::Ts {
self.stats.non_ts_payloads += 1;
return &self.out;
}
let header_bytes: [u8; BBHEADER_LEN] = match df_bytes[..BBHEADER_LEN].try_into() {
Ok(b) => b,
Err(_) => {
self.stats.header_parse_failures += 1;
return &self.out;
}
};
let data_field = &df_bytes[BBHEADER_LEN..];
let idx = plp_id as usize;
let extractor = self.extractors[idx].get_or_insert_with(CarryOverExtractor::new);
match hdr.mode {
Mode::Normal => {
extractor.feed_nm_into(&header_bytes, data_field, &mut self.up_buf);
}
Mode::HighEfficiency => {
extractor.feed_hem_into(
&header_bytes,
data_field,
hdr.matype.npd,
&mut self.up_buf,
);
}
}
self.out.append(&mut self.up_buf);
&self.out
}
#[must_use]
pub fn stats(&self) -> BbframePumpStats {
let mut carry_over = CarryOverStats::default();
for ext in self.extractors.iter().flatten() {
let s = ext.stats();
carry_over.npd_unsupported += s.npd_unsupported;
carry_over.header_parse_failures += s.header_parse_failures;
carry_over.mode_mismatches += s.mode_mismatches;
carry_over.partial_discards += s.partial_discards;
}
BbframePumpStats {
header_parse_failures: self.stats.header_parse_failures,
non_ts_payloads: self.stats.non_ts_payloads,
carry_over,
}
}
}
impl Default for BbframePump {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crc::crc8;
use crate::header::{Matype, TsGs};
const TS_SYNC: u8 = 0x47;
const TS_LEN: usize = NM_UP_SIZE;
fn inner_packet() -> [u8; TS_LEN] {
let mut p = [0xAAu8; TS_LEN];
p[0] = TS_SYNC;
p[1] = 0x41; p[2] = 0x00;
p[3] = 0x10; p
}
fn nm_bbframe(inner: &[u8; TS_LEN]) -> Vec<u8> {
let hdr = Bbheader {
matype: Matype {
ts_gs: TsGs::Ts,
sis: true,
ccm: true,
issyi: false,
npd: false,
ext: 0,
isi: 0,
},
upl: 1504,
sync: TS_SYNC,
dfl: 1504,
syncd: 0,
mode: Mode::Normal,
issy_in_header: None,
};
let mut frame = hdr.serialize().to_vec();
let mut data = [0u8; TS_LEN];
data[0] = crc8(&[0u8; TS_LEN]);
data[1..].copy_from_slice(&inner[1..]);
frame.extend_from_slice(&data);
frame
}
fn hem_bbframe(inner: &[u8; TS_LEN]) -> Vec<u8> {
let hdr = Bbheader {
matype: Matype {
ts_gs: TsGs::Ts,
sis: true,
ccm: true,
issyi: false,
npd: false,
ext: 0,
isi: 0,
},
upl: 0,
sync: 0,
dfl: (crate::packet::HEM_UP_SIZE * 8) as u16,
syncd: 0,
mode: Mode::HighEfficiency,
issy_in_header: None,
};
let mut frame = hdr.serialize().to_vec();
frame.extend_from_slice(&inner[1..]);
frame
}
#[test]
fn nm_frame_yields_inner_ts_packet() {
let inner = inner_packet();
let frame = nm_bbframe(&inner);
let mut pump = BbframePump::new();
let pkts = pump.feed(0, &frame);
assert_eq!(pkts.len(), 1, "exactly one inner TS packet expected");
assert_eq!(pkts[0][0], TS_SYNC, "sync byte restored");
assert_eq!(&pkts[0][1..], &inner[1..]);
}
#[test]
fn two_nm_frames_yield_two_packets() {
let inner = inner_packet();
let frame = nm_bbframe(&inner);
let mut pump = BbframePump::new();
let pkts1 = pump.feed(0, &frame).to_vec();
let pkts2 = pump.feed(0, &frame).to_vec();
assert_eq!(pkts1.len(), 1);
assert_eq!(pkts2.len(), 1);
}
#[test]
fn hem_frame_yields_inner_ts_packet() {
let inner = inner_packet();
let frame = hem_bbframe(&inner);
let mut pump = BbframePump::new();
let pkts = pump.feed(0, &frame);
assert_eq!(pkts.len(), 1, "exactly one inner TS packet expected");
assert_eq!(pkts[0][0], TS_SYNC, "sync byte prepended");
assert_eq!(&pkts[0][1..], &inner[1..]);
}
#[test]
fn interleaved_plps_keep_independent_carry_over() {
let inner = inner_packet();
let nm = nm_bbframe(&inner);
let mut pump = BbframePump::new();
let pkts_0a = pump.feed(0, &nm).to_vec();
let pkts_5 = pump.feed(5, &nm).to_vec();
let pkts_0b = pump.feed(0, &nm).to_vec();
assert_eq!(pkts_0a.len(), 1);
assert_eq!(pkts_5.len(), 1);
assert_eq!(pkts_0b.len(), 1);
assert_eq!(pkts_0a[0], pkts_0b[0]);
assert_eq!(pkts_5[0], inner);
}
#[test]
fn short_df_bytes_bumps_header_parse_failures() {
let mut pump = BbframePump::new();
let pkts = pump.feed(0, &[0u8; 5]); assert!(pkts.is_empty());
assert_eq!(pump.stats().header_parse_failures, 1);
}
#[test]
fn bad_bbheader_bumps_header_parse_failures() {
let mut pump = BbframePump::new();
let bad = [0xFFu8; BBHEADER_LEN + 10]; let pkts = pump.feed(0, &bad);
assert!(pkts.is_empty());
assert_eq!(pump.stats().header_parse_failures, 1);
}
#[test]
fn non_ts_matype_bumps_non_ts_payloads() {
let hdr = Bbheader {
matype: Matype {
ts_gs: TsGs::Gse,
sis: true,
ccm: true,
issyi: false,
npd: false,
ext: 0,
isi: 0,
},
upl: 0,
sync: 0,
dfl: 100,
syncd: 0,
mode: Mode::Normal,
issy_in_header: None,
};
let mut frame = hdr.serialize().to_vec();
frame.extend_from_slice(&[0u8; 50]);
let mut pump = BbframePump::new();
let pkts = pump.feed(0, &frame);
assert!(pkts.is_empty());
assert_eq!(pump.stats().non_ts_payloads, 1);
assert_eq!(pump.stats().header_parse_failures, 0); }
#[test]
fn garbage_no_panic_no_output() {
let mut pump = BbframePump::new();
let mut junk = [0u8; 200];
junk[9] = 0xFF;
assert!(pump.feed(0, &junk).is_empty());
assert!(pump.stats().header_parse_failures > 0);
}
#[test]
fn stats_aggregates_across_plps() {
let inner = inner_packet();
let nm = nm_bbframe(&inner);
let mut pump = BbframePump::new();
pump.feed(0, &nm);
pump.feed(1, &nm);
let s = pump.stats();
assert_eq!(s.carry_over.header_parse_failures, 0);
assert_eq!(s.carry_over.mode_mismatches, 0);
}
}