1use alloc::vec::Vec;
26
27use crate::ts::{TS_PACKET_SIZE, TS_SYNC_BYTE};
28
29pub const RS_PACKET_SIZE: usize = 204;
32
33pub const RS_PARITY_LEN: usize = RS_PACKET_SIZE - TS_PACKET_SIZE;
35
36pub const LOCK_CONFIRMATIONS: usize = 5;
38
39#[non_exhaustive]
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42#[cfg_attr(feature = "serde", derive(serde::Serialize))]
43pub enum PacketStride {
44 Ts188,
46 Rs204,
48}
49
50impl PacketStride {
51 pub fn name(&self) -> &'static str {
53 match self {
54 PacketStride::Ts188 => "ts_188",
55 PacketStride::Rs204 => "rs_204",
56 }
57 }
58}
59
60broadcast_common::impl_spec_display!(PacketStride);
61
62#[non_exhaustive]
64#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
65#[cfg_attr(feature = "serde", derive(serde::Serialize))]
66pub struct ResyncStats {
67 pub packets: u64,
69 pub resyncs: u64,
71 pub dropped_bytes: u64,
73}
74
75#[non_exhaustive]
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84#[cfg_attr(feature = "serde", derive(serde::Serialize))]
85pub struct ResyncLoss {
86 pub offset: u64,
88 pub dropped_bytes: u64,
90 pub packet_index: u64,
93 pub truncated_tail: bool,
96}
97
98#[derive(Debug, Default)]
117pub struct TsResync {
118 buf: Vec<u8>,
119 head: usize,
121 stride: Option<PacketStride>,
122 stats: ResyncStats,
123 consumed: u64,
125 open_loss: Option<u64>,
127 losses: Vec<ResyncLoss>,
129 tail: Vec<[u8; TS_PACKET_SIZE]>,
132}
133
134impl TsResync {
135 pub fn new() -> Self {
137 Self::default()
138 }
139
140 pub fn feed(&mut self, data: &[u8]) -> Vec<[u8; TS_PACKET_SIZE]> {
151 let mut emitted = Vec::new();
152 self.feed_into(data, &mut emitted);
153 emitted
154 }
155
156 pub fn feed_into(&mut self, data: &[u8], emitted: &mut Vec<[u8; TS_PACKET_SIZE]>) -> usize {
163 self.buf.extend_from_slice(data);
164 let before = emitted.len();
165 emitted.reserve((self.buf.len() - self.head) / TS_PACKET_SIZE);
166
167 loop {
168 match self.stride {
169 None => {
170 if let Some((offset, s)) = find_sync(&self.buf[self.head..]) {
171 self.lock(offset, s);
172 } else {
173 let keep = LOCK_CONFIRMATIONS * RS_PACKET_SIZE;
175 let tail_len = self.buf.len() - self.head;
176 if tail_len > keep {
177 let excess = tail_len - keep;
178 self.stats.dropped_bytes += excess as u64;
179 self.head += excess;
180 }
181 self.compact();
182 return emitted.len() - before;
183 }
184 }
185 Some(stride) => {
186 let s = match stride {
187 PacketStride::Ts188 => TS_PACKET_SIZE,
188 PacketStride::Rs204 => RS_PACKET_SIZE,
189 };
190 let tail_len = self.buf.len() - self.head;
191 if tail_len < s {
192 self.compact();
193 return emitted.len() - before;
194 }
195 if self.buf[self.head] == TS_SYNC_BYTE {
196 let mut packet = [0u8; TS_PACKET_SIZE];
197 packet.copy_from_slice(&self.buf[self.head..self.head + TS_PACKET_SIZE]);
198 emitted.push(packet);
199 self.head += s;
200 self.stats.packets += 1;
201 } else {
202 self.stats.resyncs += 1;
204 self.stats.dropped_bytes += 1;
205 self.open_loss
206 .get_or_insert(self.consumed + self.head as u64);
207 self.head += 1;
208 self.stride = None;
209 }
210 }
211 }
212 }
213 }
214
215 pub fn stride(&self) -> Option<PacketStride> {
217 self.stride
218 }
219
220 pub fn take_losses(&mut self) -> Vec<ResyncLoss> {
228 core::mem::take(&mut self.losses)
229 }
230
231 fn lock(&mut self, offset: usize, stride: PacketStride) {
232 self.stats.dropped_bytes += offset as u64;
233 self.head += offset;
234 self.stride = Some(stride);
235 if let Some(start) = self.open_loss.take() {
236 self.losses.push(ResyncLoss {
237 offset: start,
238 dropped_bytes: self.consumed + self.head as u64 - start,
239 packet_index: self.stats.packets,
240 truncated_tail: false,
241 });
242 }
243 }
244
245 pub fn finish(&mut self) {
256 if self.stride.is_none()
257 && let Some((offset, s)) = find_tail_sync(&self.buf[self.head..])
258 {
259 self.lock(offset, s);
260 let mut emitted = core::mem::take(&mut self.tail);
261 self.feed_into(&[], &mut emitted);
262 self.tail = emitted;
263 }
264 let end = self.consumed + self.buf.len() as u64;
265 if let Some(start) = self.open_loss.take() {
266 self.losses.push(ResyncLoss {
267 offset: start,
268 dropped_bytes: end - start,
269 packet_index: self.stats.packets,
270 truncated_tail: false,
271 });
272 } else if self.stride.is_some() && self.head < self.buf.len() {
273 self.losses.push(ResyncLoss {
274 offset: self.consumed + self.head as u64,
275 dropped_bytes: (self.buf.len() - self.head) as u64,
276 packet_index: self.stats.packets,
277 truncated_tail: true,
278 });
279 }
280 self.stats.dropped_bytes += (self.buf.len() - self.head) as u64;
281 self.head = self.buf.len();
282 self.compact();
283 }
284
285 pub fn take_tail_packets(&mut self) -> Vec<[u8; TS_PACKET_SIZE]> {
289 core::mem::take(&mut self.tail)
290 }
291
292 pub fn stats(&self) -> ResyncStats {
294 self.stats
295 }
296
297 fn compact(&mut self) {
299 if self.head > 0 {
300 self.consumed += self.head as u64;
301 self.buf.drain(..self.head);
302 self.head = 0;
303 }
304 }
305}
306
307fn find_sync(buf: &[u8]) -> Option<(usize, PacketStride)> {
313 for o in 0..buf.len() {
314 if buf[o] != TS_SYNC_BYTE {
315 continue;
316 }
317 if try_stride(buf, o, TS_PACKET_SIZE) {
318 return Some((o, PacketStride::Ts188));
319 }
320 if try_stride(buf, o, RS_PACKET_SIZE) {
321 return Some((o, PacketStride::Rs204));
322 }
323 }
324 None
325}
326
327fn find_tail_sync(buf: &[u8]) -> Option<(usize, PacketStride)> {
331 let chain = |o: usize, s: usize| {
332 o + s <= buf.len()
333 && (o..buf.len())
334 .step_by(s)
335 .take_while(|pos| pos + s <= buf.len())
336 .all(|pos| buf[pos] == TS_SYNC_BYTE)
337 };
338 (0..buf.len())
339 .filter(|&o| buf[o] == TS_SYNC_BYTE)
340 .find_map(|o| {
341 if chain(o, TS_PACKET_SIZE) {
342 Some((o, PacketStride::Ts188))
343 } else if chain(o, RS_PACKET_SIZE) {
344 Some((o, PacketStride::Rs204))
345 } else {
346 None
347 }
348 })
349}
350
351fn try_stride(buf: &[u8], offset: usize, s: usize) -> bool {
355 for k in 1..LOCK_CONFIRMATIONS {
356 let pos = offset + k * s;
357 if pos >= buf.len() || buf[pos] != TS_SYNC_BYTE {
358 return false;
359 }
360 }
361 true
362}
363
364#[must_use]
376pub fn resync(buf: &[u8]) -> Option<usize> {
377 let mut i = 0;
378 while i < buf.len() {
379 if buf[i] == TS_SYNC_BYTE {
380 let next = i + TS_PACKET_SIZE;
381 if next < buf.len() {
382 if buf[next] == TS_SYNC_BYTE {
383 return Some(i);
384 }
385 i += 1;
386 continue;
387 }
388 return Some(i);
389 }
390 i += 1;
391 }
392 None
393}
394
395#[must_use]
398pub fn resync_confirmed(buf: &[u8]) -> Option<usize> {
399 (0..buf.len().saturating_sub(TS_PACKET_SIZE))
400 .find(|&i| buf[i] == TS_SYNC_BYTE && buf[i + TS_PACKET_SIZE] == TS_SYNC_BYTE)
401}
402
403#[must_use]
406pub fn unconfirmed_candidate(buf: &[u8]) -> Option<usize> {
407 let from = buf.len().saturating_sub(TS_PACKET_SIZE);
408 buf[from..]
409 .iter()
410 .position(|&b| b == TS_SYNC_BYTE)
411 .map(|i| from + i)
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417 use alloc::vec;
418 use alloc::vec::Vec;
419 extern crate std;
420
421 fn ts_packet(tag: u8) -> [u8; TS_PACKET_SIZE] {
425 assert_ne!(tag, TS_SYNC_BYTE, "tag must not equal sync byte");
426 let mut pkt = [tag; TS_PACKET_SIZE];
427 pkt[0] = TS_SYNC_BYTE;
428 pkt
429 }
430
431 fn rs_packet(ts_tag: u8, parity: u8) -> [u8; RS_PACKET_SIZE] {
434 assert_ne!(ts_tag, TS_SYNC_BYTE);
435 assert_ne!(parity, TS_SYNC_BYTE);
436 let mut pkt = [parity; RS_PACKET_SIZE];
437 pkt[0] = TS_SYNC_BYTE;
438 pkt[1..TS_PACKET_SIZE].fill(ts_tag);
439 pkt
440 }
441
442 fn concat_ts(packets: &[[u8; TS_PACKET_SIZE]]) -> Vec<u8> {
444 let mut v = Vec::with_capacity(packets.len() * TS_PACKET_SIZE);
445 for p in packets {
446 v.extend_from_slice(p);
447 }
448 v
449 }
450
451 #[test]
455 fn lone_sync_byte_accepted_by_resync_not_confirmed() {
456 let buf = [TS_SYNC_BYTE];
457 assert_eq!(resync(&buf), Some(0));
458 assert_eq!(resync_confirmed(&buf), None);
459 }
460
461 #[test]
462 fn aligned_188_passthrough() {
463 let packets: Vec<_> = (0u8..5).map(|i| ts_packet(i + 1)).collect();
464 let data = concat_ts(&packets);
465
466 let mut r = TsResync::new();
467 let emitted = r.feed(&data);
468
469 assert_eq!(emitted.len(), 5);
470 for (i, (e, p)) in emitted.iter().zip(packets.iter()).enumerate() {
471 assert_eq!(e, p, "packet {i} mismatch");
472 }
473 assert_eq!(r.stride(), Some(PacketStride::Ts188));
474 let s = r.stats();
475 assert_eq!(s.packets, 5);
476 assert_eq!(s.resyncs, 0);
477 assert_eq!(s.dropped_bytes, 0);
478 }
479
480 #[test]
484 fn requires_n_confirmations_before_lock() {
485 assert_eq!(
489 LOCK_CONFIRMATIONS, 5,
490 "this test pins the default lock window of 5"
491 );
492
493 let four = concat_ts(&(1u8..=4).map(ts_packet).collect::<Vec<_>>());
497 let mut r = TsResync::new();
498 assert_eq!(
499 r.feed(&four).len(),
500 0,
501 "4 confirmations (< 5) must not lock or emit"
502 );
503 assert_eq!(r.stride(), None, "stride must remain None below the window");
504
505 let mut out = r.feed(&ts_packet(5));
507 out.extend(r.feed(&[]));
508 assert!(
509 r.stride().is_some(),
510 "the 5th confirmation must trigger lock"
511 );
512 assert_eq!(out.len(), 5, "all five aligned packets emit once locked");
514 }
515
516 #[test]
520 fn junk_prefix_correct_dropped_count() {
521 let pkts: Vec<_> = (0u8..6).map(|i| ts_packet(i + 1)).collect();
522 let stream = concat_ts(&pkts);
523
524 let junk_len = 7usize;
525 let junk: Vec<u8> = vec![0x00; junk_len];
526 let mut data = junk;
527 data.extend_from_slice(&stream);
528
529 let mut r = TsResync::new();
530 let emitted = r.feed(&data);
531
532 assert_eq!(emitted.len(), 6);
533 for (i, (e, p)) in emitted.iter().zip(pkts.iter()).enumerate() {
534 assert_eq!(*e, *p, "packet {i} mismatch after junk prefix");
535 }
536 let s = r.stats();
537 assert_eq!(
538 s.dropped_bytes, junk_len as u64,
539 "dropped bytes must equal junk prefix"
540 );
541 assert_eq!(s.resyncs, 0);
542 assert_eq!(s.packets, 6);
543 }
544
545 #[test]
549 fn midstream_loss_resync() {
550 let pkts: Vec<_> = (0u8..14).map(|i| ts_packet(i + 1)).collect();
553 let clean = concat_ts(&pkts);
554
555 let insert_at = 7 * TS_PACKET_SIZE + 12;
557 let mut data = clean[..insert_at].to_vec();
558 data.push(0x00);
559 data.extend_from_slice(&clean[insert_at..]);
560
561 let mut r = TsResync::new();
562 let emitted = r.feed(&data);
563
564 let s = r.stats();
565 assert!(
566 s.resyncs >= 1,
567 "mid-stream corruption must trigger a resync, got {}",
568 s.resyncs
569 );
570 assert_eq!(emitted[0], pkts[0], "first emitted packet is P0");
571 assert!(
572 emitted.len() >= 10,
573 "should recover and emit most packets, got {}",
574 emitted.len()
575 );
576 }
577
578 #[test]
582 fn rs204_detected_and_stripped() {
583 let mut stream = Vec::new();
584 let mut expected = Vec::new();
585 for i in 0u8..6 {
586 let tag = i + 1;
587 let rs = rs_packet(tag, 0xFF);
588 stream.extend_from_slice(&rs);
589 expected.push(ts_packet(tag));
590 }
591
592 let mut r = TsResync::new();
593 let emitted = r.feed(&stream);
594
595 assert_eq!(emitted.len(), 6, "RS-coded stream must emit 6 packets");
596 assert_eq!(
597 r.stride(),
598 Some(PacketStride::Rs204),
599 "stride must be Rs204"
600 );
601 for (i, (e, p)) in emitted.iter().zip(expected.iter()).enumerate() {
602 assert_eq!(e, p, "RS-stripped packet {i} mismatch");
603 }
604 for (i, pkt) in emitted.iter().enumerate() {
606 crate::ts::TsPacket::parse(pkt)
607 .unwrap_or_else(|e| panic!("RS-stripped packet {i} TsPacket::parse failed: {e}"));
608 }
609 let s = r.stats();
610 assert_eq!(s.packets, 6);
611 assert_eq!(s.resyncs, 0);
612 assert_eq!(s.dropped_bytes, 0);
613 }
614
615 #[test]
620 fn broken_sync_byte_after_lock_is_one_loss_with_offset() {
621 let mut bytes = Vec::new();
622 for t in 1..=12u8 {
623 bytes.extend_from_slice(&ts_packet(t));
624 }
625 bytes[5 * TS_PACKET_SIZE] = 0x00;
626 let mut r = TsResync::new();
627 let got = r.feed(&bytes);
628 assert_eq!(got.len(), 11);
629 r.finish();
630 let losses = r.take_losses();
631 assert_eq!(losses.len(), 1);
632 assert_eq!(losses[0].offset, 5 * TS_PACKET_SIZE as u64);
633 assert_eq!(losses[0].dropped_bytes, TS_PACKET_SIZE as u64);
634 assert_eq!(losses[0].packet_index, 5);
635 assert!(!losses[0].truncated_tail);
636 }
637
638 #[test]
639 fn leading_junk_is_not_a_loss_but_truncated_tail_is() {
640 let mut bytes = vec![0x11; 30];
641 for t in 1..=8u8 {
642 bytes.extend_from_slice(&ts_packet(t));
643 }
644 bytes.extend_from_slice(&[0x22; 50]);
645 let mut r = TsResync::new();
646 let _ = r.feed(&bytes);
647 r.finish();
648 let losses = r.take_losses();
649 assert_eq!(losses.len(), 1);
650 assert!(losses[0].truncated_tail);
651 assert_eq!(losses[0].offset, 30 + 8 * TS_PACKET_SIZE as u64);
652 assert_eq!(losses[0].dropped_bytes, 50);
653 }
654
655 #[test]
656 fn aligned_188_matches_plain_chunks() {
657 let pkts: Vec<_> = (0u8..10).map(|i| ts_packet(i + 1)).collect();
658 let data = concat_ts(&pkts);
659
660 let oracle: Vec<[u8; TS_PACKET_SIZE]> = data
662 .chunks_exact(TS_PACKET_SIZE)
663 .filter(|c| c[0] == TS_SYNC_BYTE)
664 .map(|c| c.try_into().unwrap())
665 .collect();
666
667 let mut r = TsResync::new();
668 let emitted = r.feed(&data);
669
670 assert_eq!(emitted.len(), oracle.len(), "count mismatch");
671 for (i, (e, o)) in emitted.iter().zip(oracle.iter()).enumerate() {
672 assert_eq!(e, o, "packet {i} differs from chunks-oracle");
673 }
674 }
675
676 #[test]
680 fn chunked_feed_equivalence() {
681 let pkts: Vec<_> = (0u8..6).map(|i| ts_packet(i + 1)).collect();
682 let stream = concat_ts(&pkts);
683
684 let whole = {
685 let mut r = TsResync::new();
686 r.feed(&stream)
687 };
688
689 let chunked = {
690 let mut r = TsResync::new();
691 let mut out = Vec::new();
692 for chunk in stream.chunks(100) {
693 out.extend(r.feed(chunk));
694 }
695 out
696 };
697
698 assert_eq!(whole.len(), chunked.len());
699 for (i, (w, c)) in whole.iter().zip(chunked.iter()).enumerate() {
700 assert_eq!(w, c, "packet {i} mismatch");
701 }
702 }
703
704 #[test]
710 fn fixture_m6_matches_plain_chunks() {
711 let path = test_fixtures::path("ts/m6-single.ts");
712 let data = std::fs::read(path).expect("m6-single.ts fixture not found");
713
714 let oracle: Vec<[u8; TS_PACKET_SIZE]> = data
716 .chunks_exact(TS_PACKET_SIZE)
717 .filter(|c| c[0] == TS_SYNC_BYTE)
718 .map(|c| c.try_into().unwrap())
719 .collect();
720 assert!(!oracle.is_empty(), "oracle empty — fixture empty?");
721
722 let mut r = TsResync::new();
723 let emitted = r.feed(&data);
724
725 assert_eq!(
726 emitted.len(),
727 oracle.len(),
728 "packet count: TsResync={} oracle={}",
729 emitted.len(),
730 oracle.len()
731 );
732 for (i, (e, o)) in emitted.iter().zip(oracle.iter()).enumerate() {
733 assert_eq!(e, o, "packet {i} mismatch vs oracle");
734 }
735 }
736
737 #[test]
738 fn finish_emits_the_aligned_packets_after_a_corrupt_sync_byte() {
739 let mut input = Vec::new();
740 for tag in 1u8..=10 {
741 input.extend_from_slice(&ts_packet(tag));
742 }
743 let mut bad = ts_packet(11);
744 bad[0] = 0x00;
745 input.extend_from_slice(&bad);
746 for tag in 12u8..=14 {
747 input.extend_from_slice(&ts_packet(tag));
748 }
749
750 let mut r = TsResync::new();
751 let mut packets = r.feed(&input);
752 assert_eq!(
753 packets.len(),
754 10,
755 "three packets cannot lock before the end"
756 );
757 r.finish();
758 packets.extend(r.take_tail_packets());
759 let tags: Vec<u8> = packets.iter().map(|p| p[1]).collect();
760 assert_eq!(tags, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14]);
761 let stats = r.stats();
762 assert_eq!(stats.packets, 13);
763 assert_eq!(stats.dropped_bytes, TS_PACKET_SIZE as u64);
764 let losses = r.take_losses();
765 assert_eq!(losses.len(), 1);
766 assert_eq!(losses[0].dropped_bytes, TS_PACKET_SIZE as u64);
767 assert!(!losses[0].truncated_tail);
768 }
769
770 #[test]
771 fn finish_emits_a_stream_shorter_than_the_lock_window() {
772 let packets: Vec<_> = (1u8..=4).map(ts_packet).collect();
773 let mut r = TsResync::new();
774 assert!(r.feed(&concat_ts(&packets)).is_empty());
775 r.finish();
776 assert_eq!(r.take_tail_packets(), packets);
777 assert!(r.take_tail_packets().is_empty());
778 assert_eq!(r.stride(), Some(PacketStride::Ts188));
779 assert_eq!(r.stats().dropped_bytes, 0);
780 }
781
782 #[test]
783 fn finish_emits_a_short_rs204_stream_stripped() {
784 let mut stream = Vec::new();
785 for tag in 1u8..=3 {
786 stream.extend_from_slice(&rs_packet(tag, 0xFF));
787 }
788 let mut r = TsResync::new();
789 assert!(r.feed(&stream).is_empty());
790 r.finish();
791 let packets = r.take_tail_packets();
792 assert_eq!(packets, [ts_packet(1), ts_packet(2), ts_packet(3)]);
793 assert_eq!(r.stride(), Some(PacketStride::Rs204));
794 }
795
796 #[test]
797 fn finish_does_not_lock_onto_a_stray_sync_byte_without_a_whole_packet() {
798 let mut junk = vec![0x00u8; 100];
799 junk[40] = TS_SYNC_BYTE;
800 let mut r = TsResync::new();
801 assert!(r.feed(&junk).is_empty());
802 r.finish();
803 assert!(r.take_tail_packets().is_empty());
804 assert_eq!(r.stats().dropped_bytes, 100);
805 }
806
807 #[test]
808 fn finish_does_not_emit_a_trailing_partial_packet_starting_with_sync_byte() {
809 let mut input = Vec::new();
810 for tag in 1u8..=10 {
811 input.extend_from_slice(&ts_packet(tag));
812 }
813 input.push(TS_SYNC_BYTE);
814 input.extend_from_slice(&[0x11, 0x22, 0x33, 0x44]);
815
816 let mut r = TsResync::new();
817 let mut packets = r.feed(&input);
818 r.finish();
819 packets.extend(r.take_tail_packets());
820 let tags: Vec<u8> = packets.iter().map(|p| p[1]).collect();
821 assert_eq!(tags, (1u8..=10).collect::<Vec<_>>());
822 assert!(r.take_tail_packets().is_empty());
823 let losses = r.take_losses();
824 assert_eq!(losses.len(), 1);
825 assert!(losses[0].truncated_tail);
826 assert_eq!(losses[0].dropped_bytes, 5);
827 }
828}