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 let dfl = self.dfl.to_be_bytes();
352 buf[4] = dfl[0];
353 buf[5] = dfl[1];
354 let syncd = self.syncd.to_be_bytes();
355 buf[7] = syncd[0];
356 buf[8] = syncd[1];
357 }
358 }
359 }
360
361 let computed = crc8(&buf[..9]);
363 buf[9] = computed ^ (self.mode as u8);
364
365 Ok(BBHEADER_LEN)
366 }
367}
368
369impl Bbheader {
370 pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
376 <Self as Parse>::parse(bytes)
377 }
378
379 pub fn serialize(&self) -> [u8; BBHEADER_LEN] {
381 let v = <Self as Serialize>::to_bytes(self);
382 let mut buf = [0u8; BBHEADER_LEN];
383 buf.copy_from_slice(&v);
384 buf
385 }
386
387 #[must_use]
397 pub fn issy(&self) -> Option<Issy> {
398 let bytes = self.issy_in_header?;
399 decode_issy_long(bytes)
400 .ok()
401 .or_else(|| decode_issy_short([bytes[0], bytes[1]]).ok())
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408
409 #[test]
410 fn parse_rejects_buffer_shorter_than_10() {
411 assert!(Bbheader::parse(&[0u8; 9]).is_err());
412 }
413
414 #[test]
415 fn parse_nm_ts_extracts_all_fields() {
416 let mut hdr = [0u8; BBHEADER_LEN];
422 hdr[0] = 0xF0; hdr[1] = 0x00; let upl: u16 = 0x07D0; hdr[2..4].copy_from_slice(&upl.to_be_bytes());
426 let dfl: u16 = 0xBC00; hdr[4..6].copy_from_slice(&dfl.to_be_bytes());
428 hdr[6] = 0x47; let syncd: u16 = 0x0000; hdr[7..9].copy_from_slice(&syncd.to_be_bytes());
431 hdr[9] = crc8(&hdr[..9]); let result = Bbheader::parse(&hdr).unwrap();
434 assert_eq!(result.mode, Mode::Normal);
435 assert_eq!(result.matype.ts_gs, TsGs::Ts);
436 assert!(result.matype.sis);
437 assert!(result.matype.ccm);
438 assert!(!result.matype.issyi);
439 assert!(!result.matype.npd);
440 assert_eq!(result.matype.ext, 0);
441 assert_eq!(result.matype.isi, 0x00);
442 assert_eq!(result.upl, upl);
443 assert_eq!(result.sync, 0x47);
444 assert_eq!(result.dfl, dfl);
445 assert_eq!(result.syncd, syncd);
446 }
447
448 #[test]
449 fn parse_nm_gcs_treats_sync_as_transport_protocol_byte() {
450 let mut hdr = [0u8; BBHEADER_LEN];
451 hdr[0] = 0x50; hdr[1] = 0x00;
453 let upl: u16 = 0x0000; hdr[2..4].copy_from_slice(&upl.to_be_bytes());
455 let dfl: u16 = 0x4000; hdr[4..6].copy_from_slice(&dfl.to_be_bytes());
457 hdr[6] = 0x3C; let syncd: u16 = 0x0000;
459 hdr[7..9].copy_from_slice(&syncd.to_be_bytes());
460 hdr[9] = crc8(&hdr[..9]);
461
462 let result = Bbheader::parse(&hdr).unwrap();
463 assert_eq!(result.mode, Mode::Normal);
464 assert_eq!(result.matype.ts_gs, TsGs::Gcs);
465 assert_eq!(result.sync, 0x3C);
466 assert_eq!(result.upl, upl);
467 }
468
469 #[test]
470 fn parse_detects_nm_via_crc_xor_0() {
471 let mut hdr = [0u8; BBHEADER_LEN];
473 hdr[0] = 0xF0;
474 hdr[1] = 0x00;
475 hdr[2] = 0x07;
476 hdr[3] = 0xD0; hdr[4] = 0xBC;
478 hdr[5] = 0x00; hdr[6] = 0x47; hdr[7] = 0x00;
481 hdr[8] = 0x00; hdr[9] = crc8(&hdr[..9]); let result = Bbheader::parse(&hdr).unwrap();
485 assert_eq!(result.mode, Mode::Normal);
486 }
487
488 #[test]
489 fn parse_rejects_crc_mismatch_in_both_modes() {
490 let mut hdr = [0u8; BBHEADER_LEN];
491 hdr[0] = 0xF0;
492 hdr[1] = 0x00;
493 hdr[2] = 0x07;
494 hdr[3] = 0xD0;
495 hdr[4] = 0xBC;
496 hdr[5] = 0x00;
497 hdr[6] = 0x47;
498 hdr[7] = 0x00;
499 hdr[8] = 0x00;
500 hdr[9] = 0xFF; let result = Bbheader::parse(&hdr);
503 assert!(result.is_err());
504 }
505
506 #[test]
507 fn parse_matype_extracts_ts_gs_enum_for_each_of_gfps_ts_gcs_gse() {
508 for (ts_gs_val, expected) in [
509 (0b00, TsGs::Gfps),
510 (0b01, TsGs::Gcs),
511 (0b10, TsGs::Gse),
512 (0b11, TsGs::Ts),
513 ] {
514 let ma1 = (ts_gs_val << 6) | 0x30; let mut hdr = [0u8; BBHEADER_LEN];
516 hdr[0] = ma1;
517 hdr[1] = 0x00;
518 hdr[2..9].copy_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
519 hdr[9] = crc8(&hdr[..9]);
520 let result = Bbheader::parse(&hdr).unwrap();
521 assert_eq!(result.matype.ts_gs, expected, "ts_gs=0x{:02b}", ts_gs_val);
522 }
523 }
524
525 #[test]
526 fn parse_matype_extracts_sis_isi_on_multi_stream() {
527 let mut hdr = [0u8; BBHEADER_LEN];
529 hdr[0] = 0xD0; hdr[1] = 0xAB; hdr[2] = 0x07;
532 hdr[3] = 0xD0;
533 hdr[4] = 0xBC;
534 hdr[5] = 0x00;
535 hdr[6] = 0x47;
536 hdr[7] = 0x00;
537 hdr[8] = 0x00;
538 hdr[9] = crc8(&hdr[..9]);
539
540 let result = Bbheader::parse(&hdr).unwrap();
541 assert!(!result.matype.sis);
542 assert_eq!(result.matype.isi, 0xAB);
543 }
544
545 #[test]
546 fn parse_matype_extracts_roll_off_2_bits_as_ext_for_s2_context() {
547 let mut hdr = [0u8; BBHEADER_LEN];
549 hdr[0] = 0xF3; hdr[1] = 0x00;
551 hdr[2] = 0x07;
552 hdr[3] = 0xD0;
553 hdr[4] = 0xBC;
554 hdr[5] = 0x00;
555 hdr[6] = 0x47;
556 hdr[7] = 0x00;
557 hdr[8] = 0x00;
558 hdr[9] = crc8(&hdr[..9]);
559
560 let result = Bbheader::parse(&hdr).unwrap();
561 assert_eq!(result.matype.ext, 0b11);
562 }
563
564 #[test]
565 fn serialize_nm_produces_expected_bytes() {
566 let hdr = Bbheader {
567 matype: Matype {
568 ts_gs: TsGs::Ts,
569 sis: true,
570 ccm: true,
571 issyi: false,
572 npd: false,
573 ext: 0,
574 isi: 0x00,
575 },
576 upl: 188 * 8,
577 sync: 0x47,
578 dfl: 48328,
579 syncd: 0,
580 mode: Mode::Normal,
581 issy_in_header: None,
582 };
583 let buf = hdr.serialize();
584
585 let parsed = Bbheader::parse(&buf).unwrap();
586 assert_eq!(parsed.matype.ts_gs, TsGs::Ts);
587 assert!(parsed.matype.sis);
588 assert!(parsed.matype.ccm);
589 assert_eq!(parsed.upl, 188 * 8);
590 assert_eq!(parsed.sync, 0x47);
591 assert_eq!(parsed.dfl, 48328);
592 assert_eq!(parsed.syncd, 0);
593 assert_eq!(parsed.mode, Mode::Normal);
594 }
595
596 #[test]
597 fn serialize_round_trip_nm_ts_preserves_every_field() {
598 let orig = Bbheader {
599 matype: Matype {
600 ts_gs: TsGs::Ts,
601 sis: true,
602 ccm: true,
603 issyi: true,
604 npd: false,
605 ext: 0,
606 isi: 0x00,
607 },
608 upl: 1504,
609 sync: 0x47,
610 dfl: 48328,
611 syncd: 0,
612 mode: Mode::Normal,
613 issy_in_header: None,
614 };
615 let buf = orig.serialize();
616 let parsed = Bbheader::parse(&buf).unwrap();
617 assert_eq!(orig.matype.ts_gs, parsed.matype.ts_gs);
618 assert_eq!(orig.matype.sis, parsed.matype.sis);
619 assert_eq!(orig.matype.ccm, parsed.matype.ccm);
620 assert_eq!(orig.matype.issyi, parsed.matype.issyi);
621 assert_eq!(orig.matype.npd, parsed.matype.npd);
622 assert_eq!(orig.matype.ext, parsed.matype.ext);
623 assert_eq!(orig.matype.isi, parsed.matype.isi);
624 assert_eq!(orig.upl, parsed.upl);
625 assert_eq!(orig.sync, parsed.sync);
626 assert_eq!(orig.dfl, parsed.dfl);
627 assert_eq!(orig.syncd, parsed.syncd);
628 assert_eq!(orig.mode, parsed.mode);
629 }
630
631 #[test]
632 fn serialize_round_trip_nm_gcs() {
633 let orig = Bbheader {
634 matype: Matype {
635 ts_gs: TsGs::Gcs,
636 sis: true,
637 ccm: false,
638 issyi: false,
639 npd: false,
640 ext: 0,
641 isi: 0x00,
642 },
643 upl: 0,
644 sync: 0x00,
645 dfl: 16384,
646 syncd: 0,
647 mode: Mode::Normal,
648 issy_in_header: None,
649 };
650 let buf = orig.serialize();
651 let parsed = Bbheader::parse(&buf).unwrap();
652 assert_eq!(orig.matype.ts_gs, parsed.matype.ts_gs);
653 assert_eq!(orig.matype.sis, parsed.matype.sis);
654 assert_eq!(orig.matype.ccm, parsed.matype.ccm);
655 assert_eq!(orig.dfl, parsed.dfl);
656 assert_eq!(orig.syncd, parsed.syncd);
657 assert_eq!(orig.mode, parsed.mode);
658 }
659
660 #[test]
661 fn serialize_crc8_always_matches_bytes_0_to_8() {
662 let hdr = Bbheader {
663 matype: Matype {
664 ts_gs: TsGs::Gse,
665 sis: true,
666 ccm: true,
667 issyi: true,
668 npd: false,
669 ext: 0,
670 isi: 0x00,
671 },
672 upl: 0,
673 sync: 0xFF,
674 dfl: 32768,
675 syncd: 0,
676 mode: Mode::Normal,
677 issy_in_header: None,
678 };
679 let buf = hdr.serialize();
680 let computed = crc8(&buf[..9]);
681 assert_eq!(computed ^ buf[9], 0); assert_eq!(buf[9], computed); }
684
685 #[test]
686 fn parse_detects_hem_via_crc_xor_1() {
687 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
689 let result = Bbheader::parse(&hdr).unwrap();
690 assert_eq!(result.mode, Mode::HighEfficiency);
691 }
692
693 #[test]
694 fn parse_hem_extracts_matype_dfl_syncd() {
695 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 assert_eq!(result.matype.ts_gs, TsGs::Ts);
699 assert!(result.matype.sis);
700 assert!(result.matype.ccm);
701 assert!(result.matype.issyi);
702 assert!(!result.matype.npd);
703 assert_eq!(result.matype.ext, 0);
704 assert_eq!(result.dfl, 48328);
705 assert_eq!(result.syncd, 0x0350);
706 }
707
708 #[test]
709 fn parse_hem_preserves_three_issy_bytes() {
710 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
711 let result = Bbheader::parse(&hdr).unwrap();
712 let issy = result.issy_in_header.unwrap();
713 assert_eq!(issy, [0xa4, 0x28, 0xe2]);
715 }
716
717 #[test]
718 fn issy_accessor_decodes_hem_iscr_long() {
719 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
723 let result = Bbheader::parse(&hdr).unwrap();
724 let issy = result.issy().expect("ISSY decode from HEM header");
725 assert_eq!(issy, Issy::IscrLong(0x0024_28E2));
726 }
727
728 #[test]
729 fn issy_accessor_returns_none_for_nm() {
730 let mut hdr = [0u8; BBHEADER_LEN];
731 hdr[0] = 0xF0;
732 hdr[1] = 0x00;
733 hdr[2] = 0x07;
734 hdr[3] = 0xD0;
735 hdr[4] = 0xBC;
736 hdr[5] = 0x00;
737 hdr[6] = 0x47;
738 hdr[7] = 0x00;
739 hdr[8] = 0x00;
740 hdr[9] = crc8(&hdr[..9]);
741 let result = Bbheader::parse(&hdr).unwrap();
742 assert_eq!(result.mode, Mode::Normal);
743 assert!(result.issy().is_none());
744 }
745
746 #[test]
747 fn issy_accessor_falls_back_to_short_form() {
748 let hdr = Bbheader {
752 matype: Matype {
753 ts_gs: TsGs::Ts,
754 sis: true,
755 ccm: true,
756 issyi: true,
757 npd: false,
758 ext: 0,
759 isi: 0x00,
760 },
761 upl: 0,
762 sync: 0,
763 dfl: 50000,
764 syncd: 100,
765 mode: Mode::HighEfficiency,
766 issy_in_header: Some([0x7A, 0xBC, 0x00]),
767 };
768 let issy = hdr.issy().expect("short-form ISSY fallback");
769 assert_eq!(issy, Issy::IscrShort(0x7ABC));
770 }
771
772 #[test]
773 fn parse_hem_leaves_upl_bits_as_zero_and_sync_as_zero() {
774 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
775 let result = Bbheader::parse(&hdr).unwrap();
776 assert_eq!(result.upl, 0);
777 assert_eq!(result.sync, 0);
778 }
779
780 #[test]
781 fn parse_hem_rejects_when_mode_xor_not_0_or_1() {
782 let mut hdr = [0u8; BBHEADER_LEN];
784 hdr[0] = 0xF0;
785 hdr[1] = 0x00;
786 hdr[2] = 0x00;
787 hdr[3] = 0x00;
788 hdr[4] = 0x00;
789 hdr[5] = 0x00;
790 hdr[6] = 0x00;
791 hdr[7] = 0x00;
792 hdr[8] = 0x00;
793 hdr[9] = crc8(&hdr[..9]) ^ 0x02; assert!(Bbheader::parse(&hdr).is_err());
795 }
796
797 #[test]
798 fn parse_same_bytes_different_mode_byte_produces_different_bbheader() {
799 let mut hdr1 = [0xF8, 0x00, 0x00, 0x00, 0xBC, 0xC8, 0x00, 0x03, 0x50, 0x00];
801 hdr1[9] = crc8(&hdr1[..9]); let mut hdr2 = hdr1;
803 hdr2[9] ^= 0x01; let result1 = Bbheader::parse(&hdr1).unwrap();
806 let result2 = Bbheader::parse(&hdr2).unwrap();
807 assert_eq!(result1.mode, Mode::Normal);
808 assert_eq!(result2.mode, Mode::HighEfficiency);
809 }
810
811 #[test]
812 fn serialize_hem_round_trip() {
813 let orig = Bbheader {
814 matype: Matype {
815 ts_gs: TsGs::Ts,
816 sis: true,
817 ccm: true,
818 issyi: true,
819 npd: false,
820 ext: 0,
821 isi: 0x00,
822 },
823 upl: 0, sync: 0, dfl: 48328,
826 syncd: 848,
827 mode: Mode::HighEfficiency,
828 issy_in_header: Some([0xA4, 0x28, 0xE2]),
829 };
830 let buf = orig.serialize();
831 let parsed = Bbheader::parse(&buf).unwrap();
832 assert_eq!(orig.mode, parsed.mode);
833 assert_eq!(orig.matype.ts_gs, parsed.matype.ts_gs);
834 assert_eq!(orig.dfl, parsed.dfl);
835 assert_eq!(orig.syncd, parsed.syncd);
836 assert_eq!(orig.issy_in_header, parsed.issy_in_header);
837 }
838
839 #[test]
840 fn serialize_hem_sets_crc_xor_mode_byte_correctly() {
841 let hdr = Bbheader {
842 matype: Matype {
843 ts_gs: TsGs::Ts,
844 sis: true,
845 ccm: true,
846 issyi: false,
847 npd: false,
848 ext: 0,
849 isi: 0x05,
850 },
851 upl: 0,
852 sync: 0,
853 dfl: 48000,
854 syncd: 0,
855 mode: Mode::HighEfficiency,
856 issy_in_header: Some([0x00, 0x00, 0x00]),
857 };
858 let buf = hdr.serialize();
859 let computed = crc8(&buf[..9]);
860 assert_eq!(buf[9], computed ^ 1);
862 }
863
864 #[test]
865 fn serialize_hem_with_issy_bytes_zero_writes_expected_layout() {
866 let hdr = Bbheader {
867 matype: Matype {
868 ts_gs: TsGs::Ts,
869 sis: true,
870 ccm: true,
871 issyi: true,
872 npd: false,
873 ext: 0,
874 isi: 0x00,
875 },
876 upl: 0,
877 sync: 0,
878 dfl: 50000,
879 syncd: 100,
880 mode: Mode::HighEfficiency,
881 issy_in_header: Some([0x00, 0x00, 0x00]),
882 };
883 let buf = hdr.serialize();
884 let parsed = Bbheader::parse(&buf).unwrap();
885 assert_eq!(parsed.mode, Mode::HighEfficiency);
886 assert_eq!(parsed.issy_in_header, Some([0x00, 0x00, 0x00]));
887 assert_eq!(parsed.dfl, 50000);
888 assert_eq!(parsed.syncd, 100);
889 }
890
891 #[test]
892 fn parse_valid_dvbt2_hem_bbframe_rai() {
893 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
895 assert_eq!(Bbheader::parse(&hdr).unwrap().dfl, 48328);
896 }
897
898 #[test]
899 fn exhaustive_tsgs_sweep() {
900 let mut matched = 0u16;
901 for byte in 0u8..=0xFF {
902 if let Ok(v) = TsGs::try_from(byte) {
903 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
904 matched += 1;
905 }
906 }
907 assert_eq!(matched, 4, "expected 4 matched variants");
908 }
909
910 #[test]
911 fn exhaustive_mode_sweep() {
912 let mut matched = 0u16;
913 for byte in 0u8..=0xFF {
914 if let Ok(v) = Mode::try_from(byte) {
915 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
916 matched += 1;
917 }
918 }
919 assert_eq!(matched, 2, "expected 2 matched variants");
920 }
921
922 #[test]
923 fn trait_parse_and_serialize_round_trip() {
924 let orig = Bbheader {
925 matype: Matype {
926 ts_gs: TsGs::Gse,
927 sis: true,
928 ccm: true,
929 issyi: true,
930 npd: false,
931 ext: 0,
932 isi: 0x00,
933 },
934 upl: 0,
935 sync: 0xFF,
936 dfl: 32768,
937 syncd: 0,
938 mode: Mode::Normal,
939 issy_in_header: None,
940 };
941 let v = <Bbheader as Serialize>::to_bytes(&orig);
942 let parsed = <Bbheader as Parse>::parse(&v).unwrap();
943 assert_eq!(orig.matype, parsed.matype);
944 assert_eq!(orig.upl, parsed.upl);
945 assert_eq!(orig.sync, parsed.sync);
946 assert_eq!(orig.dfl, parsed.dfl);
947 assert_eq!(orig.syncd, parsed.syncd);
948 assert_eq!(orig.mode, parsed.mode);
949 }
950
951 #[test]
952 fn serialize_into_rejects_buffer_too_small() {
953 let hdr = Bbheader {
954 matype: Matype {
955 ts_gs: TsGs::Ts,
956 sis: true,
957 ccm: true,
958 issyi: false,
959 npd: false,
960 ext: 0,
961 isi: 0x00,
962 },
963 upl: 0,
964 sync: 0x47,
965 dfl: 0,
966 syncd: 0,
967 mode: Mode::Normal,
968 issy_in_header: None,
969 };
970 let mut small = [0u8; BBHEADER_LEN - 1];
971 let err = hdr.serialize_into(&mut small).unwrap_err();
972 assert_eq!(
973 err,
974 Error::OutputBufferTooSmall {
975 need: BBHEADER_LEN,
976 have: small.len(),
977 }
978 );
979 }
980}