1use crate::header::{Bbheader, Mode, BBHEADER_LEN};
24
25pub const NM_UP_SIZE: usize = 188;
27
28pub const HEM_UP_SIZE: usize = 187;
30
31pub const TS_SYNC_BYTE: u8 = 0x47;
33
34#[derive(Clone, Copy)]
39#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
40pub struct NmTsIter<'a> {
41 data: &'a [u8],
42 pos: usize,
43}
44
45impl<'a> NmTsIter<'a> {
46 pub fn new(data: &'a [u8]) -> Self {
51 Self { data, pos: 0 }
52 }
53
54 pub fn remaining(self) -> &'a [u8] {
56 self.data.get(self.pos..).unwrap_or(&[])
57 }
58}
59
60impl Iterator for NmTsIter<'_> {
61 type Item = [u8; NM_UP_SIZE];
62
63 fn next(&mut self) -> Option<Self::Item> {
64 if self.pos + NM_UP_SIZE > self.data.len() {
65 return None;
66 }
67 let mut pkt = [0u8; NM_UP_SIZE];
68 pkt[0] = TS_SYNC_BYTE; pkt[1..].copy_from_slice(&self.data[self.pos + 1..self.pos + NM_UP_SIZE]);
70 self.pos += NM_UP_SIZE;
71 Some(pkt)
72 }
73
74 fn size_hint(&self) -> (usize, Option<usize>) {
75 let remaining = self.data.len().saturating_sub(self.pos);
76 let count = remaining / NM_UP_SIZE;
77 (count, Some(count))
78 }
79}
80
81#[derive(Clone, Copy)]
87#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
88pub struct HemTsIter<'a> {
89 data: &'a [u8],
90 pos: usize,
91 npd: bool,
92}
93
94impl<'a> HemTsIter<'a> {
95 pub fn new(data: &'a [u8], npd: bool) -> Self {
100 Self { data, pos: 0, npd }
101 }
102
103 pub fn remaining(self) -> &'a [u8] {
105 self.data.get(self.pos..).unwrap_or(&[])
106 }
107}
108
109impl Iterator for HemTsIter<'_> {
110 type Item = [u8; NM_UP_SIZE];
111
112 fn next(&mut self) -> Option<Self::Item> {
113 let stride = HEM_UP_SIZE + if self.npd { 1 } else { 0 };
114 if self.pos + stride > self.data.len() {
115 return None;
116 }
117 let mut pkt = [0u8; NM_UP_SIZE];
118 pkt[0] = TS_SYNC_BYTE;
119 pkt[1..].copy_from_slice(&self.data[self.pos..self.pos + HEM_UP_SIZE]);
120 self.pos += stride;
121 Some(pkt)
122 }
123
124 fn size_hint(&self) -> (usize, Option<usize>) {
125 let stride = HEM_UP_SIZE + if self.npd { 1 } else { 0 };
126 let remaining = self.data.len().saturating_sub(self.pos);
127 let count = remaining / stride;
128 (count, Some(count))
129 }
130}
131
132#[derive(Clone, Copy)]
137pub enum UpIter<'a> {
138 Normal(NmTsIter<'a>),
140 HighEfficiency(HemTsIter<'a>),
142}
143
144impl Iterator for UpIter<'_> {
145 type Item = [u8; NM_UP_SIZE];
146
147 fn next(&mut self) -> Option<Self::Item> {
148 match self {
149 Self::Normal(it) => it.next(),
150 Self::HighEfficiency(it) => it.next(),
151 }
152 }
153
154 fn size_hint(&self) -> (usize, Option<usize>) {
155 match self {
156 Self::Normal(it) => it.size_hint(),
157 Self::HighEfficiency(it) => it.size_hint(),
158 }
159 }
160}
161
162pub fn up_iter<'a>(data: &'a [u8], bbheader: &Bbheader) -> UpIter<'a> {
168 match bbheader.mode {
169 Mode::Normal => UpIter::Normal(NmTsIter::new(data)),
170 Mode::HighEfficiency => UpIter::HighEfficiency(HemTsIter::new(data, bbheader.matype.npd)),
171 }
172}
173
174pub struct CarryOverExtractor {
183 buf: [u8; NM_UP_SIZE],
185 pos: usize,
187 stats: CarryOverStats,
189}
190
191impl Default for CarryOverExtractor {
192 fn default() -> Self {
193 Self {
194 buf: [0u8; NM_UP_SIZE],
195 pos: 0,
196 stats: CarryOverStats::default(),
197 }
198 }
199}
200
201#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
210#[non_exhaustive]
211pub struct CarryOverStats {
212 pub npd_unsupported: u64,
217 pub header_parse_failures: u64,
219 pub mode_mismatches: u64,
222 pub partial_discards: u64,
225}
226
227impl CarryOverExtractor {
228 pub fn new() -> Self {
230 Self::default()
231 }
232
233 #[must_use]
237 pub fn stats(&self) -> CarryOverStats {
238 self.stats
239 }
240
241 pub fn feed_hem(
249 &mut self,
250 bbheader_bytes: &[u8; BBHEADER_LEN],
251 data_field: &[u8],
252 npd: bool,
253 ) -> Vec<[u8; NM_UP_SIZE]> {
254 let mut out = Vec::new();
255 self.feed_hem_into(bbheader_bytes, data_field, npd, &mut out);
256 out
257 }
258
259 pub fn feed_hem_into(
263 &mut self,
264 bbheader_bytes: &[u8; BBHEADER_LEN],
265 data_field: &[u8],
266 npd: bool,
267 out: &mut Vec<[u8; NM_UP_SIZE]>,
268 ) {
269 out.clear();
270 if npd {
273 self.stats.npd_unsupported += 1;
274 return;
275 }
276 let hdr = match Bbheader::parse(bbheader_bytes) {
277 Ok(h) => h,
278 Err(_) => {
279 self.stats.header_parse_failures += 1;
280 return;
281 }
282 };
283 if hdr.mode != Mode::HighEfficiency {
285 self.stats.mode_mismatches += 1;
286 return;
287 }
288
289 let stride = HEM_UP_SIZE;
290 let syncd_bytes = (hdr.syncd / 8) as usize;
291 let dfl_bytes = (hdr.dfl / 8) as usize;
292 let data = &data_field[..dfl_bytes.min(data_field.len())];
293
294 if self.pos > 0 {
296 let need = stride + 1 - self.pos; if syncd_bytes == need && data.len() >= need {
298 self.buf[self.pos..self.pos + need].copy_from_slice(&data[..need]);
299 self.buf[0] = TS_SYNC_BYTE;
300 out.push(self.buf);
301 self.pos = 0;
302 } else {
303 self.stats.partial_discards += 1;
305 self.pos = 0;
306 }
307 }
308
309 let mut i = syncd_bytes;
311 while i + stride <= data.len() {
312 self.buf[0] = TS_SYNC_BYTE;
314 self.buf[1..1 + stride].copy_from_slice(&data[i..i + stride]);
315 out.push(self.buf);
316 i += stride;
317 }
318
319 if i < data.len() {
321 let tail = (data.len() - i).min(stride);
322 self.buf[1..1 + tail].copy_from_slice(&data[i..i + tail]);
324 self.pos = 1 + tail;
325 } else {
326 self.pos = 0;
327 }
328 }
329
330 pub fn feed_nm(
333 &mut self,
334 bbheader_bytes: &[u8; BBHEADER_LEN],
335 data_field: &[u8],
336 ) -> Vec<[u8; NM_UP_SIZE]> {
337 let mut out = Vec::new();
338 self.feed_nm_into(bbheader_bytes, data_field, &mut out);
339 out
340 }
341
342 pub fn feed_nm_into(
346 &mut self,
347 bbheader_bytes: &[u8; BBHEADER_LEN],
348 data_field: &[u8],
349 out: &mut Vec<[u8; NM_UP_SIZE]>,
350 ) {
351 out.clear();
352 let hdr = match Bbheader::parse(bbheader_bytes) {
353 Ok(h) => h,
354 Err(_) => {
355 self.stats.header_parse_failures += 1;
356 return;
357 }
358 };
359 if hdr.mode != Mode::Normal {
361 self.stats.mode_mismatches += 1;
362 return;
363 }
364
365 let stride = NM_UP_SIZE;
366 let syncd_bytes = (hdr.syncd / 8) as usize;
367 let dfl_bytes = (hdr.dfl / 8) as usize;
368 let data = &data_field[..dfl_bytes.min(data_field.len())];
369
370 if self.pos > 0 {
372 let need = stride - self.pos;
373 if syncd_bytes == need && data.len() >= need {
374 self.buf[self.pos..self.pos + need].copy_from_slice(&data[..need]);
375 self.buf[0] = TS_SYNC_BYTE; out.push(self.buf);
377 self.pos = 0;
378 } else {
379 self.stats.partial_discards += 1;
381 self.pos = 0;
382 }
383 }
384
385 let mut i = syncd_bytes;
387 while i + stride <= data.len() {
388 self.buf.copy_from_slice(&data[i..i + stride]);
389 self.buf[0] = TS_SYNC_BYTE; out.push(self.buf);
391 i += stride;
392 }
393
394 if i < data.len() {
396 let tail = (data.len() - i).min(stride);
397 self.buf[..tail].copy_from_slice(&data[i..i + tail]);
398 self.pos = tail;
399 } else {
400 self.pos = 0;
401 }
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408 use crate::header::{Bbheader, Matype, Mode, TsGs};
409
410 fn make_nm_header(syncd: u16) -> Bbheader {
411 Bbheader {
412 matype: Matype {
413 ts_gs: TsGs::Ts,
414 sis: true,
415 ccm: true,
416 issyi: false,
417 npd: false,
418 ext: 0,
419 isi: 0,
420 },
421 upl: 0,
422 sync: 0x47,
423 dfl: 0,
424 syncd,
425 mode: Mode::Normal,
426 issy_in_header: None,
427 }
428 }
429
430 fn make_hem_header(npd: bool) -> Bbheader {
431 Bbheader {
432 matype: Matype {
433 ts_gs: TsGs::Ts,
434 sis: true,
435 ccm: true,
436 issyi: false,
437 npd,
438 ext: 0,
439 isi: 0,
440 },
441 upl: 0,
442 sync: 0,
443 dfl: 0,
444 syncd: 0,
445 mode: Mode::HighEfficiency,
446 issy_in_header: None,
447 }
448 }
449
450 #[test]
451 fn nm_extracts_single_complete_up() {
452 let mut data = vec![0xAA; NM_UP_SIZE];
453 data[0] = 0xFF; for (i, byte) in data.iter_mut().enumerate().skip(1) {
455 *byte = i as u8;
456 }
457
458 let _hdr = make_nm_header(0);
459 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
460
461 assert_eq!(pkts.len(), 1);
462 assert_eq!(pkts[0][0], TS_SYNC_BYTE);
463 assert_eq!(&pkts[0][1..], &data[1..]);
464 }
465
466 #[test]
467 fn nm_multiple_back_to_back_ups() {
468 let num_ups = 3;
469 let mut data = Vec::with_capacity(num_ups * NM_UP_SIZE);
470
471 for i in 0..num_ups {
472 data.push(0x00); for j in 1..NM_UP_SIZE {
474 data.push((i * 10 + j) as u8);
475 }
476 }
477
478 let _hdr = make_nm_header(0);
479 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
480
481 assert_eq!(pkts.len(), num_ups);
482 for pkt in &pkts {
483 assert_eq!(pkt[0], TS_SYNC_BYTE);
484 }
485 }
486
487 #[test]
488 fn nm_partial_tail_does_not_yield() {
489 let mut data = vec![0xAA; NM_UP_SIZE + 50];
490 data[0] = 0xFF; for (i, byte) in data.iter_mut().enumerate().skip(1) {
492 *byte = i as u8;
493 }
494
495 let _hdr = make_nm_header(0);
496 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
497
498 assert_eq!(pkts.len(), 1); }
500
501 #[test]
502 fn nm_crc_byte_replaced_with_sync_only() {
503 let mut data = vec![0u8; NM_UP_SIZE];
504 data[0] = 0x42; data[1] = 0x47; for (i, byte) in data.iter_mut().enumerate().skip(2) {
507 *byte = i as u8;
508 }
509
510 let _hdr = make_nm_header(0);
511 let pkt = up_iter(&data, &_hdr).next().unwrap();
512
513 assert_eq!(pkt[0], TS_SYNC_BYTE); assert_eq!(pkt[1], 0x47); assert_eq!(&pkt[2..], &data[2..]);
516 }
517
518 #[test]
519 fn hem_extracts_up_with_sync_prepend() {
520 let data: Vec<u8> = (0..HEM_UP_SIZE as u8).cycle().take(HEM_UP_SIZE).collect();
521 let mut expected = [0u8; NM_UP_SIZE];
522 expected[0] = TS_SYNC_BYTE;
523 expected[1..].copy_from_slice(&data[..HEM_UP_SIZE]);
524
525 let _hdr = make_hem_header(false);
526 let pkt = up_iter(&data, &_hdr).next().unwrap();
527
528 assert_eq!(pkt, expected);
529 }
530
531 #[test]
532 fn hem_multiple_ups_without_npd() {
533 let num_ups = 3;
534 let data = vec![0xAB; num_ups * HEM_UP_SIZE];
535 let expected: [u8; NM_UP_SIZE] = {
536 let mut e = [0xAB; NM_UP_SIZE];
537 e[0] = TS_SYNC_BYTE;
538 e
539 };
540
541 let _hdr = make_hem_header(false);
542 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
543
544 assert_eq!(pkts.len(), num_ups);
545 for pkt in &pkts {
546 assert_eq!(*pkt, expected);
547 }
548 }
549
550 #[test]
551 fn hem_with_npd_skips_dnp_bytes() {
552 let up_size = HEM_UP_SIZE;
553 let num_ups = 2;
554 let stride = up_size + 1;
556 let mut data = Vec::with_capacity(num_ups * stride);
557
558 for i in 0..num_ups {
559 for j in 0..up_size {
560 data.push((i * up_size + j) as u8);
561 }
562 data.push(i as u8); }
564
565 let _hdr = make_hem_header(true);
566 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
567
568 assert_eq!(pkts.len(), num_ups);
569 for (i, pkt) in pkts.iter().enumerate() {
570 assert_eq!(pkt[0], TS_SYNC_BYTE);
571 let offset = i * stride;
573 assert_eq!(&pkt[1..], &data[offset..offset + up_size]);
574 }
575 }
576
577 #[test]
578 fn nm_remaining_returns_unconsumed_tail() {
579 let data = vec![0xAA; NM_UP_SIZE * 2 + 50];
580 let _hdr = make_nm_header(0);
581 let mut iter = NmTsIter::new(&data);
582
583 let _p1 = iter.next().unwrap();
584 let _p2 = iter.next().unwrap();
585 let remaining = iter.remaining();
586
587 assert_eq!(remaining.len(), 50);
588 }
589
590 #[test]
591 fn hem_remaining_returns_unconsumed_tail() {
592 let data = vec![0xAA; HEM_UP_SIZE * 2 + 30];
593 let _hdr = make_hem_header(false);
594 let mut iter = HemTsIter::new(&data, false);
595
596 let _p1 = iter.next().unwrap();
597 let _p2 = iter.next().unwrap();
598 let remaining = iter.remaining();
599
600 assert_eq!(remaining.len(), 30);
601 }
602
603 #[test]
604 fn empty_data_yields_nothing() {
605 let _hdr = make_nm_header(0);
606 let pkts: Vec<_> = up_iter(&[], &_hdr).collect();
607 assert!(pkts.is_empty());
608 }
609
610 #[test]
611 fn data_shorter_than_one_up_yields_nothing() {
612 let data = vec![0xAA; 100]; let _hdr = make_nm_header(0);
614 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
615 assert!(pkts.is_empty());
616 }
617
618 #[test]
619 fn carry_over_extractor_emits_ts_across_two_bbframes_hem() {
620 let make_hem_header = |syncd_bits: u16, dfl_bits: u16| -> [u8; 10] {
623 let mut h = [0u8; 10];
624 h[0] = 0xF0;
626 h[1] = 0x00; h[2] = 0x00;
629 h[3] = 0x00;
630 h[4] = (dfl_bits >> 8) as u8;
631 h[5] = (dfl_bits & 0xFF) as u8;
632 h[6] = 0x00; h[7] = (syncd_bits >> 8) as u8;
634 h[8] = (syncd_bits & 0xFF) as u8;
635 let crc = crate::crc::crc8(&h[..9]);
637 h[9] = crc ^ 1;
638 h
639 };
640
641 let frame1_data = (0..70u8).map(|i| 0xA0 | (i & 0x0F)).collect::<Vec<u8>>();
645 let frame2_data = (0..200u8).map(|i| 0xB0 | (i & 0x0F)).collect::<Vec<u8>>();
646 let hdr1 = make_hem_header(0, (frame1_data.len() * 8) as u16);
647 let hdr2 = make_hem_header(0, (frame2_data.len() * 8) as u16);
648
649 let mut extractor = CarryOverExtractor::new();
650 let packets1 = extractor.feed_hem(&hdr1, &frame1_data, false);
651 assert_eq!(packets1.len(), 0, "70 bytes (< 187) must not yet emit a UP");
652
653 let packets2 = extractor.feed_hem(&hdr2, &frame2_data, false);
654 assert!(!packets2.is_empty(), "boundary UP should complete");
655 assert_eq!(
656 packets2[0][0], 0x47,
657 "first emitted packet has sync byte prepended"
658 );
659 }
660
661 #[test]
662 fn carry_over_hem_completion_success_path() {
663 let make_hem_header = |syncd_bits: u16, dfl_bits: u16| -> [u8; 10] {
668 let mut h = [0u8; 10];
669 h[0] = 0xF0; h[4] = (dfl_bits >> 8) as u8;
671 h[5] = (dfl_bits & 0xFF) as u8;
672 h[7] = (syncd_bits >> 8) as u8;
673 h[8] = (syncd_bits & 0xFF) as u8;
674 h[9] = crate::crc::crc8(&h[..9]) ^ 1; h
676 };
677
678 let frame1: Vec<u8> = (0..70u8).map(|i| 0xA0 | (i & 0x0F)).collect();
680 let need = 117usize;
682 let frame2: Vec<u8> = (0..(need + HEM_UP_SIZE) as u16)
684 .map(|i| 0xB0 | (i & 0x0F) as u8)
685 .collect();
686 let h1 = make_hem_header(0, (frame1.len() * 8) as u16);
687 let h2 = make_hem_header((need * 8) as u16, (frame2.len() * 8) as u16);
688
689 let mut ex = CarryOverExtractor::new();
690 let p1 = ex.feed_hem(&h1, &frame1, false);
691 assert_eq!(p1.len(), 0, "frame1's 70-byte partial emits nothing yet");
692
693 let p2 = ex.feed_hem(&h2, &frame2, false);
694 assert_eq!(
695 ex.stats().partial_discards,
696 0,
697 "completion path must be taken, NOT the discard branch"
698 );
699 assert_eq!(p2.len(), 2, "completed boundary UP + one fresh UP");
700 assert_eq!(p2[0][0], TS_SYNC_BYTE);
702 assert_eq!(&p2[0][1..71], &frame1[..]);
703 assert_eq!(&p2[0][71..188], &frame2[..need]);
704 assert_eq!(p2[1][0], TS_SYNC_BYTE);
706 assert_eq!(&p2[1][1..188], &frame2[need..need + HEM_UP_SIZE]);
707 }
708
709 #[test]
710 fn feed_into_matches_allocating_api() {
711 let make_hem_header = |syncd_bits: u16, dfl_bits: u16| -> [u8; 10] {
717 let mut h = [0u8; 10];
718 h[0] = 0xF0;
719 h[4] = (dfl_bits >> 8) as u8;
720 h[5] = (dfl_bits & 0xFF) as u8;
721 h[7] = (syncd_bits >> 8) as u8;
722 h[8] = (syncd_bits & 0xFF) as u8;
723 let crc = crate::crc::crc8(&h[..9]);
724 h[9] = crc ^ 1;
725 h
726 };
727 let f1 = (0..70u8).map(|i| 0xA0 | (i & 0x0F)).collect::<Vec<u8>>();
728 let f2 = (0..200u8).map(|i| 0xB0 | (i & 0x0F)).collect::<Vec<u8>>();
729 let h1 = make_hem_header(0, (f1.len() * 8) as u16);
730 let h2 = make_hem_header(0, (f2.len() * 8) as u16);
731
732 let mut alloc = CarryOverExtractor::new();
733 let a1 = alloc.feed_hem(&h1, &f1, false);
734 let a2 = alloc.feed_hem(&h2, &f2, false);
735
736 let mut reuse = CarryOverExtractor::new();
737 let mut buf = Vec::new();
738 reuse.feed_hem_into(&h1, &f1, false, &mut buf);
739 let b1 = buf.clone();
740 reuse.feed_hem_into(&h2, &f2, false, &mut buf);
741 let b2 = buf.clone();
742
743 assert_eq!(a1, b1, "frame 1 output matches across APIs");
744 assert_eq!(
745 a2, b2,
746 "frame 2 (carry-over) output matches; buffer was cleared"
747 );
748 }
749
750 #[test]
751 fn remaining_safe_when_pos_equals_len() {
752 let data = vec![0xAA; NM_UP_SIZE];
753 let mut iter = NmTsIter::new(&data);
754 let _p = iter.next().unwrap();
755 let remaining = iter.remaining();
757 assert!(remaining.is_empty());
758 }
759
760 #[test]
761 fn remaining_safe_when_pos_exceeds_len() {
762 let data = vec![0xAA; 10];
766 let iter = NmTsIter {
767 data: &data,
768 pos: 20,
769 };
770 let remaining = iter.remaining();
771 assert!(remaining.is_empty());
772 }
773
774 #[test]
775 fn stats_count_npd_skip_and_mode_mismatch() {
776 let mut ext = CarryOverExtractor::new();
777 let mut out = Vec::new();
778
779 let hem = make_hem_header(true).serialize();
782 ext.feed_hem_into(&hem, &[0u8; NM_UP_SIZE], true, &mut out);
783 assert!(out.is_empty());
784 assert_eq!(ext.stats().npd_unsupported, 1);
785
786 let nm = make_nm_header(0).serialize();
788 ext.feed_hem_into(&nm, &[0u8; NM_UP_SIZE], false, &mut out);
789 assert!(out.is_empty());
790 assert_eq!(ext.stats().mode_mismatches, 1);
791 assert_eq!(ext.stats().npd_unsupported, 1);
793 }
794}