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 std::fmt::Display for TsGs {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 write!(f, "TsGs::{self:?}")
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize))]
59#[repr(u8)]
60#[non_exhaustive]
61pub enum Mode {
62 Normal = 0,
64 HighEfficiency = 1,
66}
67
68impl From<num_enum::TryFromPrimitiveError<Mode>> for Error {
69 fn from(e: num_enum::TryFromPrimitiveError<Mode>) -> Self {
70 Error::InvalidMode { mode: e.number }
71 }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80#[cfg_attr(feature = "serde", derive(serde::Serialize))]
81pub struct Matype {
82 pub ts_gs: TsGs,
84 pub sis: bool,
86 pub ccm: bool,
88 pub issyi: bool,
90 pub npd: bool,
92 pub ext: u8,
94 pub isi: u8,
96}
97
98impl Matype {
99 const MASK_TS_GS: u8 = 0xC0;
100 const MASK_SIS: u8 = 0x20;
101 const MASK_CCM: u8 = 0x10;
102 const MASK_ISSYI: u8 = 0x08;
103 const MASK_NPD: u8 = 0x04;
104 const MASK_EXT: u8 = 0x03;
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113#[cfg_attr(feature = "serde", derive(serde::Serialize))]
114#[non_exhaustive]
115pub enum RollOff {
116 Alpha035,
118 Alpha025,
120 Alpha020,
122 Reserved,
124}
125
126impl RollOff {
127 #[must_use]
130 pub fn from_bits(bits: u8) -> Self {
131 match bits & 0x03 {
132 0 => Self::Alpha035,
133 1 => Self::Alpha025,
134 2 => Self::Alpha020,
135 _ => Self::Reserved,
136 }
137 }
138
139 #[must_use]
142 pub fn to_bits(self) -> u8 {
143 match self {
144 Self::Alpha035 => 0,
145 Self::Alpha025 => 1,
146 Self::Alpha020 => 2,
147 Self::Reserved => 3,
148 }
149 }
150
151 #[must_use]
154 pub fn name(self) -> &'static str {
155 match self {
156 Self::Alpha035 => "α0.35",
157 Self::Alpha025 => "α0.25",
158 Self::Alpha020 => "α0.20",
159 Self::Reserved => "reserved/S2X-low",
160 }
161 }
162}
163
164impl Matype {
165 #[must_use]
169 pub fn roll_off(&self) -> RollOff {
170 RollOff::from_bits(self.ext & 0x03)
171 }
172}
173
174impl TryFrom<[u8; 2]> for Matype {
175 type Error = Error;
176
177 fn try_from(bytes: [u8; 2]) -> Result<Self, Self::Error> {
178 let matype1 = bytes[0];
179 let matype2 = bytes[1];
180
181 let ts_gs = TsGs::try_from((matype1 & Matype::MASK_TS_GS) >> 6)?;
182 let sis = matype1 & Matype::MASK_SIS != 0;
183 let ccm = matype1 & Matype::MASK_CCM != 0;
184 let issyi = matype1 & Matype::MASK_ISSYI != 0;
185 let npd = matype1 & Matype::MASK_NPD != 0;
186 let ext = matype1 & Matype::MASK_EXT;
187
188 Ok(Matype {
189 ts_gs,
190 sis,
191 ccm,
192 issyi,
193 npd,
194 ext,
195 isi: matype2,
196 })
197 }
198}
199
200impl From<Matype> for [u8; 2] {
201 fn from(m: Matype) -> Self {
202 let mut matype1: u8 = 0;
206 matype1 |= (u8::from(m.ts_gs) << 6) & Matype::MASK_TS_GS;
207 if m.sis {
208 matype1 |= Matype::MASK_SIS;
209 }
210 if m.ccm {
211 matype1 |= Matype::MASK_CCM;
212 }
213 if m.issyi {
214 matype1 |= Matype::MASK_ISSYI;
215 }
216 if m.npd {
217 matype1 |= Matype::MASK_NPD;
218 }
219 matype1 |= m.ext & Matype::MASK_EXT;
220 [matype1, m.isi]
221 }
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
233#[cfg_attr(feature = "serde", derive(serde::Serialize))]
234pub struct Bbheader {
235 pub matype: Matype,
237 pub upl: u16,
239 pub sync: u8,
241 pub dfl: u16,
243 pub syncd: u16,
245 pub mode: Mode,
247 pub issy_in_header: Option<[u8; 3]>,
249}
250
251impl<'a> Parse<'a> for Bbheader {
252 type Error = Error;
253
254 fn parse(bytes: &'a [u8]) -> Result<Self, Self::Error> {
255 if bytes.len() < BBHEADER_LEN {
256 return Err(Error::BufferTooShort {
257 need: BBHEADER_LEN,
258 have: bytes.len(),
259 what: "BBHEADER",
260 });
261 }
262
263 let matype_bytes = [bytes[0], bytes[1]];
264 let matype = Matype::try_from(matype_bytes)?;
265 let dfl = u16::from_be_bytes([bytes[4], bytes[5]]);
266 let syncd = u16::from_be_bytes([bytes[7], bytes[8]]);
267 let crc_stored = bytes[9];
268
269 if dfl > DFL_MAX_BITS {
270 return Err(Error::DflOutOfRange {
271 dfl,
272 max: DFL_MAX_BITS,
273 });
274 }
275
276 let computed_crc = crc8(&bytes[..9]);
283 let mode_val = computed_crc ^ crc_stored;
284 let mode = Mode::try_from(mode_val)?;
285
286 let (upl, sync, issy_in_header) = match mode {
287 Mode::Normal => (u16::from_be_bytes([bytes[2], bytes[3]]), bytes[6], None),
288 Mode::HighEfficiency => {
289 (0, 0, Some([bytes[2], bytes[3], bytes[6]]))
292 }
293 };
294
295 Ok(Bbheader {
296 matype,
297 upl,
298 sync,
299 dfl,
300 syncd,
301 mode,
302 issy_in_header,
303 })
304 }
305}
306
307impl Serialize for Bbheader {
308 type Error = Error;
309
310 fn serialized_len(&self) -> usize {
311 BBHEADER_LEN
312 }
313
314 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, Self::Error> {
315 if buf.len() < BBHEADER_LEN {
316 return Err(Error::OutputBufferTooSmall {
317 need: BBHEADER_LEN,
318 have: buf.len(),
319 });
320 }
321
322 let ma = <[u8; 2]>::from(self.matype);
323 buf[0] = ma[0];
324 buf[1] = ma[1];
325
326 match self.mode {
327 Mode::Normal => {
328 let upl = self.upl.to_be_bytes();
329 buf[2] = upl[0];
330 buf[3] = upl[1];
331 let dfl = self.dfl.to_be_bytes();
332 buf[4] = dfl[0];
333 buf[5] = dfl[1];
334 buf[6] = self.sync;
335 let syncd = self.syncd.to_be_bytes();
336 buf[7] = syncd[0];
337 buf[8] = syncd[1];
338 }
339 Mode::HighEfficiency => {
340 if let Some(issy) = self.issy_in_header {
341 buf[2] = issy[0];
342 buf[3] = issy[1];
343 let dfl = self.dfl.to_be_bytes();
344 buf[4] = dfl[0];
345 buf[5] = dfl[1];
346 buf[6] = issy[2];
347 let syncd = self.syncd.to_be_bytes();
348 buf[7] = syncd[0];
349 buf[8] = syncd[1];
350 } else {
351 buf[2] = 0;
356 buf[3] = 0;
357 let dfl = self.dfl.to_be_bytes();
358 buf[4] = dfl[0];
359 buf[5] = dfl[1];
360 buf[6] = 0;
361 let syncd = self.syncd.to_be_bytes();
362 buf[7] = syncd[0];
363 buf[8] = syncd[1];
364 }
365 }
366 }
367
368 let computed = crc8(&buf[..9]);
370 buf[9] = computed ^ (self.mode as u8);
371
372 Ok(BBHEADER_LEN)
373 }
374}
375
376impl Bbheader {
377 pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
383 <Self as Parse>::parse(bytes)
384 }
385
386 pub fn serialize(&self) -> [u8; BBHEADER_LEN] {
388 let v = <Self as Serialize>::to_bytes(self);
389 let mut buf = [0u8; BBHEADER_LEN];
390 buf.copy_from_slice(&v);
391 buf
392 }
393
394 #[must_use]
404 pub fn issy(&self) -> Option<Issy> {
405 let bytes = self.issy_in_header?;
406 decode_issy_long(bytes)
407 .ok()
408 .or_else(|| decode_issy_short([bytes[0], bytes[1]]).ok())
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use super::*;
415
416 #[test]
417 fn parse_rejects_buffer_shorter_than_10() {
418 assert!(Bbheader::parse(&[0u8; 9]).is_err());
419 }
420
421 #[test]
422 fn parse_nm_ts_extracts_all_fields() {
423 let mut hdr = [0u8; BBHEADER_LEN];
429 hdr[0] = 0xF0; hdr[1] = 0x00; let upl: u16 = 0x07D0; hdr[2..4].copy_from_slice(&upl.to_be_bytes());
433 let dfl: u16 = 0xBC00; hdr[4..6].copy_from_slice(&dfl.to_be_bytes());
435 hdr[6] = 0x47; let syncd: u16 = 0x0000; hdr[7..9].copy_from_slice(&syncd.to_be_bytes());
438 hdr[9] = crc8(&hdr[..9]); let result = Bbheader::parse(&hdr).unwrap();
441 assert_eq!(result.mode, Mode::Normal);
442 assert_eq!(result.matype.ts_gs, TsGs::Ts);
443 assert!(result.matype.sis);
444 assert!(result.matype.ccm);
445 assert!(!result.matype.issyi);
446 assert!(!result.matype.npd);
447 assert_eq!(result.matype.ext, 0);
448 assert_eq!(result.matype.isi, 0x00);
449 assert_eq!(result.upl, upl);
450 assert_eq!(result.sync, 0x47);
451 assert_eq!(result.dfl, dfl);
452 assert_eq!(result.syncd, syncd);
453 }
454
455 #[test]
456 fn parse_nm_gcs_treats_sync_as_transport_protocol_byte() {
457 let mut hdr = [0u8; BBHEADER_LEN];
458 hdr[0] = 0x50; hdr[1] = 0x00;
460 let upl: u16 = 0x0000; hdr[2..4].copy_from_slice(&upl.to_be_bytes());
462 let dfl: u16 = 0x4000; hdr[4..6].copy_from_slice(&dfl.to_be_bytes());
464 hdr[6] = 0x3C; let syncd: u16 = 0x0000;
466 hdr[7..9].copy_from_slice(&syncd.to_be_bytes());
467 hdr[9] = crc8(&hdr[..9]);
468
469 let result = Bbheader::parse(&hdr).unwrap();
470 assert_eq!(result.mode, Mode::Normal);
471 assert_eq!(result.matype.ts_gs, TsGs::Gcs);
472 assert_eq!(result.sync, 0x3C);
473 assert_eq!(result.upl, upl);
474 }
475
476 #[test]
477 fn parse_detects_nm_via_crc_xor_0() {
478 let mut hdr = [0u8; BBHEADER_LEN];
480 hdr[0] = 0xF0;
481 hdr[1] = 0x00;
482 hdr[2] = 0x07;
483 hdr[3] = 0xD0; hdr[4] = 0xBC;
485 hdr[5] = 0x00; hdr[6] = 0x47; hdr[7] = 0x00;
488 hdr[8] = 0x00; hdr[9] = crc8(&hdr[..9]); let result = Bbheader::parse(&hdr).unwrap();
492 assert_eq!(result.mode, Mode::Normal);
493 }
494
495 #[test]
496 fn parse_rejects_crc_mismatch_in_both_modes() {
497 let mut hdr = [0u8; BBHEADER_LEN];
498 hdr[0] = 0xF0;
499 hdr[1] = 0x00;
500 hdr[2] = 0x07;
501 hdr[3] = 0xD0;
502 hdr[4] = 0xBC;
503 hdr[5] = 0x00;
504 hdr[6] = 0x47;
505 hdr[7] = 0x00;
506 hdr[8] = 0x00;
507 hdr[9] = 0xFF; let result = Bbheader::parse(&hdr);
510 assert!(result.is_err());
511 }
512
513 #[test]
514 fn parse_matype_extracts_ts_gs_enum_for_each_of_gfps_ts_gcs_gse() {
515 for (ts_gs_val, expected) in [
516 (0b00, TsGs::Gfps),
517 (0b01, TsGs::Gcs),
518 (0b10, TsGs::Gse),
519 (0b11, TsGs::Ts),
520 ] {
521 let ma1 = (ts_gs_val << 6) | 0x30; let mut hdr = [0u8; BBHEADER_LEN];
523 hdr[0] = ma1;
524 hdr[1] = 0x00;
525 hdr[2..9].copy_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
526 hdr[9] = crc8(&hdr[..9]);
527 let result = Bbheader::parse(&hdr).unwrap();
528 assert_eq!(result.matype.ts_gs, expected, "ts_gs=0x{:02b}", ts_gs_val);
529 }
530 }
531
532 #[test]
533 fn parse_matype_extracts_sis_isi_on_multi_stream() {
534 let mut hdr = [0u8; BBHEADER_LEN];
536 hdr[0] = 0xD0; hdr[1] = 0xAB; hdr[2] = 0x07;
539 hdr[3] = 0xD0;
540 hdr[4] = 0xBC;
541 hdr[5] = 0x00;
542 hdr[6] = 0x47;
543 hdr[7] = 0x00;
544 hdr[8] = 0x00;
545 hdr[9] = crc8(&hdr[..9]);
546
547 let result = Bbheader::parse(&hdr).unwrap();
548 assert!(!result.matype.sis);
549 assert_eq!(result.matype.isi, 0xAB);
550 }
551
552 #[test]
553 fn parse_matype_extracts_roll_off_2_bits_as_ext_for_s2_context() {
554 let mut hdr = [0u8; BBHEADER_LEN];
556 hdr[0] = 0xF3; hdr[1] = 0x00;
558 hdr[2] = 0x07;
559 hdr[3] = 0xD0;
560 hdr[4] = 0xBC;
561 hdr[5] = 0x00;
562 hdr[6] = 0x47;
563 hdr[7] = 0x00;
564 hdr[8] = 0x00;
565 hdr[9] = crc8(&hdr[..9]);
566
567 let result = Bbheader::parse(&hdr).unwrap();
568 assert_eq!(result.matype.ext, 0b11);
569 }
570
571 #[test]
572 fn serialize_nm_produces_expected_bytes() {
573 let hdr = Bbheader {
574 matype: Matype {
575 ts_gs: TsGs::Ts,
576 sis: true,
577 ccm: true,
578 issyi: false,
579 npd: false,
580 ext: 0,
581 isi: 0x00,
582 },
583 upl: 188 * 8,
584 sync: 0x47,
585 dfl: 48328,
586 syncd: 0,
587 mode: Mode::Normal,
588 issy_in_header: None,
589 };
590 let buf = hdr.serialize();
591
592 let parsed = Bbheader::parse(&buf).unwrap();
593 assert_eq!(parsed.matype.ts_gs, TsGs::Ts);
594 assert!(parsed.matype.sis);
595 assert!(parsed.matype.ccm);
596 assert_eq!(parsed.upl, 188 * 8);
597 assert_eq!(parsed.sync, 0x47);
598 assert_eq!(parsed.dfl, 48328);
599 assert_eq!(parsed.syncd, 0);
600 assert_eq!(parsed.mode, Mode::Normal);
601 }
602
603 #[test]
604 fn serialize_round_trip_nm_ts_preserves_every_field() {
605 let orig = Bbheader {
606 matype: Matype {
607 ts_gs: TsGs::Ts,
608 sis: true,
609 ccm: true,
610 issyi: true,
611 npd: false,
612 ext: 0,
613 isi: 0x00,
614 },
615 upl: 1504,
616 sync: 0x47,
617 dfl: 48328,
618 syncd: 0,
619 mode: Mode::Normal,
620 issy_in_header: None,
621 };
622 let buf = orig.serialize();
623 let parsed = Bbheader::parse(&buf).unwrap();
624 assert_eq!(orig.matype.ts_gs, parsed.matype.ts_gs);
625 assert_eq!(orig.matype.sis, parsed.matype.sis);
626 assert_eq!(orig.matype.ccm, parsed.matype.ccm);
627 assert_eq!(orig.matype.issyi, parsed.matype.issyi);
628 assert_eq!(orig.matype.npd, parsed.matype.npd);
629 assert_eq!(orig.matype.ext, parsed.matype.ext);
630 assert_eq!(orig.matype.isi, parsed.matype.isi);
631 assert_eq!(orig.upl, parsed.upl);
632 assert_eq!(orig.sync, parsed.sync);
633 assert_eq!(orig.dfl, parsed.dfl);
634 assert_eq!(orig.syncd, parsed.syncd);
635 assert_eq!(orig.mode, parsed.mode);
636 }
637
638 #[test]
639 fn serialize_round_trip_nm_gcs() {
640 let orig = Bbheader {
641 matype: Matype {
642 ts_gs: TsGs::Gcs,
643 sis: true,
644 ccm: false,
645 issyi: false,
646 npd: false,
647 ext: 0,
648 isi: 0x00,
649 },
650 upl: 0,
651 sync: 0x00,
652 dfl: 16384,
653 syncd: 0,
654 mode: Mode::Normal,
655 issy_in_header: None,
656 };
657 let buf = orig.serialize();
658 let parsed = Bbheader::parse(&buf).unwrap();
659 assert_eq!(orig.matype.ts_gs, parsed.matype.ts_gs);
660 assert_eq!(orig.matype.sis, parsed.matype.sis);
661 assert_eq!(orig.matype.ccm, parsed.matype.ccm);
662 assert_eq!(orig.dfl, parsed.dfl);
663 assert_eq!(orig.syncd, parsed.syncd);
664 assert_eq!(orig.mode, parsed.mode);
665 }
666
667 #[test]
668 fn serialize_crc8_always_matches_bytes_0_to_8() {
669 let hdr = Bbheader {
670 matype: Matype {
671 ts_gs: TsGs::Gse,
672 sis: true,
673 ccm: true,
674 issyi: true,
675 npd: false,
676 ext: 0,
677 isi: 0x00,
678 },
679 upl: 0,
680 sync: 0xFF,
681 dfl: 32768,
682 syncd: 0,
683 mode: Mode::Normal,
684 issy_in_header: None,
685 };
686 let buf = hdr.serialize();
687 let computed = crc8(&buf[..9]);
688 assert_eq!(computed ^ buf[9], 0); assert_eq!(buf[9], computed); }
691
692 #[test]
693 fn parse_detects_hem_via_crc_xor_1() {
694 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
696 let result = Bbheader::parse(&hdr).unwrap();
697 assert_eq!(result.mode, Mode::HighEfficiency);
698 }
699
700 #[test]
701 fn parse_hem_extracts_matype_dfl_syncd() {
702 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
703 let result = Bbheader::parse(&hdr).unwrap();
704 assert_eq!(result.mode, Mode::HighEfficiency);
705 assert_eq!(result.matype.ts_gs, TsGs::Ts);
706 assert!(result.matype.sis);
707 assert!(result.matype.ccm);
708 assert!(result.matype.issyi);
709 assert!(!result.matype.npd);
710 assert_eq!(result.matype.ext, 0);
711 assert_eq!(result.dfl, 48328);
712 assert_eq!(result.syncd, 0x0350);
713 }
714
715 #[test]
716 fn parse_hem_preserves_three_issy_bytes() {
717 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
718 let result = Bbheader::parse(&hdr).unwrap();
719 let issy = result.issy_in_header.unwrap();
720 assert_eq!(issy, [0xa4, 0x28, 0xe2]);
722 }
723
724 #[test]
725 fn issy_accessor_decodes_hem_iscr_long() {
726 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
730 let result = Bbheader::parse(&hdr).unwrap();
731 let issy = result.issy().expect("ISSY decode from HEM header");
732 assert_eq!(issy, Issy::IscrLong(0x0024_28E2));
733 }
734
735 #[test]
736 fn issy_accessor_returns_none_for_nm() {
737 let mut hdr = [0u8; BBHEADER_LEN];
738 hdr[0] = 0xF0;
739 hdr[1] = 0x00;
740 hdr[2] = 0x07;
741 hdr[3] = 0xD0;
742 hdr[4] = 0xBC;
743 hdr[5] = 0x00;
744 hdr[6] = 0x47;
745 hdr[7] = 0x00;
746 hdr[8] = 0x00;
747 hdr[9] = crc8(&hdr[..9]);
748 let result = Bbheader::parse(&hdr).unwrap();
749 assert_eq!(result.mode, Mode::Normal);
750 assert!(result.issy().is_none());
751 }
752
753 #[test]
754 fn issy_accessor_falls_back_to_short_form() {
755 let hdr = Bbheader {
759 matype: Matype {
760 ts_gs: TsGs::Ts,
761 sis: true,
762 ccm: true,
763 issyi: true,
764 npd: false,
765 ext: 0,
766 isi: 0x00,
767 },
768 upl: 0,
769 sync: 0,
770 dfl: 50000,
771 syncd: 100,
772 mode: Mode::HighEfficiency,
773 issy_in_header: Some([0x7A, 0xBC, 0x00]),
774 };
775 let issy = hdr.issy().expect("short-form ISSY fallback");
776 assert_eq!(issy, Issy::IscrShort(0x7ABC));
777 }
778
779 #[test]
780 fn parse_hem_leaves_upl_bits_as_zero_and_sync_as_zero() {
781 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
782 let result = Bbheader::parse(&hdr).unwrap();
783 assert_eq!(result.upl, 0);
784 assert_eq!(result.sync, 0);
785 }
786
787 #[test]
788 fn parse_hem_rejects_when_mode_xor_not_0_or_1() {
789 let mut hdr = [0u8; BBHEADER_LEN];
791 hdr[0] = 0xF0;
792 hdr[1] = 0x00;
793 hdr[2] = 0x00;
794 hdr[3] = 0x00;
795 hdr[4] = 0x00;
796 hdr[5] = 0x00;
797 hdr[6] = 0x00;
798 hdr[7] = 0x00;
799 hdr[8] = 0x00;
800 hdr[9] = crc8(&hdr[..9]) ^ 0x02; assert!(Bbheader::parse(&hdr).is_err());
802 }
803
804 #[test]
805 fn parse_same_bytes_different_mode_byte_produces_different_bbheader() {
806 let mut hdr1 = [0xF8, 0x00, 0x00, 0x00, 0xBC, 0xC8, 0x00, 0x03, 0x50, 0x00];
808 hdr1[9] = crc8(&hdr1[..9]); let mut hdr2 = hdr1;
810 hdr2[9] ^= 0x01; let result1 = Bbheader::parse(&hdr1).unwrap();
813 let result2 = Bbheader::parse(&hdr2).unwrap();
814 assert_eq!(result1.mode, Mode::Normal);
815 assert_eq!(result2.mode, Mode::HighEfficiency);
816 }
817
818 #[test]
819 fn serialize_hem_round_trip() {
820 let orig = Bbheader {
821 matype: Matype {
822 ts_gs: TsGs::Ts,
823 sis: true,
824 ccm: true,
825 issyi: true,
826 npd: false,
827 ext: 0,
828 isi: 0x00,
829 },
830 upl: 0, sync: 0, dfl: 48328,
833 syncd: 848,
834 mode: Mode::HighEfficiency,
835 issy_in_header: Some([0xA4, 0x28, 0xE2]),
836 };
837 let buf = orig.serialize();
838 let parsed = Bbheader::parse(&buf).unwrap();
839 assert_eq!(orig.mode, parsed.mode);
840 assert_eq!(orig.matype.ts_gs, parsed.matype.ts_gs);
841 assert_eq!(orig.dfl, parsed.dfl);
842 assert_eq!(orig.syncd, parsed.syncd);
843 assert_eq!(orig.issy_in_header, parsed.issy_in_header);
844 }
845
846 #[test]
847 fn serialize_hem_sets_crc_xor_mode_byte_correctly() {
848 let hdr = Bbheader {
849 matype: Matype {
850 ts_gs: TsGs::Ts,
851 sis: true,
852 ccm: true,
853 issyi: false,
854 npd: false,
855 ext: 0,
856 isi: 0x05,
857 },
858 upl: 0,
859 sync: 0,
860 dfl: 48000,
861 syncd: 0,
862 mode: Mode::HighEfficiency,
863 issy_in_header: Some([0x00, 0x00, 0x00]),
864 };
865 let buf = hdr.serialize();
866 let computed = crc8(&buf[..9]);
867 assert_eq!(buf[9], computed ^ 1);
869 }
870
871 #[test]
872 fn serialize_hem_with_issy_bytes_zero_writes_expected_layout() {
873 let hdr = Bbheader {
874 matype: Matype {
875 ts_gs: TsGs::Ts,
876 sis: true,
877 ccm: true,
878 issyi: true,
879 npd: false,
880 ext: 0,
881 isi: 0x00,
882 },
883 upl: 0,
884 sync: 0,
885 dfl: 50000,
886 syncd: 100,
887 mode: Mode::HighEfficiency,
888 issy_in_header: Some([0x00, 0x00, 0x00]),
889 };
890 let buf = hdr.serialize();
891 let parsed = Bbheader::parse(&buf).unwrap();
892 assert_eq!(parsed.mode, Mode::HighEfficiency);
893 assert_eq!(parsed.issy_in_header, Some([0x00, 0x00, 0x00]));
894 assert_eq!(parsed.dfl, 50000);
895 assert_eq!(parsed.syncd, 100);
896 }
897
898 #[test]
899 fn parse_valid_dvbt2_hem_bbframe_rai() {
900 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
902 assert_eq!(Bbheader::parse(&hdr).unwrap().dfl, 48328);
903 }
904
905 #[test]
906 fn exhaustive_tsgs_sweep() {
907 let mut matched = 0u16;
908 for byte in 0u8..=0xFF {
909 if let Ok(v) = TsGs::try_from(byte) {
910 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
911 matched += 1;
912 }
913 }
914 assert_eq!(matched, 4, "expected 4 matched variants");
915 }
916
917 #[test]
918 fn exhaustive_mode_sweep() {
919 let mut matched = 0u16;
920 for byte in 0u8..=0xFF {
921 if let Ok(v) = Mode::try_from(byte) {
922 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
923 matched += 1;
924 }
925 }
926 assert_eq!(matched, 2, "expected 2 matched variants");
927 }
928
929 #[test]
930 fn trait_parse_and_serialize_round_trip() {
931 let orig = Bbheader {
932 matype: Matype {
933 ts_gs: TsGs::Gse,
934 sis: true,
935 ccm: true,
936 issyi: true,
937 npd: false,
938 ext: 0,
939 isi: 0x00,
940 },
941 upl: 0,
942 sync: 0xFF,
943 dfl: 32768,
944 syncd: 0,
945 mode: Mode::Normal,
946 issy_in_header: None,
947 };
948 let v = <Bbheader as Serialize>::to_bytes(&orig);
949 let parsed = <Bbheader as Parse>::parse(&v).unwrap();
950 assert_eq!(orig.matype, parsed.matype);
951 assert_eq!(orig.upl, parsed.upl);
952 assert_eq!(orig.sync, parsed.sync);
953 assert_eq!(orig.dfl, parsed.dfl);
954 assert_eq!(orig.syncd, parsed.syncd);
955 assert_eq!(orig.mode, parsed.mode);
956 }
957
958 #[test]
959 fn serialize_into_hem_no_issy_is_deterministic_regardless_of_buffer_content() {
960 let hdr = Bbheader {
964 matype: Matype {
965 ts_gs: TsGs::Ts,
966 sis: true,
967 ccm: true,
968 issyi: false,
969 npd: false,
970 ext: 0,
971 isi: 0x00,
972 },
973 upl: 0,
974 sync: 0,
975 dfl: 48000,
976 syncd: 256,
977 mode: Mode::HighEfficiency,
978 issy_in_header: None,
979 };
980
981 let clean = <Bbheader as Serialize>::to_bytes(&hdr);
983
984 let mut dirty = [0xFFu8; BBHEADER_LEN];
987 hdr.serialize_into(&mut dirty).unwrap();
988
989 assert_eq!(
990 clean.as_slice(),
991 dirty.as_slice(),
992 "serialize_into into dirty buffer must produce identical bytes to to_bytes()"
993 );
994
995 let parsed = Bbheader::parse(&dirty).unwrap();
999 assert_eq!(parsed.mode, Mode::HighEfficiency);
1000 assert_eq!(parsed.issy_in_header, Some([0, 0, 0]));
1001 assert_eq!(parsed.dfl, 48000);
1002 assert_eq!(parsed.syncd, 256);
1003 }
1004
1005 #[test]
1006 fn serialize_into_rejects_buffer_too_small() {
1007 let hdr = Bbheader {
1008 matype: Matype {
1009 ts_gs: TsGs::Ts,
1010 sis: true,
1011 ccm: true,
1012 issyi: false,
1013 npd: false,
1014 ext: 0,
1015 isi: 0x00,
1016 },
1017 upl: 0,
1018 sync: 0x47,
1019 dfl: 0,
1020 syncd: 0,
1021 mode: Mode::Normal,
1022 issy_in_header: None,
1023 };
1024 let mut small = [0u8; BBHEADER_LEN - 1];
1025 let err = hdr.serialize_into(&mut small).unwrap_err();
1026 assert_eq!(
1027 err,
1028 Error::OutputBufferTooSmall {
1029 need: BBHEADER_LEN,
1030 have: small.len(),
1031 }
1032 );
1033 }
1034}