1use dvb_common::{Parse, Serialize};
7use num_enum::TryFromPrimitive;
8
9use crate::crc::crc8;
10use crate::error::Error;
11
12pub const BBHEADER_LEN: usize = 10;
14pub const DFL_MAX_BITS: u16 = 64800;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize))]
25#[repr(u8)]
26pub enum TsGs {
27 Gfps = 0b00,
29 Ts = 0b11,
31 Gcs = 0b01,
33 Gse = 0b10,
35}
36
37impl From<TsGs> for u8 {
38 fn from(t: TsGs) -> Self {
39 t as u8
40 }
41}
42
43impl From<num_enum::TryFromPrimitiveError<TsGs>> for Error {
44 fn from(e: num_enum::TryFromPrimitiveError<TsGs>) -> Self {
45 Error::UnsupportedTsGs { ts_gs: e.number }
46 }
47}
48
49impl std::fmt::Display for TsGs {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 write!(f, "TsGs::{self:?}")
52 }
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
57#[cfg_attr(feature = "serde", derive(serde::Serialize))]
58#[repr(u8)]
59#[non_exhaustive]
60pub enum Mode {
61 Normal = 0,
63 HighEfficiency = 1,
65}
66
67impl From<num_enum::TryFromPrimitiveError<Mode>> for Error {
68 fn from(e: num_enum::TryFromPrimitiveError<Mode>) -> Self {
69 Error::InvalidMode { mode: e.number }
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79#[cfg_attr(feature = "serde", derive(serde::Serialize))]
80pub struct Matype {
81 pub ts_gs: TsGs,
83 pub sis: bool,
85 pub ccm: bool,
87 pub issyi: bool,
89 pub npd: bool,
91 pub ext: u8,
93 pub isi: u8,
95}
96
97impl Matype {
98 const MASK_TS_GS: u8 = 0xC0;
99 const MASK_SIS: u8 = 0x20;
100 const MASK_CCM: u8 = 0x10;
101 const MASK_ISSYI: u8 = 0x08;
102 const MASK_NPD: u8 = 0x04;
103 const MASK_EXT: u8 = 0x03;
104}
105
106impl TryFrom<[u8; 2]> for Matype {
107 type Error = Error;
108
109 fn try_from(bytes: [u8; 2]) -> Result<Self, Self::Error> {
110 let matype1 = bytes[0];
111 let matype2 = bytes[1];
112
113 let ts_gs = TsGs::try_from((matype1 & Matype::MASK_TS_GS) >> 6)?;
114 let sis = matype1 & Matype::MASK_SIS != 0;
115 let ccm = matype1 & Matype::MASK_CCM != 0;
116 let issyi = matype1 & Matype::MASK_ISSYI != 0;
117 let npd = matype1 & Matype::MASK_NPD != 0;
118 let ext = matype1 & Matype::MASK_EXT;
119
120 Ok(Matype {
121 ts_gs,
122 sis,
123 ccm,
124 issyi,
125 npd,
126 ext,
127 isi: matype2,
128 })
129 }
130}
131
132impl From<Matype> for [u8; 2] {
133 fn from(m: Matype) -> Self {
134 let mut matype1: u8 = 0;
138 matype1 |= (u8::from(m.ts_gs) << 6) & Matype::MASK_TS_GS;
139 if m.sis {
140 matype1 |= Matype::MASK_SIS;
141 }
142 if m.ccm {
143 matype1 |= Matype::MASK_CCM;
144 }
145 if m.issyi {
146 matype1 |= Matype::MASK_ISSYI;
147 }
148 if m.npd {
149 matype1 |= Matype::MASK_NPD;
150 }
151 matype1 |= m.ext & Matype::MASK_EXT;
152 [matype1, m.isi]
153 }
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165#[cfg_attr(feature = "serde", derive(serde::Serialize))]
166pub struct Bbheader {
167 pub matype: Matype,
169 pub upl: u16,
171 pub sync: u8,
173 pub dfl: u16,
175 pub syncd: u16,
177 pub mode: Mode,
179 pub issy_in_header: Option<[u8; 3]>,
181}
182
183impl<'a> Parse<'a> for Bbheader {
184 type Error = Error;
185
186 fn parse(bytes: &'a [u8]) -> Result<Self, Self::Error> {
187 if bytes.len() < BBHEADER_LEN {
188 return Err(Error::BufferTooShort {
189 need: BBHEADER_LEN,
190 have: bytes.len(),
191 what: "BBHEADER",
192 });
193 }
194
195 let matype_bytes = [bytes[0], bytes[1]];
196 let matype = Matype::try_from(matype_bytes)?;
197 let dfl = u16::from_be_bytes([bytes[4], bytes[5]]);
198 let syncd = u16::from_be_bytes([bytes[7], bytes[8]]);
199 let crc_stored = bytes[9];
200
201 if dfl > DFL_MAX_BITS {
202 return Err(Error::DflOutOfRange {
203 dfl,
204 max: DFL_MAX_BITS,
205 });
206 }
207
208 let computed_crc = crc8(&bytes[..9]);
215 let mode_val = computed_crc ^ crc_stored;
216 let mode = Mode::try_from(mode_val)?;
217
218 let (upl, sync, issy_in_header) = match mode {
219 Mode::Normal => (u16::from_be_bytes([bytes[2], bytes[3]]), bytes[6], None),
220 Mode::HighEfficiency => {
221 (0, 0, Some([bytes[2], bytes[3], bytes[6]]))
224 }
225 };
226
227 Ok(Bbheader {
228 matype,
229 upl,
230 sync,
231 dfl,
232 syncd,
233 mode,
234 issy_in_header,
235 })
236 }
237}
238
239impl Serialize for Bbheader {
240 type Error = Error;
241
242 fn serialized_len(&self) -> usize {
243 BBHEADER_LEN
244 }
245
246 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, Self::Error> {
247 if buf.len() < BBHEADER_LEN {
248 return Err(Error::OutputBufferTooSmall {
249 need: BBHEADER_LEN,
250 have: buf.len(),
251 });
252 }
253
254 let ma = <[u8; 2]>::from(self.matype);
255 buf[0] = ma[0];
256 buf[1] = ma[1];
257
258 match self.mode {
259 Mode::Normal => {
260 let upl = self.upl.to_be_bytes();
261 buf[2] = upl[0];
262 buf[3] = upl[1];
263 let dfl = self.dfl.to_be_bytes();
264 buf[4] = dfl[0];
265 buf[5] = dfl[1];
266 buf[6] = self.sync;
267 let syncd = self.syncd.to_be_bytes();
268 buf[7] = syncd[0];
269 buf[8] = syncd[1];
270 }
271 Mode::HighEfficiency => {
272 if let Some(issy) = self.issy_in_header {
273 buf[2] = issy[0];
274 buf[3] = issy[1];
275 let dfl = self.dfl.to_be_bytes();
276 buf[4] = dfl[0];
277 buf[5] = dfl[1];
278 buf[6] = issy[2];
279 let syncd = self.syncd.to_be_bytes();
280 buf[7] = syncd[0];
281 buf[8] = syncd[1];
282 } else {
283 let dfl = self.dfl.to_be_bytes();
284 buf[4] = dfl[0];
285 buf[5] = dfl[1];
286 let syncd = self.syncd.to_be_bytes();
287 buf[7] = syncd[0];
288 buf[8] = syncd[1];
289 }
290 }
291 }
292
293 let computed = crc8(&buf[..9]);
295 buf[9] = computed ^ (self.mode as u8);
296
297 Ok(BBHEADER_LEN)
298 }
299}
300
301impl Bbheader {
302 pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
308 <Self as Parse>::parse(bytes)
309 }
310
311 pub fn serialize(&self) -> [u8; BBHEADER_LEN] {
313 let v = <Self as Serialize>::to_bytes(self);
314 let mut buf = [0u8; BBHEADER_LEN];
315 buf.copy_from_slice(&v);
316 buf
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn parse_rejects_buffer_shorter_than_10() {
326 assert!(Bbheader::parse(&[0u8; 9]).is_err());
327 }
328
329 #[test]
330 fn parse_nm_ts_extracts_all_fields() {
331 let mut hdr = [0u8; BBHEADER_LEN];
337 hdr[0] = 0xF0; hdr[1] = 0x00; let upl: u16 = 0x07D0; hdr[2..4].copy_from_slice(&upl.to_be_bytes());
341 let dfl: u16 = 0xBC00; hdr[4..6].copy_from_slice(&dfl.to_be_bytes());
343 hdr[6] = 0x47; let syncd: u16 = 0x0000; hdr[7..9].copy_from_slice(&syncd.to_be_bytes());
346 hdr[9] = crc8(&hdr[..9]); let result = Bbheader::parse(&hdr).unwrap();
349 assert_eq!(result.mode, Mode::Normal);
350 assert_eq!(result.matype.ts_gs, TsGs::Ts);
351 assert!(result.matype.sis);
352 assert!(result.matype.ccm);
353 assert!(!result.matype.issyi);
354 assert!(!result.matype.npd);
355 assert_eq!(result.matype.ext, 0);
356 assert_eq!(result.matype.isi, 0x00);
357 assert_eq!(result.upl, upl);
358 assert_eq!(result.sync, 0x47);
359 assert_eq!(result.dfl, dfl);
360 assert_eq!(result.syncd, syncd);
361 }
362
363 #[test]
364 fn parse_nm_gcs_treats_sync_as_transport_protocol_byte() {
365 let mut hdr = [0u8; BBHEADER_LEN];
366 hdr[0] = 0x50; hdr[1] = 0x00;
368 let upl: u16 = 0x0000; hdr[2..4].copy_from_slice(&upl.to_be_bytes());
370 let dfl: u16 = 0x4000; hdr[4..6].copy_from_slice(&dfl.to_be_bytes());
372 hdr[6] = 0x3C; let syncd: u16 = 0x0000;
374 hdr[7..9].copy_from_slice(&syncd.to_be_bytes());
375 hdr[9] = crc8(&hdr[..9]);
376
377 let result = Bbheader::parse(&hdr).unwrap();
378 assert_eq!(result.mode, Mode::Normal);
379 assert_eq!(result.matype.ts_gs, TsGs::Gcs);
380 assert_eq!(result.sync, 0x3C);
381 assert_eq!(result.upl, upl);
382 }
383
384 #[test]
385 fn parse_detects_nm_via_crc_xor_0() {
386 let mut hdr = [0u8; BBHEADER_LEN];
388 hdr[0] = 0xF0;
389 hdr[1] = 0x00;
390 hdr[2] = 0x07;
391 hdr[3] = 0xD0; hdr[4] = 0xBC;
393 hdr[5] = 0x00; hdr[6] = 0x47; hdr[7] = 0x00;
396 hdr[8] = 0x00; hdr[9] = crc8(&hdr[..9]); let result = Bbheader::parse(&hdr).unwrap();
400 assert_eq!(result.mode, Mode::Normal);
401 }
402
403 #[test]
404 fn parse_rejects_crc_mismatch_in_both_modes() {
405 let mut hdr = [0u8; BBHEADER_LEN];
406 hdr[0] = 0xF0;
407 hdr[1] = 0x00;
408 hdr[2] = 0x07;
409 hdr[3] = 0xD0;
410 hdr[4] = 0xBC;
411 hdr[5] = 0x00;
412 hdr[6] = 0x47;
413 hdr[7] = 0x00;
414 hdr[8] = 0x00;
415 hdr[9] = 0xFF; let result = Bbheader::parse(&hdr);
418 assert!(result.is_err());
419 }
420
421 #[test]
422 fn parse_matype_extracts_ts_gs_enum_for_each_of_gfps_ts_gcs_gse() {
423 for (ts_gs_val, expected) in [
424 (0b00, TsGs::Gfps),
425 (0b01, TsGs::Gcs),
426 (0b10, TsGs::Gse),
427 (0b11, TsGs::Ts),
428 ] {
429 let ma1 = (ts_gs_val << 6) | 0x30; let mut hdr = [0u8; BBHEADER_LEN];
431 hdr[0] = ma1;
432 hdr[1] = 0x00;
433 hdr[2..9].copy_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
434 hdr[9] = crc8(&hdr[..9]);
435 let result = Bbheader::parse(&hdr).unwrap();
436 assert_eq!(result.matype.ts_gs, expected, "ts_gs=0x{:02b}", ts_gs_val);
437 }
438 }
439
440 #[test]
441 fn parse_matype_extracts_sis_isi_on_multi_stream() {
442 let mut hdr = [0u8; BBHEADER_LEN];
444 hdr[0] = 0xD0; hdr[1] = 0xAB; hdr[2] = 0x07;
447 hdr[3] = 0xD0;
448 hdr[4] = 0xBC;
449 hdr[5] = 0x00;
450 hdr[6] = 0x47;
451 hdr[7] = 0x00;
452 hdr[8] = 0x00;
453 hdr[9] = crc8(&hdr[..9]);
454
455 let result = Bbheader::parse(&hdr).unwrap();
456 assert!(!result.matype.sis);
457 assert_eq!(result.matype.isi, 0xAB);
458 }
459
460 #[test]
461 fn parse_matype_extracts_roll_off_2_bits_as_ext_for_s2_context() {
462 let mut hdr = [0u8; BBHEADER_LEN];
464 hdr[0] = 0xF3; hdr[1] = 0x00;
466 hdr[2] = 0x07;
467 hdr[3] = 0xD0;
468 hdr[4] = 0xBC;
469 hdr[5] = 0x00;
470 hdr[6] = 0x47;
471 hdr[7] = 0x00;
472 hdr[8] = 0x00;
473 hdr[9] = crc8(&hdr[..9]);
474
475 let result = Bbheader::parse(&hdr).unwrap();
476 assert_eq!(result.matype.ext, 0b11);
477 }
478
479 #[test]
480 fn serialize_nm_produces_expected_bytes() {
481 let hdr = Bbheader {
482 matype: Matype {
483 ts_gs: TsGs::Ts,
484 sis: true,
485 ccm: true,
486 issyi: false,
487 npd: false,
488 ext: 0,
489 isi: 0x00,
490 },
491 upl: 188 * 8,
492 sync: 0x47,
493 dfl: 48328,
494 syncd: 0,
495 mode: Mode::Normal,
496 issy_in_header: None,
497 };
498 let buf = hdr.serialize();
499
500 let parsed = Bbheader::parse(&buf).unwrap();
501 assert_eq!(parsed.matype.ts_gs, TsGs::Ts);
502 assert!(parsed.matype.sis);
503 assert!(parsed.matype.ccm);
504 assert_eq!(parsed.upl, 188 * 8);
505 assert_eq!(parsed.sync, 0x47);
506 assert_eq!(parsed.dfl, 48328);
507 assert_eq!(parsed.syncd, 0);
508 assert_eq!(parsed.mode, Mode::Normal);
509 }
510
511 #[test]
512 fn serialize_round_trip_nm_ts_preserves_every_field() {
513 let orig = Bbheader {
514 matype: Matype {
515 ts_gs: TsGs::Ts,
516 sis: true,
517 ccm: true,
518 issyi: true,
519 npd: false,
520 ext: 0,
521 isi: 0x00,
522 },
523 upl: 1504,
524 sync: 0x47,
525 dfl: 48328,
526 syncd: 0,
527 mode: Mode::Normal,
528 issy_in_header: None,
529 };
530 let buf = orig.serialize();
531 let parsed = Bbheader::parse(&buf).unwrap();
532 assert_eq!(orig.matype.ts_gs, parsed.matype.ts_gs);
533 assert_eq!(orig.matype.sis, parsed.matype.sis);
534 assert_eq!(orig.matype.ccm, parsed.matype.ccm);
535 assert_eq!(orig.matype.issyi, parsed.matype.issyi);
536 assert_eq!(orig.matype.npd, parsed.matype.npd);
537 assert_eq!(orig.matype.ext, parsed.matype.ext);
538 assert_eq!(orig.matype.isi, parsed.matype.isi);
539 assert_eq!(orig.upl, parsed.upl);
540 assert_eq!(orig.sync, parsed.sync);
541 assert_eq!(orig.dfl, parsed.dfl);
542 assert_eq!(orig.syncd, parsed.syncd);
543 assert_eq!(orig.mode, parsed.mode);
544 }
545
546 #[test]
547 fn serialize_round_trip_nm_gcs() {
548 let orig = Bbheader {
549 matype: Matype {
550 ts_gs: TsGs::Gcs,
551 sis: true,
552 ccm: false,
553 issyi: false,
554 npd: false,
555 ext: 0,
556 isi: 0x00,
557 },
558 upl: 0,
559 sync: 0x00,
560 dfl: 16384,
561 syncd: 0,
562 mode: Mode::Normal,
563 issy_in_header: None,
564 };
565 let buf = orig.serialize();
566 let parsed = Bbheader::parse(&buf).unwrap();
567 assert_eq!(orig.matype.ts_gs, parsed.matype.ts_gs);
568 assert_eq!(orig.matype.sis, parsed.matype.sis);
569 assert_eq!(orig.matype.ccm, parsed.matype.ccm);
570 assert_eq!(orig.dfl, parsed.dfl);
571 assert_eq!(orig.syncd, parsed.syncd);
572 assert_eq!(orig.mode, parsed.mode);
573 }
574
575 #[test]
576 fn serialize_crc8_always_matches_bytes_0_to_8() {
577 let hdr = Bbheader {
578 matype: Matype {
579 ts_gs: TsGs::Gse,
580 sis: true,
581 ccm: true,
582 issyi: true,
583 npd: false,
584 ext: 0,
585 isi: 0x00,
586 },
587 upl: 0,
588 sync: 0xFF,
589 dfl: 32768,
590 syncd: 0,
591 mode: Mode::Normal,
592 issy_in_header: None,
593 };
594 let buf = hdr.serialize();
595 let computed = crc8(&buf[..9]);
596 assert_eq!(computed ^ buf[9], 0); assert_eq!(buf[9], computed); }
599
600 #[test]
601 fn parse_detects_hem_via_crc_xor_1() {
602 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
604 let result = Bbheader::parse(&hdr).unwrap();
605 assert_eq!(result.mode, Mode::HighEfficiency);
606 }
607
608 #[test]
609 fn parse_hem_extracts_matype_dfl_syncd() {
610 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
611 let result = Bbheader::parse(&hdr).unwrap();
612 assert_eq!(result.mode, Mode::HighEfficiency);
613 assert_eq!(result.matype.ts_gs, TsGs::Ts);
614 assert!(result.matype.sis);
615 assert!(result.matype.ccm);
616 assert!(result.matype.issyi);
617 assert!(!result.matype.npd);
618 assert_eq!(result.matype.ext, 0);
619 assert_eq!(result.dfl, 48328);
620 assert_eq!(result.syncd, 0x0350);
621 }
622
623 #[test]
624 fn parse_hem_preserves_three_issy_bytes() {
625 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
626 let result = Bbheader::parse(&hdr).unwrap();
627 let issy = result.issy_in_header.unwrap();
628 assert_eq!(issy, [0xa4, 0x28, 0xe2]);
630 }
631
632 #[test]
633 fn parse_hem_leaves_upl_bits_as_zero_and_sync_as_zero() {
634 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
635 let result = Bbheader::parse(&hdr).unwrap();
636 assert_eq!(result.upl, 0);
637 assert_eq!(result.sync, 0);
638 }
639
640 #[test]
641 fn parse_hem_rejects_when_mode_xor_not_0_or_1() {
642 let mut hdr = [0u8; BBHEADER_LEN];
644 hdr[0] = 0xF0;
645 hdr[1] = 0x00;
646 hdr[2] = 0x00;
647 hdr[3] = 0x00;
648 hdr[4] = 0x00;
649 hdr[5] = 0x00;
650 hdr[6] = 0x00;
651 hdr[7] = 0x00;
652 hdr[8] = 0x00;
653 hdr[9] = crc8(&hdr[..9]) ^ 0x02; assert!(Bbheader::parse(&hdr).is_err());
655 }
656
657 #[test]
658 fn parse_same_bytes_different_mode_byte_produces_different_bbheader() {
659 let mut hdr1 = [0xF8, 0x00, 0x00, 0x00, 0xBC, 0xC8, 0x00, 0x03, 0x50, 0x00];
661 hdr1[9] = crc8(&hdr1[..9]); let mut hdr2 = hdr1;
663 hdr2[9] ^= 0x01; let result1 = Bbheader::parse(&hdr1).unwrap();
666 let result2 = Bbheader::parse(&hdr2).unwrap();
667 assert_eq!(result1.mode, Mode::Normal);
668 assert_eq!(result2.mode, Mode::HighEfficiency);
669 }
670
671 #[test]
672 fn serialize_hem_round_trip() {
673 let orig = Bbheader {
674 matype: Matype {
675 ts_gs: TsGs::Ts,
676 sis: true,
677 ccm: true,
678 issyi: true,
679 npd: false,
680 ext: 0,
681 isi: 0x00,
682 },
683 upl: 0, sync: 0, dfl: 48328,
686 syncd: 848,
687 mode: Mode::HighEfficiency,
688 issy_in_header: Some([0xA4, 0x28, 0xE2]),
689 };
690 let buf = orig.serialize();
691 let parsed = Bbheader::parse(&buf).unwrap();
692 assert_eq!(orig.mode, parsed.mode);
693 assert_eq!(orig.matype.ts_gs, parsed.matype.ts_gs);
694 assert_eq!(orig.dfl, parsed.dfl);
695 assert_eq!(orig.syncd, parsed.syncd);
696 assert_eq!(orig.issy_in_header, parsed.issy_in_header);
697 }
698
699 #[test]
700 fn serialize_hem_sets_crc_xor_mode_byte_correctly() {
701 let hdr = Bbheader {
702 matype: Matype {
703 ts_gs: TsGs::Ts,
704 sis: true,
705 ccm: true,
706 issyi: false,
707 npd: false,
708 ext: 0,
709 isi: 0x05,
710 },
711 upl: 0,
712 sync: 0,
713 dfl: 48000,
714 syncd: 0,
715 mode: Mode::HighEfficiency,
716 issy_in_header: Some([0x00, 0x00, 0x00]),
717 };
718 let buf = hdr.serialize();
719 let computed = crc8(&buf[..9]);
720 assert_eq!(buf[9], computed ^ 1);
722 }
723
724 #[test]
725 fn serialize_hem_with_issy_bytes_zero_writes_expected_layout() {
726 let hdr = Bbheader {
727 matype: Matype {
728 ts_gs: TsGs::Ts,
729 sis: true,
730 ccm: true,
731 issyi: true,
732 npd: false,
733 ext: 0,
734 isi: 0x00,
735 },
736 upl: 0,
737 sync: 0,
738 dfl: 50000,
739 syncd: 100,
740 mode: Mode::HighEfficiency,
741 issy_in_header: Some([0x00, 0x00, 0x00]),
742 };
743 let buf = hdr.serialize();
744 let parsed = Bbheader::parse(&buf).unwrap();
745 assert_eq!(parsed.mode, Mode::HighEfficiency);
746 assert_eq!(parsed.issy_in_header, Some([0x00, 0x00, 0x00]));
747 assert_eq!(parsed.dfl, 50000);
748 assert_eq!(parsed.syncd, 100);
749 }
750
751 #[test]
752 fn parse_valid_dvbt2_hem_bbframe_rai() {
753 let hdr: [u8; BBHEADER_LEN] = [0xf8, 0x00, 0xa4, 0x28, 0xbc, 0xc8, 0xe2, 0x03, 0x50, 0x1f];
755 assert_eq!(Bbheader::parse(&hdr).unwrap().dfl, 48328);
756 }
757
758 #[test]
759 fn exhaustive_tsgs_sweep() {
760 let mut matched = 0u16;
761 for byte in 0u8..=0xFF {
762 if let Ok(v) = TsGs::try_from(byte) {
763 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
764 matched += 1;
765 }
766 }
767 assert_eq!(matched, 4, "expected 4 matched variants");
768 }
769
770 #[test]
771 fn exhaustive_mode_sweep() {
772 let mut matched = 0u16;
773 for byte in 0u8..=0xFF {
774 if let Ok(v) = Mode::try_from(byte) {
775 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
776 matched += 1;
777 }
778 }
779 assert_eq!(matched, 2, "expected 2 matched variants");
780 }
781
782 #[test]
783 fn trait_parse_and_serialize_round_trip() {
784 let orig = Bbheader {
785 matype: Matype {
786 ts_gs: TsGs::Gse,
787 sis: true,
788 ccm: true,
789 issyi: true,
790 npd: false,
791 ext: 0,
792 isi: 0x00,
793 },
794 upl: 0,
795 sync: 0xFF,
796 dfl: 32768,
797 syncd: 0,
798 mode: Mode::Normal,
799 issy_in_header: None,
800 };
801 let v = <Bbheader as Serialize>::to_bytes(&orig);
802 let parsed = <Bbheader as Parse>::parse(&v).unwrap();
803 assert_eq!(orig.matype, parsed.matype);
804 assert_eq!(orig.upl, parsed.upl);
805 assert_eq!(orig.sync, parsed.sync);
806 assert_eq!(orig.dfl, parsed.dfl);
807 assert_eq!(orig.syncd, parsed.syncd);
808 assert_eq!(orig.mode, parsed.mode);
809 }
810
811 #[test]
812 fn serialize_into_rejects_buffer_too_small() {
813 let hdr = Bbheader {
814 matype: Matype {
815 ts_gs: TsGs::Ts,
816 sis: true,
817 ccm: true,
818 issyi: false,
819 npd: false,
820 ext: 0,
821 isi: 0x00,
822 },
823 upl: 0,
824 sync: 0x47,
825 dfl: 0,
826 syncd: 0,
827 mode: Mode::Normal,
828 issy_in_header: None,
829 };
830 let mut small = [0u8; BBHEADER_LEN - 1];
831 let err = hdr.serialize_into(&mut small).unwrap_err();
832 assert_eq!(
833 err,
834 Error::OutputBufferTooSmall {
835 need: BBHEADER_LEN,
836 have: small.len(),
837 }
838 );
839 }
840}