1use dvb_common::{Parse, Serialize};
7use num_enum::TryFromPrimitive;
8
9use crate::crc::crc8;
10use crate::error::Error;
11use crate::issy::{decode_issy_long, decode_issy_short, Issy};
12
13pub const BBHEADER_LEN: usize = 10;
15pub const DFL_MAX_BITS: u16 = 64800;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize))]
26#[repr(u8)]
27pub enum TsGs {
28 Gfps = 0b00,
30 Ts = 0b11,
32 Gcs = 0b01,
34 Gse = 0b10,
36}
37
38impl From<TsGs> for u8 {
39 fn from(t: TsGs) -> Self {
40 t as u8
41 }
42}
43
44impl From<num_enum::TryFromPrimitiveError<TsGs>> for Error {
45 fn from(e: num_enum::TryFromPrimitiveError<TsGs>) -> Self {
46 Error::UnsupportedTsGs { ts_gs: e.number }
47 }
48}
49
50impl TsGs {
51 #[must_use]
52 pub fn name(&self) -> &'static str {
54 match self {
55 Self::Gfps => "GFPS",
56 Self::Ts => "TS",
57 Self::Gcs => "GCS",
58 Self::Gse => "GSE",
59 }
60 }
61}
62
63dvb_common::impl_spec_display!(TsGs);
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize))]
68#[repr(u8)]
69#[non_exhaustive]
70pub enum Mode {
71 Normal = 0,
73 HighEfficiency = 1,
75}
76
77impl From<num_enum::TryFromPrimitiveError<Mode>> for Error {
78 fn from(e: num_enum::TryFromPrimitiveError<Mode>) -> Self {
79 Error::InvalidMode { mode: e.number }
80 }
81}
82
83impl Mode {
84 #[must_use]
85 pub fn name(&self) -> &'static str {
87 match self {
88 Self::Normal => "Normal",
89 Self::HighEfficiency => "High Efficiency",
90 }
91 }
92}
93
94dvb_common::impl_spec_display!(Mode);
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize))]
103pub struct Matype {
104 pub ts_gs: TsGs,
106 pub sis: bool,
108 pub ccm: bool,
110 pub issyi: bool,
112 pub npd: bool,
114 pub ext: u8,
116 pub isi: u8,
118}
119
120impl Matype {
121 const MASK_TS_GS: u8 = 0xC0;
122 const MASK_SIS: u8 = 0x20;
123 const MASK_CCM: u8 = 0x10;
124 const MASK_ISSYI: u8 = 0x08;
125 const MASK_NPD: u8 = 0x04;
126 const MASK_EXT: u8 = 0x03;
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135#[cfg_attr(feature = "serde", derive(serde::Serialize))]
136#[non_exhaustive]
137pub enum RollOff {
138 Alpha035,
140 Alpha025,
142 Alpha020,
144 Reserved,
146}
147
148impl RollOff {
149 #[must_use]
152 pub fn from_bits(bits: u8) -> Self {
153 match bits & 0x03 {
154 0 => Self::Alpha035,
155 1 => Self::Alpha025,
156 2 => Self::Alpha020,
157 _ => Self::Reserved,
158 }
159 }
160
161 #[must_use]
164 pub fn to_bits(self) -> u8 {
165 match self {
166 Self::Alpha035 => 0,
167 Self::Alpha025 => 1,
168 Self::Alpha020 => 2,
169 Self::Reserved => 3,
170 }
171 }
172
173 #[must_use]
176 pub fn name(self) -> &'static str {
177 match self {
178 Self::Alpha035 => "α=0.35",
179 Self::Alpha025 => "α=0.25",
180 Self::Alpha020 => "α=0.20",
181 Self::Reserved => "reserved/S2X-low",
182 }
183 }
184}
185
186dvb_common::impl_spec_display!(RollOff);
187
188impl Matype {
189 #[must_use]
193 pub fn roll_off(&self) -> RollOff {
194 RollOff::from_bits(self.ext & 0x03)
195 }
196}
197
198impl TryFrom<[u8; 2]> for Matype {
199 type Error = Error;
200
201 fn try_from(bytes: [u8; 2]) -> Result<Self, Self::Error> {
202 let matype1 = bytes[0];
203 let matype2 = bytes[1];
204
205 let ts_gs = TsGs::try_from((matype1 & Matype::MASK_TS_GS) >> 6)?;
206 let sis = matype1 & Matype::MASK_SIS != 0;
207 let ccm = matype1 & Matype::MASK_CCM != 0;
208 let issyi = matype1 & Matype::MASK_ISSYI != 0;
209 let npd = matype1 & Matype::MASK_NPD != 0;
210 let ext = matype1 & Matype::MASK_EXT;
211
212 Ok(Matype {
213 ts_gs,
214 sis,
215 ccm,
216 issyi,
217 npd,
218 ext,
219 isi: matype2,
220 })
221 }
222}
223
224impl From<Matype> for [u8; 2] {
225 fn from(m: Matype) -> Self {
226 let mut matype1: u8 = 0;
230 matype1 |= (u8::from(m.ts_gs) << 6) & Matype::MASK_TS_GS;
231 if m.sis {
232 matype1 |= Matype::MASK_SIS;
233 }
234 if m.ccm {
235 matype1 |= Matype::MASK_CCM;
236 }
237 if m.issyi {
238 matype1 |= Matype::MASK_ISSYI;
239 }
240 if m.npd {
241 matype1 |= Matype::MASK_NPD;
242 }
243 matype1 |= m.ext & Matype::MASK_EXT;
244 [matype1, m.isi]
245 }
246}
247
248#[derive(Debug, Clone, Copy, PartialEq, Eq)]
257#[cfg_attr(feature = "serde", derive(serde::Serialize))]
258pub struct Bbheader {
259 pub matype: Matype,
261 pub upl: u16,
263 pub sync: u8,
265 pub dfl: u16,
267 pub syncd: u16,
269 pub mode: Mode,
271 pub issy_in_header: Option<[u8; 3]>,
273}
274
275impl<'a> Parse<'a> for Bbheader {
276 type Error = Error;
277
278 fn parse(bytes: &'a [u8]) -> Result<Self, Self::Error> {
279 if bytes.len() < BBHEADER_LEN {
280 return Err(Error::BufferTooShort {
281 need: BBHEADER_LEN,
282 have: bytes.len(),
283 what: "BBHEADER",
284 });
285 }
286
287 let matype_bytes = [bytes[0], bytes[1]];
288 let matype = Matype::try_from(matype_bytes)?;
289 let dfl = u16::from_be_bytes([bytes[4], bytes[5]]);
290 let syncd = u16::from_be_bytes([bytes[7], bytes[8]]);
291 let crc_stored = bytes[9];
292
293 if dfl > DFL_MAX_BITS {
294 return Err(Error::DflOutOfRange {
295 dfl,
296 max: DFL_MAX_BITS,
297 });
298 }
299
300 let computed_crc = crc8(&bytes[..9]);
307 let mode_val = computed_crc ^ crc_stored;
308 let mode = Mode::try_from(mode_val)?;
309
310 let (upl, sync, issy_in_header) = match mode {
311 Mode::Normal => (u16::from_be_bytes([bytes[2], bytes[3]]), bytes[6], None),
312 Mode::HighEfficiency => {
313 (0, 0, Some([bytes[2], bytes[3], bytes[6]]))
316 }
317 };
318
319 Ok(Bbheader {
320 matype,
321 upl,
322 sync,
323 dfl,
324 syncd,
325 mode,
326 issy_in_header,
327 })
328 }
329}
330
331impl Serialize for Bbheader {
332 type Error = Error;
333
334 fn serialized_len(&self) -> usize {
335 BBHEADER_LEN
336 }
337
338 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, Self::Error> {
339 if buf.len() < BBHEADER_LEN {
340 return Err(Error::OutputBufferTooSmall {
341 need: BBHEADER_LEN,
342 have: buf.len(),
343 });
344 }
345
346 let ma = <[u8; 2]>::from(self.matype);
347 buf[0] = ma[0];
348 buf[1] = ma[1];
349
350 match self.mode {
351 Mode::Normal => {
352 let upl = self.upl.to_be_bytes();
353 buf[2] = upl[0];
354 buf[3] = upl[1];
355 let dfl = self.dfl.to_be_bytes();
356 buf[4] = dfl[0];
357 buf[5] = dfl[1];
358 buf[6] = self.sync;
359 let syncd = self.syncd.to_be_bytes();
360 buf[7] = syncd[0];
361 buf[8] = syncd[1];
362 }
363 Mode::HighEfficiency => {
364 if let Some(issy) = self.issy_in_header {
365 buf[2] = issy[0];
366 buf[3] = issy[1];
367 let dfl = self.dfl.to_be_bytes();
368 buf[4] = dfl[0];
369 buf[5] = dfl[1];
370 buf[6] = issy[2];
371 let syncd = self.syncd.to_be_bytes();
372 buf[7] = syncd[0];
373 buf[8] = syncd[1];
374 } else {
375 buf[2] = 0;
380 buf[3] = 0;
381 let dfl = self.dfl.to_be_bytes();
382 buf[4] = dfl[0];
383 buf[5] = dfl[1];
384 buf[6] = 0;
385 let syncd = self.syncd.to_be_bytes();
386 buf[7] = syncd[0];
387 buf[8] = syncd[1];
388 }
389 }
390 }
391
392 let computed = crc8(&buf[..9]);
394 buf[9] = computed ^ (self.mode as u8);
395
396 Ok(BBHEADER_LEN)
397 }
398}
399
400impl Bbheader {
401 pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
407 <Self as Parse>::parse(bytes)
408 }
409
410 pub fn serialize(&self) -> [u8; BBHEADER_LEN] {
412 let v = <Self as Serialize>::to_bytes(self);
413 let mut buf = [0u8; BBHEADER_LEN];
414 buf.copy_from_slice(&v);
415 buf
416 }
417
418 #[must_use]
428 pub fn issy(&self) -> Option<Issy> {
429 let bytes = self.issy_in_header?;
430 decode_issy_long(bytes)
431 .ok()
432 .or_else(|| decode_issy_short([bytes[0], bytes[1]]).ok())
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439
440 #[test]
441 fn parse_rejects_buffer_shorter_than_10() {
442 assert!(Bbheader::parse(&[0u8; 9]).is_err());
443 }
444
445 #[test]
446 fn parse_nm_ts_extracts_all_fields() {
447 let mut hdr = [0u8; BBHEADER_LEN];
453 hdr[0] = 0xF0; hdr[1] = 0x00; let upl: u16 = 0x07D0; hdr[2..4].copy_from_slice(&upl.to_be_bytes());
457 let dfl: u16 = 0xBC00; hdr[4..6].copy_from_slice(&dfl.to_be_bytes());
459 hdr[6] = 0x47; let syncd: u16 = 0x0000; hdr[7..9].copy_from_slice(&syncd.to_be_bytes());
462 hdr[9] = crc8(&hdr[..9]); let result = Bbheader::parse(&hdr).unwrap();
465 assert_eq!(result.mode, Mode::Normal);
466 assert_eq!(result.matype.ts_gs, TsGs::Ts);
467 assert!(result.matype.sis);
468 assert!(result.matype.ccm);
469 assert!(!result.matype.issyi);
470 assert!(!result.matype.npd);
471 assert_eq!(result.matype.ext, 0);
472 assert_eq!(result.matype.isi, 0x00);
473 assert_eq!(result.upl, upl);
474 assert_eq!(result.sync, 0x47);
475 assert_eq!(result.dfl, dfl);
476 assert_eq!(result.syncd, syncd);
477 }
478
479 #[test]
480 fn parse_nm_gcs_treats_sync_as_transport_protocol_byte() {
481 let mut hdr = [0u8; BBHEADER_LEN];
482 hdr[0] = 0x50; hdr[1] = 0x00;
484 let upl: u16 = 0x0000; hdr[2..4].copy_from_slice(&upl.to_be_bytes());
486 let dfl: u16 = 0x4000; hdr[4..6].copy_from_slice(&dfl.to_be_bytes());
488 hdr[6] = 0x3C; let syncd: u16 = 0x0000;
490 hdr[7..9].copy_from_slice(&syncd.to_be_bytes());
491 hdr[9] = crc8(&hdr[..9]);
492
493 let result = Bbheader::parse(&hdr).unwrap();
494 assert_eq!(result.mode, Mode::Normal);
495 assert_eq!(result.matype.ts_gs, TsGs::Gcs);
496 assert_eq!(result.sync, 0x3C);
497 assert_eq!(result.upl, upl);
498 }
499
500 #[test]
501 fn parse_detects_nm_via_crc_xor_0() {
502 let mut hdr = [0u8; BBHEADER_LEN];
504 hdr[0] = 0xF0;
505 hdr[1] = 0x00;
506 hdr[2] = 0x07;
507 hdr[3] = 0xD0; hdr[4] = 0xBC;
509 hdr[5] = 0x00; hdr[6] = 0x47; hdr[7] = 0x00;
512 hdr[8] = 0x00; hdr[9] = crc8(&hdr[..9]); let result = Bbheader::parse(&hdr).unwrap();
516 assert_eq!(result.mode, Mode::Normal);
517 }
518
519 #[test]
520 fn parse_rejects_crc_mismatch_in_both_modes() {
521 let mut hdr = [0u8; BBHEADER_LEN];
522 hdr[0] = 0xF0;
523 hdr[1] = 0x00;
524 hdr[2] = 0x07;
525 hdr[3] = 0xD0;
526 hdr[4] = 0xBC;
527 hdr[5] = 0x00;
528 hdr[6] = 0x47;
529 hdr[7] = 0x00;
530 hdr[8] = 0x00;
531 hdr[9] = 0xFF; let result = Bbheader::parse(&hdr);
534 assert!(result.is_err());
535 }
536
537 #[test]
538 fn parse_matype_extracts_ts_gs_enum_for_each_of_gfps_ts_gcs_gse() {
539 for (ts_gs_val, expected) in [
540 (0b00, TsGs::Gfps),
541 (0b01, TsGs::Gcs),
542 (0b10, TsGs::Gse),
543 (0b11, TsGs::Ts),
544 ] {
545 let ma1 = (ts_gs_val << 6) | 0x30; let mut hdr = [0u8; BBHEADER_LEN];
547 hdr[0] = ma1;
548 hdr[1] = 0x00;
549 hdr[2..9].copy_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
550 hdr[9] = crc8(&hdr[..9]);
551 let result = Bbheader::parse(&hdr).unwrap();
552 assert_eq!(result.matype.ts_gs, expected, "ts_gs=0x{:02b}", ts_gs_val);
553 }
554 }
555
556 #[test]
557 fn parse_matype_extracts_sis_isi_on_multi_stream() {
558 let mut hdr = [0u8; BBHEADER_LEN];
560 hdr[0] = 0xD0; hdr[1] = 0xAB; hdr[2] = 0x07;
563 hdr[3] = 0xD0;
564 hdr[4] = 0xBC;
565 hdr[5] = 0x00;
566 hdr[6] = 0x47;
567 hdr[7] = 0x00;
568 hdr[8] = 0x00;
569 hdr[9] = crc8(&hdr[..9]);
570
571 let result = Bbheader::parse(&hdr).unwrap();
572 assert!(!result.matype.sis);
573 assert_eq!(result.matype.isi, 0xAB);
574 }
575
576 #[test]
577 fn parse_matype_extracts_roll_off_2_bits_as_ext_for_s2_context() {
578 let mut hdr = [0u8; BBHEADER_LEN];
580 hdr[0] = 0xF3; hdr[1] = 0x00;
582 hdr[2] = 0x07;
583 hdr[3] = 0xD0;
584 hdr[4] = 0xBC;
585 hdr[5] = 0x00;
586 hdr[6] = 0x47;
587 hdr[7] = 0x00;
588 hdr[8] = 0x00;
589 hdr[9] = crc8(&hdr[..9]);
590
591 let result = Bbheader::parse(&hdr).unwrap();
592 assert_eq!(result.matype.ext, 0b11);
593 }
594
595 #[test]
596 fn serialize_nm_produces_expected_bytes() {
597 let hdr = Bbheader {
598 matype: Matype {
599 ts_gs: TsGs::Ts,
600 sis: true,
601 ccm: true,
602 issyi: false,
603 npd: false,
604 ext: 0,
605 isi: 0x00,
606 },
607 upl: 188 * 8,
608 sync: 0x47,
609 dfl: 48328,
610 syncd: 0,
611 mode: Mode::Normal,
612 issy_in_header: None,
613 };
614 let buf = hdr.serialize();
615
616 let parsed = Bbheader::parse(&buf).unwrap();
617 assert_eq!(parsed.matype.ts_gs, TsGs::Ts);
618 assert!(parsed.matype.sis);
619 assert!(parsed.matype.ccm);
620 assert_eq!(parsed.upl, 188 * 8);
621 assert_eq!(parsed.sync, 0x47);
622 assert_eq!(parsed.dfl, 48328);
623 assert_eq!(parsed.syncd, 0);
624 assert_eq!(parsed.mode, Mode::Normal);
625 }
626
627 #[test]
628 fn serialize_round_trip_nm_ts_preserves_every_field() {
629 let orig = Bbheader {
630 matype: Matype {
631 ts_gs: TsGs::Ts,
632 sis: true,
633 ccm: true,
634 issyi: true,
635 npd: false,
636 ext: 0,
637 isi: 0x00,
638 },
639 upl: 1504,
640 sync: 0x47,
641 dfl: 48328,
642 syncd: 0,
643 mode: Mode::Normal,
644 issy_in_header: None,
645 };
646 let buf = orig.serialize();
647 let parsed = Bbheader::parse(&buf).unwrap();
648 assert_eq!(orig.matype.ts_gs, parsed.matype.ts_gs);
649 assert_eq!(orig.matype.sis, parsed.matype.sis);
650 assert_eq!(orig.matype.ccm, parsed.matype.ccm);
651 assert_eq!(orig.matype.issyi, parsed.matype.issyi);
652 assert_eq!(orig.matype.npd, parsed.matype.npd);
653 assert_eq!(orig.matype.ext, parsed.matype.ext);
654 assert_eq!(orig.matype.isi, parsed.matype.isi);
655 assert_eq!(orig.upl, parsed.upl);
656 assert_eq!(orig.sync, parsed.sync);
657 assert_eq!(orig.dfl, parsed.dfl);
658 assert_eq!(orig.syncd, parsed.syncd);
659 assert_eq!(orig.mode, parsed.mode);
660 }
661
662 #[test]
663 fn serialize_round_trip_nm_gcs() {
664 let orig = Bbheader {
665 matype: Matype {
666 ts_gs: TsGs::Gcs,
667 sis: true,
668 ccm: false,
669 issyi: false,
670 npd: false,
671 ext: 0,
672 isi: 0x00,
673 },
674 upl: 0,
675 sync: 0x00,
676 dfl: 16384,
677 syncd: 0,
678 mode: Mode::Normal,
679 issy_in_header: None,
680 };
681 let buf = orig.serialize();
682 let parsed = Bbheader::parse(&buf).unwrap();
683 assert_eq!(orig.matype.ts_gs, parsed.matype.ts_gs);
684 assert_eq!(orig.matype.sis, parsed.matype.sis);
685 assert_eq!(orig.matype.ccm, parsed.matype.ccm);
686 assert_eq!(orig.dfl, parsed.dfl);
687 assert_eq!(orig.syncd, parsed.syncd);
688 assert_eq!(orig.mode, parsed.mode);
689 }
690
691 #[test]
692 fn serialize_crc8_always_matches_bytes_0_to_8() {
693 let hdr = Bbheader {
694 matype: Matype {
695 ts_gs: TsGs::Gse,
696 sis: true,
697 ccm: true,
698 issyi: true,
699 npd: false,
700 ext: 0,
701 isi: 0x00,
702 },
703 upl: 0,
704 sync: 0xFF,
705 dfl: 32768,
706 syncd: 0,
707 mode: Mode::Normal,
708 issy_in_header: None,
709 };
710 let buf = hdr.serialize();
711 let computed = crc8(&buf[..9]);
712 assert_eq!(computed ^ buf[9], 0); assert_eq!(buf[9], computed); }
715
716 #[test]
717 fn parse_detects_hem_via_crc_xor_1() {
718 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
720 let result = Bbheader::parse(&hdr).unwrap();
721 assert_eq!(result.mode, Mode::HighEfficiency);
722 }
723
724 #[test]
725 fn parse_hem_extracts_matype_dfl_syncd() {
726 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
727 let result = Bbheader::parse(&hdr).unwrap();
728 assert_eq!(result.mode, Mode::HighEfficiency);
729 assert_eq!(result.matype.ts_gs, TsGs::Ts);
730 assert!(result.matype.sis);
731 assert!(result.matype.ccm);
732 assert!(result.matype.issyi);
733 assert!(!result.matype.npd);
734 assert_eq!(result.matype.ext, 0);
735 assert_eq!(result.dfl, 48328);
736 assert_eq!(result.syncd, 0x0350);
737 }
738
739 #[test]
740 fn parse_hem_preserves_three_issy_bytes() {
741 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
742 let result = Bbheader::parse(&hdr).unwrap();
743 let issy = result.issy_in_header.unwrap();
744 assert_eq!(issy, [0xa4, 0x28, 0xe2]);
746 }
747
748 #[test]
749 fn issy_accessor_decodes_hem_iscr_long() {
750 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
754 let result = Bbheader::parse(&hdr).unwrap();
755 let issy = result.issy().expect("ISSY decode from HEM header");
756 assert_eq!(issy, Issy::IscrLong(0x0024_28E2));
757 }
758
759 #[test]
760 fn issy_accessor_returns_none_for_nm() {
761 let mut hdr = [0u8; BBHEADER_LEN];
762 hdr[0] = 0xF0;
763 hdr[1] = 0x00;
764 hdr[2] = 0x07;
765 hdr[3] = 0xD0;
766 hdr[4] = 0xBC;
767 hdr[5] = 0x00;
768 hdr[6] = 0x47;
769 hdr[7] = 0x00;
770 hdr[8] = 0x00;
771 hdr[9] = crc8(&hdr[..9]);
772 let result = Bbheader::parse(&hdr).unwrap();
773 assert_eq!(result.mode, Mode::Normal);
774 assert!(result.issy().is_none());
775 }
776
777 #[test]
778 fn issy_accessor_falls_back_to_short_form() {
779 let hdr = Bbheader {
783 matype: Matype {
784 ts_gs: TsGs::Ts,
785 sis: true,
786 ccm: true,
787 issyi: true,
788 npd: false,
789 ext: 0,
790 isi: 0x00,
791 },
792 upl: 0,
793 sync: 0,
794 dfl: 50000,
795 syncd: 100,
796 mode: Mode::HighEfficiency,
797 issy_in_header: Some([0x7A, 0xBC, 0x00]),
798 };
799 let issy = hdr.issy().expect("short-form ISSY fallback");
800 assert_eq!(issy, Issy::IscrShort(0x7ABC));
801 }
802
803 #[test]
804 fn parse_hem_leaves_upl_bits_as_zero_and_sync_as_zero() {
805 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
806 let result = Bbheader::parse(&hdr).unwrap();
807 assert_eq!(result.upl, 0);
808 assert_eq!(result.sync, 0);
809 }
810
811 #[test]
812 fn parse_hem_rejects_when_mode_xor_not_0_or_1() {
813 let mut hdr = [0u8; BBHEADER_LEN];
815 hdr[0] = 0xF0;
816 hdr[1] = 0x00;
817 hdr[2] = 0x00;
818 hdr[3] = 0x00;
819 hdr[4] = 0x00;
820 hdr[5] = 0x00;
821 hdr[6] = 0x00;
822 hdr[7] = 0x00;
823 hdr[8] = 0x00;
824 hdr[9] = crc8(&hdr[..9]) ^ 0x02; assert!(Bbheader::parse(&hdr).is_err());
826 }
827
828 #[test]
829 fn parse_same_bytes_different_mode_byte_produces_different_bbheader() {
830 let mut hdr1 = [0xF8, 0x00, 0x00, 0x00, 0xBC, 0xC8, 0x00, 0x03, 0x50, 0x00];
832 hdr1[9] = crc8(&hdr1[..9]); let mut hdr2 = hdr1;
834 hdr2[9] ^= 0x01; let result1 = Bbheader::parse(&hdr1).unwrap();
837 let result2 = Bbheader::parse(&hdr2).unwrap();
838 assert_eq!(result1.mode, Mode::Normal);
839 assert_eq!(result2.mode, Mode::HighEfficiency);
840 }
841
842 #[test]
843 fn serialize_hem_round_trip() {
844 let orig = Bbheader {
845 matype: Matype {
846 ts_gs: TsGs::Ts,
847 sis: true,
848 ccm: true,
849 issyi: true,
850 npd: false,
851 ext: 0,
852 isi: 0x00,
853 },
854 upl: 0, sync: 0, dfl: 48328,
857 syncd: 848,
858 mode: Mode::HighEfficiency,
859 issy_in_header: Some([0xA4, 0x28, 0xE2]),
860 };
861 let buf = orig.serialize();
862 let parsed = Bbheader::parse(&buf).unwrap();
863 assert_eq!(orig.mode, parsed.mode);
864 assert_eq!(orig.matype.ts_gs, parsed.matype.ts_gs);
865 assert_eq!(orig.dfl, parsed.dfl);
866 assert_eq!(orig.syncd, parsed.syncd);
867 assert_eq!(orig.issy_in_header, parsed.issy_in_header);
868 }
869
870 #[test]
871 fn serialize_hem_sets_crc_xor_mode_byte_correctly() {
872 let hdr = Bbheader {
873 matype: Matype {
874 ts_gs: TsGs::Ts,
875 sis: true,
876 ccm: true,
877 issyi: false,
878 npd: false,
879 ext: 0,
880 isi: 0x05,
881 },
882 upl: 0,
883 sync: 0,
884 dfl: 48000,
885 syncd: 0,
886 mode: Mode::HighEfficiency,
887 issy_in_header: Some([0x00, 0x00, 0x00]),
888 };
889 let buf = hdr.serialize();
890 let computed = crc8(&buf[..9]);
891 assert_eq!(buf[9], computed ^ 1);
893 }
894
895 #[test]
896 fn serialize_hem_with_issy_bytes_zero_writes_expected_layout() {
897 let hdr = Bbheader {
898 matype: Matype {
899 ts_gs: TsGs::Ts,
900 sis: true,
901 ccm: true,
902 issyi: true,
903 npd: false,
904 ext: 0,
905 isi: 0x00,
906 },
907 upl: 0,
908 sync: 0,
909 dfl: 50000,
910 syncd: 100,
911 mode: Mode::HighEfficiency,
912 issy_in_header: Some([0x00, 0x00, 0x00]),
913 };
914 let buf = hdr.serialize();
915 let parsed = Bbheader::parse(&buf).unwrap();
916 assert_eq!(parsed.mode, Mode::HighEfficiency);
917 assert_eq!(parsed.issy_in_header, Some([0x00, 0x00, 0x00]));
918 assert_eq!(parsed.dfl, 50000);
919 assert_eq!(parsed.syncd, 100);
920 }
921
922 #[test]
923 fn parse_valid_dvbt2_hem_bbframe_rai() {
924 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
926 assert_eq!(Bbheader::parse(&hdr).unwrap().dfl, 48328);
927 }
928
929 #[test]
930 fn exhaustive_tsgs_sweep() {
931 let mut matched = 0u16;
932 for byte in 0u8..=0xFF {
933 if let Ok(v) = TsGs::try_from(byte) {
934 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
935 matched += 1;
936 }
937 }
938 assert_eq!(matched, 4, "expected 4 matched variants");
939 }
940
941 #[test]
942 fn exhaustive_mode_sweep() {
943 let mut matched = 0u16;
944 for byte in 0u8..=0xFF {
945 if let Ok(v) = Mode::try_from(byte) {
946 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
947 matched += 1;
948 }
949 }
950 assert_eq!(matched, 2, "expected 2 matched variants");
951 }
952
953 #[test]
954 fn trait_parse_and_serialize_round_trip() {
955 let orig = Bbheader {
956 matype: Matype {
957 ts_gs: TsGs::Gse,
958 sis: true,
959 ccm: true,
960 issyi: true,
961 npd: false,
962 ext: 0,
963 isi: 0x00,
964 },
965 upl: 0,
966 sync: 0xFF,
967 dfl: 32768,
968 syncd: 0,
969 mode: Mode::Normal,
970 issy_in_header: None,
971 };
972 let v = <Bbheader as Serialize>::to_bytes(&orig);
973 let parsed = <Bbheader as Parse>::parse(&v).unwrap();
974 assert_eq!(orig.matype, parsed.matype);
975 assert_eq!(orig.upl, parsed.upl);
976 assert_eq!(orig.sync, parsed.sync);
977 assert_eq!(orig.dfl, parsed.dfl);
978 assert_eq!(orig.syncd, parsed.syncd);
979 assert_eq!(orig.mode, parsed.mode);
980 }
981
982 #[test]
983 fn serialize_into_hem_no_issy_is_deterministic_regardless_of_buffer_content() {
984 let hdr = Bbheader {
988 matype: Matype {
989 ts_gs: TsGs::Ts,
990 sis: true,
991 ccm: true,
992 issyi: false,
993 npd: false,
994 ext: 0,
995 isi: 0x00,
996 },
997 upl: 0,
998 sync: 0,
999 dfl: 48000,
1000 syncd: 256,
1001 mode: Mode::HighEfficiency,
1002 issy_in_header: None,
1003 };
1004
1005 let clean = <Bbheader as Serialize>::to_bytes(&hdr);
1007
1008 let mut dirty = [0xFFu8; BBHEADER_LEN];
1011 hdr.serialize_into(&mut dirty).unwrap();
1012
1013 assert_eq!(
1014 clean.as_slice(),
1015 dirty.as_slice(),
1016 "serialize_into into dirty buffer must produce identical bytes to to_bytes()"
1017 );
1018
1019 let parsed = Bbheader::parse(&dirty).unwrap();
1023 assert_eq!(parsed.mode, Mode::HighEfficiency);
1024 assert_eq!(parsed.issy_in_header, Some([0, 0, 0]));
1025 assert_eq!(parsed.dfl, 48000);
1026 assert_eq!(parsed.syncd, 256);
1027 }
1028
1029 #[test]
1030 fn serialize_into_rejects_buffer_too_small() {
1031 let hdr = Bbheader {
1032 matype: Matype {
1033 ts_gs: TsGs::Ts,
1034 sis: true,
1035 ccm: true,
1036 issyi: false,
1037 npd: false,
1038 ext: 0,
1039 isi: 0x00,
1040 },
1041 upl: 0,
1042 sync: 0x47,
1043 dfl: 0,
1044 syncd: 0,
1045 mode: Mode::Normal,
1046 issy_in_header: None,
1047 };
1048 let mut small = [0u8; BBHEADER_LEN - 1];
1049 let err = hdr.serialize_into(&mut small).unwrap_err();
1050 assert_eq!(
1051 err,
1052 Error::OutputBufferTooSmall {
1053 need: BBHEADER_LEN,
1054 have: small.len(),
1055 }
1056 );
1057 }
1058}