1use alloc::vec::Vec;
26
27use crate::data_unit_id::DataUnitId;
28use crate::error::{Error, Result};
29use crate::line_header::{LINE_HEADER_LEN, LineHeader};
30
31pub const TXT_DATA_BLOCK_LEN: usize = 42;
33pub const TELETEXT_FIELD_LEN: usize = LINE_HEADER_LEN + 1 + TXT_DATA_BLOCK_LEN;
35pub const TELETEXT_DATA_UNIT_LENGTH: u8 = 0x2C;
38
39pub const FRAMING_CODE_EBU: u8 = 0b1110_0100;
41pub const FRAMING_CODE_INVERTED: u8 = 0b0001_1011;
43
44pub const VPS_DATA_BLOCK_LEN: usize = 13;
46pub const VPS_FIELD_LEN: usize = LINE_HEADER_LEN + VPS_DATA_BLOCK_LEN;
48
49pub const WSS_FIELD_LEN: usize = LINE_HEADER_LEN + 2;
52pub const WSS_DATA_BLOCK_MASK: u16 = 0x3FFF;
54const WSS_BYTE2_DATA_MASK: u8 = 0x3F;
57pub const WSS_RESERVED_TAIL: u8 = 0b11;
59
60pub const CC_FIELD_LEN: usize = LINE_HEADER_LEN + 2;
63
64pub const MONO_HEADER_LEN: usize = 4;
68
69const MONO_FIRST_SEGMENT: u8 = 0b1000_0000;
71const MONO_LAST_SEGMENT: u8 = 0b0100_0000;
73const MONO_FIELD_PARITY: u8 = 0b0010_0000;
75const MONO_LINE_OFFSET: u8 = 0b0001_1111;
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85#[cfg_attr(feature = "serde", derive(serde::Serialize))]
86pub struct TeletextDataField {
87 pub header: LineHeader,
90 pub framing_code: u8,
92 #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_txt_block"))]
94 pub txt_data_block: [u8; TXT_DATA_BLOCK_LEN],
95}
96
97#[cfg(feature = "serde")]
100fn serialize_txt_block<S>(
101 block: &[u8; TXT_DATA_BLOCK_LEN],
102 s: S,
103) -> core::result::Result<S::Ok, S::Error>
104where
105 S: serde::Serializer,
106{
107 s.serialize_bytes(block)
108}
109
110impl TeletextDataField {
111 pub fn serialized_len(&self) -> usize {
113 TELETEXT_FIELD_LEN
114 }
115
116 pub fn parse(data: &[u8]) -> Result<Self> {
119 if data.len() < TELETEXT_FIELD_LEN {
120 return Err(Error::BufferTooShort {
121 need: TELETEXT_FIELD_LEN,
122 have: data.len(),
123 what: "txt_data_field",
124 });
125 }
126 let header = LineHeader::from_byte(data[0]);
127 let framing_code = data[1];
128 let mut txt_data_block = [0u8; TXT_DATA_BLOCK_LEN];
129 txt_data_block.copy_from_slice(&data[2..2 + TXT_DATA_BLOCK_LEN]);
130 Ok(TeletextDataField {
131 header,
132 framing_code,
133 txt_data_block,
134 })
135 }
136
137 pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
139 if out.len() < TELETEXT_FIELD_LEN {
140 return Err(Error::OutputBufferTooSmall {
141 need: TELETEXT_FIELD_LEN,
142 have: out.len(),
143 });
144 }
145 out[0] = self.header.to_byte()?;
146 out[1] = self.framing_code;
147 out[2..2 + TXT_DATA_BLOCK_LEN].copy_from_slice(&self.txt_data_block);
148 Ok(TELETEXT_FIELD_LEN)
149 }
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157#[cfg_attr(feature = "serde", derive(serde::Serialize))]
158pub struct VpsDataField {
159 pub header: LineHeader,
161 pub vps_data_block: [u8; VPS_DATA_BLOCK_LEN],
163}
164
165impl VpsDataField {
166 pub fn serialized_len(&self) -> usize {
168 VPS_FIELD_LEN
169 }
170
171 pub fn parse(data: &[u8]) -> Result<Self> {
173 if data.len() < VPS_FIELD_LEN {
174 return Err(Error::BufferTooShort {
175 need: VPS_FIELD_LEN,
176 have: data.len(),
177 what: "vps_data_field",
178 });
179 }
180 let header = LineHeader::from_byte(data[0]);
181 let mut vps_data_block = [0u8; VPS_DATA_BLOCK_LEN];
182 vps_data_block.copy_from_slice(&data[1..1 + VPS_DATA_BLOCK_LEN]);
183 Ok(VpsDataField {
184 header,
185 vps_data_block,
186 })
187 }
188
189 pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
191 if out.len() < VPS_FIELD_LEN {
192 return Err(Error::OutputBufferTooSmall {
193 need: VPS_FIELD_LEN,
194 have: out.len(),
195 });
196 }
197 out[0] = self.header.to_byte()?;
198 out[1..1 + VPS_DATA_BLOCK_LEN].copy_from_slice(&self.vps_data_block);
199 Ok(VPS_FIELD_LEN)
200 }
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
209#[cfg_attr(feature = "serde", derive(serde::Serialize))]
210pub struct WssDataField {
211 pub header: LineHeader,
213 pub wss_data_block: u16,
215}
216
217impl WssDataField {
218 pub fn serialized_len(&self) -> usize {
220 WSS_FIELD_LEN
221 }
222
223 pub fn parse(data: &[u8]) -> Result<Self> {
226 if data.len() < WSS_FIELD_LEN {
227 return Err(Error::BufferTooShort {
228 need: WSS_FIELD_LEN,
229 have: data.len(),
230 what: "wss_data_field",
231 });
232 }
233 let header = LineHeader::from_byte(data[0]);
234 let wss_data_block =
236 (((data[1] as u16) << 6) | ((data[2] as u16) >> 2)) & WSS_DATA_BLOCK_MASK;
237 Ok(WssDataField {
238 header,
239 wss_data_block,
240 })
241 }
242
243 pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
246 if out.len() < WSS_FIELD_LEN {
247 return Err(Error::OutputBufferTooSmall {
248 need: WSS_FIELD_LEN,
249 have: out.len(),
250 });
251 }
252 if self.wss_data_block > WSS_DATA_BLOCK_MASK {
253 return Err(Error::FieldTooWide {
254 what: "wss_data_block",
255 value: self.wss_data_block as u32,
256 bits: 14,
257 });
258 }
259 out[0] = self.header.to_byte()?;
260 out[1] = (self.wss_data_block >> 6) as u8;
261 out[2] = (((self.wss_data_block as u8) & WSS_BYTE2_DATA_MASK) << 2) | WSS_RESERVED_TAIL;
262 Ok(WSS_FIELD_LEN)
263 }
264}
265
266#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269#[cfg_attr(feature = "serde", derive(serde::Serialize))]
270pub struct ClosedCaptioningDataField {
271 pub header: LineHeader,
273 pub closed_captioning_data_block: u16,
276}
277
278impl ClosedCaptioningDataField {
279 pub fn serialized_len(&self) -> usize {
281 CC_FIELD_LEN
282 }
283
284 pub fn parse(data: &[u8]) -> Result<Self> {
286 if data.len() < CC_FIELD_LEN {
287 return Err(Error::BufferTooShort {
288 need: CC_FIELD_LEN,
289 have: data.len(),
290 what: "closed_captioning_data_field",
291 });
292 }
293 let header = LineHeader::from_byte(data[0]);
294 let closed_captioning_data_block = u16::from_be_bytes([data[1], data[2]]);
295 Ok(ClosedCaptioningDataField {
296 header,
297 closed_captioning_data_block,
298 })
299 }
300
301 pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
303 if out.len() < CC_FIELD_LEN {
304 return Err(Error::OutputBufferTooSmall {
305 need: CC_FIELD_LEN,
306 have: out.len(),
307 });
308 }
309 out[0] = self.header.to_byte()?;
310 out[1..3].copy_from_slice(&self.closed_captioning_data_block.to_be_bytes());
311 Ok(CC_FIELD_LEN)
312 }
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
323#[cfg_attr(feature = "serde", derive(serde::Serialize))]
324pub struct MonochromeDataField<'a> {
325 pub first_segment: bool,
327 pub last_segment: bool,
329 pub field_parity: bool,
331 pub line_offset: u8,
333 pub first_pixel_position: u16,
336 #[cfg_attr(feature = "serde", serde(borrow))]
338 pub samples: &'a [u8],
339}
340
341impl<'a> MonochromeDataField<'a> {
342 pub fn serialized_len(&self) -> usize {
344 MONO_HEADER_LEN + self.samples.len()
345 }
346
347 pub fn parse(data: &'a [u8]) -> Result<Self> {
351 if data.len() < MONO_HEADER_LEN {
352 return Err(Error::BufferTooShort {
353 need: MONO_HEADER_LEN,
354 have: data.len(),
355 what: "monochrome_data_field header",
356 });
357 }
358 let b0 = data[0];
359 let first_segment = (b0 & MONO_FIRST_SEGMENT) != 0;
360 let last_segment = (b0 & MONO_LAST_SEGMENT) != 0;
361 let field_parity = (b0 & MONO_FIELD_PARITY) != 0;
362 let line_offset = b0 & MONO_LINE_OFFSET;
363 let first_pixel_position = u16::from_be_bytes([data[1], data[2]]);
364 let n_pixels = data[3] as usize;
365 if n_pixels == 0 {
367 return Err(Error::InvalidField {
368 what: "n_pixels",
369 reason: "n_pixels shall be > 0 (ETSI EN 301 775 §4.9.2)",
370 });
371 }
372 if data.len() < MONO_HEADER_LEN + n_pixels {
373 return Err(Error::BufferTooShort {
374 need: MONO_HEADER_LEN + n_pixels,
375 have: data.len(),
376 what: "monochrome Y_value samples",
377 });
378 }
379 let samples = &data[MONO_HEADER_LEN..MONO_HEADER_LEN + n_pixels];
380 Ok(MonochromeDataField {
381 first_segment,
382 last_segment,
383 field_parity,
384 line_offset,
385 first_pixel_position,
386 samples,
387 })
388 }
389
390 pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
393 let total = self.serialized_len();
394 if out.len() < total {
395 return Err(Error::OutputBufferTooSmall {
396 need: total,
397 have: out.len(),
398 });
399 }
400 if self.line_offset > MONO_LINE_OFFSET {
401 return Err(Error::FieldTooWide {
402 what: "line_offset",
403 value: self.line_offset as u32,
404 bits: 5,
405 });
406 }
407 if self.samples.len() > u8::MAX as usize {
408 return Err(Error::FieldTooWide {
409 what: "n_pixels",
410 value: self.samples.len() as u32,
411 bits: 8,
412 });
413 }
414 let mut b0 = self.line_offset;
415 if self.first_segment {
416 b0 |= MONO_FIRST_SEGMENT;
417 }
418 if self.last_segment {
419 b0 |= MONO_LAST_SEGMENT;
420 }
421 if self.field_parity {
422 b0 |= MONO_FIELD_PARITY;
423 }
424 out[0] = b0;
425 out[1..3].copy_from_slice(&self.first_pixel_position.to_be_bytes());
426 out[3] = self.samples.len() as u8;
427 out[MONO_HEADER_LEN..total].copy_from_slice(self.samples);
428 Ok(total)
429 }
430}
431
432#[derive(Debug, Clone, PartialEq, Eq)]
435#[cfg_attr(feature = "serde", derive(serde::Serialize))]
436#[non_exhaustive]
437pub enum DataUnitPayload<'a> {
438 Teletext(TeletextDataField),
440 Vps(VpsDataField),
442 Wss(WssDataField),
444 ClosedCaptioning(ClosedCaptioningDataField),
446 Monochrome(#[cfg_attr(feature = "serde", serde(borrow))] MonochromeDataField<'a>),
448 Stuffing {
451 length: u8,
453 },
454 Opaque(#[cfg_attr(feature = "serde", serde(borrow))] &'a [u8]),
457}
458
459impl<'a> DataUnitPayload<'a> {
460 pub fn serialized_len(&self) -> usize {
462 match self {
463 DataUnitPayload::Teletext(f) => f.serialized_len(),
464 DataUnitPayload::Vps(f) => f.serialized_len(),
465 DataUnitPayload::Wss(f) => f.serialized_len(),
466 DataUnitPayload::ClosedCaptioning(f) => f.serialized_len(),
467 DataUnitPayload::Monochrome(f) => f.serialized_len(),
468 DataUnitPayload::Stuffing { length } => *length as usize,
469 DataUnitPayload::Opaque(b) => b.len(),
470 }
471 }
472
473 pub fn parse(id: DataUnitId, body: &'a [u8]) -> Result<Self> {
477 match id {
478 DataUnitId::EbuTeletextNonSubtitle
479 | DataUnitId::EbuTeletextSubtitle
480 | DataUnitId::InvertedTeletext => {
481 Ok(DataUnitPayload::Teletext(TeletextDataField::parse(body)?))
482 }
483 DataUnitId::Vps => Ok(DataUnitPayload::Vps(VpsDataField::parse(body)?)),
484 DataUnitId::Wss => Ok(DataUnitPayload::Wss(WssDataField::parse(body)?)),
485 DataUnitId::ClosedCaptioning => Ok(DataUnitPayload::ClosedCaptioning(
486 ClosedCaptioningDataField::parse(body)?,
487 )),
488 DataUnitId::Monochrome422Samples => Ok(DataUnitPayload::Monochrome(
489 MonochromeDataField::parse(body)?,
490 )),
491 DataUnitId::Stuffing => {
492 if body.len() > u8::MAX as usize {
493 return Err(Error::InvalidDataUnitLength {
494 length: 0,
495 id: id.to_u8(),
496 reason: "stuffing length exceeds 8 bits",
497 });
498 }
499 Ok(DataUnitPayload::Stuffing {
500 length: body.len() as u8,
501 })
502 }
503 DataUnitId::Reserved(_) | DataUnitId::UserDefined(_) => {
504 Ok(DataUnitPayload::Opaque(body))
505 }
506 }
507 }
508
509 pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
511 match self {
512 DataUnitPayload::Teletext(f) => f.serialize_into(out),
513 DataUnitPayload::Vps(f) => f.serialize_into(out),
514 DataUnitPayload::Wss(f) => f.serialize_into(out),
515 DataUnitPayload::ClosedCaptioning(f) => f.serialize_into(out),
516 DataUnitPayload::Monochrome(f) => f.serialize_into(out),
517 DataUnitPayload::Stuffing { length } => {
518 let n = *length as usize;
519 if out.len() < n {
520 return Err(Error::OutputBufferTooSmall {
521 need: n,
522 have: out.len(),
523 });
524 }
525 for b in out.iter_mut().take(n) {
526 *b = crate::data_unit_id::ID_STUFFING; }
528 Ok(n)
529 }
530 DataUnitPayload::Opaque(b) => {
531 if out.len() < b.len() {
532 return Err(Error::OutputBufferTooSmall {
533 need: b.len(),
534 have: out.len(),
535 });
536 }
537 out[..b.len()].copy_from_slice(b);
538 Ok(b.len())
539 }
540 }
541 }
542}
543
544#[derive(Debug, Clone, PartialEq, Eq)]
547#[cfg_attr(feature = "serde", derive(serde::Serialize))]
548pub struct DataUnit<'a> {
549 pub id: DataUnitId,
551 #[cfg_attr(feature = "serde", serde(borrow))]
553 pub payload: DataUnitPayload<'a>,
554}
555
556impl<'a> DataUnit<'a> {
557 pub fn data_unit_length(&self) -> usize {
559 self.payload.serialized_len()
560 }
561
562 pub fn serialized_len(&self) -> usize {
564 2 + self.data_unit_length()
565 }
566
567 pub fn teletext(id: DataUnitId, field: TeletextDataField) -> Self {
570 DataUnit {
571 id,
572 payload: DataUnitPayload::Teletext(field),
573 }
574 }
575
576 pub fn vps(field: VpsDataField) -> Self {
578 DataUnit {
579 id: DataUnitId::Vps,
580 payload: DataUnitPayload::Vps(field),
581 }
582 }
583
584 pub fn wss(field: WssDataField) -> Self {
586 DataUnit {
587 id: DataUnitId::Wss,
588 payload: DataUnitPayload::Wss(field),
589 }
590 }
591
592 pub fn closed_captioning(field: ClosedCaptioningDataField) -> Self {
594 DataUnit {
595 id: DataUnitId::ClosedCaptioning,
596 payload: DataUnitPayload::ClosedCaptioning(field),
597 }
598 }
599
600 pub fn monochrome(field: MonochromeDataField<'a>) -> Self {
602 DataUnit {
603 id: DataUnitId::Monochrome422Samples,
604 payload: DataUnitPayload::Monochrome(field),
605 }
606 }
607
608 pub fn stuffing(length: u8) -> Self {
610 DataUnit {
611 id: DataUnitId::Stuffing,
612 payload: DataUnitPayload::Stuffing { length },
613 }
614 }
615
616 pub fn parse(data: &'a [u8]) -> Result<(Self, usize)> {
619 if data.len() < 2 {
620 return Err(Error::BufferTooShort {
621 need: 2,
622 have: data.len(),
623 what: "data_unit header (id + length)",
624 });
625 }
626 let id = DataUnitId::from_u8(data[0]);
627 let length = data[1] as usize;
628 let body_end = 2 + length;
629 if data.len() < body_end {
630 return Err(Error::BufferTooShort {
631 need: body_end,
632 have: data.len(),
633 what: "data_unit body",
634 });
635 }
636 let body = &data[2..body_end];
637 let payload = DataUnitPayload::parse(id, body)?;
638 if payload.serialized_len() != length {
641 return Err(Error::InvalidDataUnitLength {
642 length: data[1],
643 id: data[0],
644 reason: "typed payload size does not match data_unit_length",
645 });
646 }
647 Ok((DataUnit { id, payload }, body_end))
648 }
649
650 pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
652 let total = self.serialized_len();
653 if out.len() < total {
654 return Err(Error::OutputBufferTooSmall {
655 need: total,
656 have: out.len(),
657 });
658 }
659 let length = self.data_unit_length();
660 if length > u8::MAX as usize {
661 return Err(Error::FieldTooWide {
662 what: "data_unit_length",
663 value: length as u32,
664 bits: 8,
665 });
666 }
667 out[0] = self.id.to_u8();
668 out[1] = length as u8;
669 let written = self.payload.serialize_into(&mut out[2..total])?;
670 debug_assert_eq!(written, length);
671 Ok(2 + written)
672 }
673}
674
675#[derive(Debug, Clone, PartialEq, Eq)]
681#[cfg_attr(feature = "serde", derive(serde::Serialize))]
682pub struct DataField<'a> {
683 pub data_identifier: u8,
686 #[cfg_attr(feature = "serde", serde(borrow))]
688 pub data_units: Vec<DataUnit<'a>>,
689}
690
691impl<'a> DataField<'a> {
692 pub fn new(data_identifier: u8, data_units: Vec<DataUnit<'a>>) -> Self {
694 DataField {
695 data_identifier,
696 data_units,
697 }
698 }
699
700 pub fn serialized_len(&self) -> usize {
702 1 + self
703 .data_units
704 .iter()
705 .map(DataUnit::serialized_len)
706 .sum::<usize>()
707 }
708
709 pub fn parse(data: &'a [u8]) -> Result<Self> {
712 if data.is_empty() {
713 return Err(Error::BufferTooShort {
714 need: 1,
715 have: 0,
716 what: "data_identifier",
717 });
718 }
719 let data_identifier = data[0];
720 let mut data_units = Vec::new();
721 let mut off = 1;
722 while off < data.len() {
723 let (unit, consumed) = DataUnit::parse(&data[off..])?;
724 data_units.push(unit);
725 off += consumed;
726 }
727 Ok(DataField {
728 data_identifier,
729 data_units,
730 })
731 }
732
733 pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
735 let total = self.serialized_len();
736 if out.len() < total {
737 return Err(Error::OutputBufferTooSmall {
738 need: total,
739 have: out.len(),
740 });
741 }
742 out[0] = self.data_identifier;
743 let mut off = 1;
744 for unit in &self.data_units {
745 off += unit.serialize_into(&mut out[off..])?;
746 }
747 Ok(off)
748 }
749}
750
751#[cfg(test)]
752mod tests {
753 use super::*;
754 use crate::line_header::LineHeader;
755 use alloc::vec;
756
757 fn unit_round_trip(unit: &DataUnit, expected_wire: &[u8]) {
759 let mut out = vec![0u8; unit.serialized_len()];
760 let n = unit.serialize_into(&mut out).unwrap();
761 assert_eq!(n, unit.serialized_len());
762 assert_eq!(out, expected_wire, "exact wire bytes");
763 let (re, consumed) = DataUnit::parse(&out).unwrap();
764 assert_eq!(consumed, out.len());
765 assert_eq!(&re, unit, "reparse must equal the original");
766 }
767
768 #[test]
769 fn teletext_exact_wire_bytes() {
770 let block = [0xAAu8; TXT_DATA_BLOCK_LEN];
771 let field = TeletextDataField {
772 header: LineHeader::new(true, 7), framing_code: FRAMING_CODE_EBU,
774 txt_data_block: block,
775 };
776 let unit = DataUnit::teletext(DataUnitId::EbuTeletextSubtitle, field);
777
778 let mut expected = vec![0x03, 0x2C, 0xE7, FRAMING_CODE_EBU];
780 expected.extend_from_slice(&block);
781 assert_eq!(unit.data_unit_length(), TELETEXT_DATA_UNIT_LENGTH as usize);
782 unit_round_trip(&unit, &expected);
783 }
784
785 #[test]
786 fn vps_exact_wire_bytes() {
787 let block = [
788 0x01u8, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
789 ];
790 let field = VpsDataField {
791 header: LineHeader::new(true, 16), vps_data_block: block,
793 };
794 let unit = DataUnit::vps(field);
795
796 let mut expected = vec![0xC3, 0x0E, 0xF0];
798 expected.extend_from_slice(&block);
799 unit_round_trip(&unit, &expected);
800 }
801
802 #[test]
803 fn wss_exact_wire_bytes_and_bit_packing() {
804 let field = WssDataField {
806 header: LineHeader::new(true, 23), wss_data_block: 0x3A5C,
808 };
809 let unit = DataUnit::wss(field);
810
811 let expected = vec![0xC4, 0x03, 0xF7, 0xE9, 0x73];
816 unit_round_trip(&unit, &expected);
817 }
818
819 #[test]
820 fn cc_exact_wire_bytes() {
821 let field = ClosedCaptioningDataField {
822 header: LineHeader::new(false, 21), closed_captioning_data_block: 0x9425,
824 };
825 let unit = DataUnit::closed_captioning(field);
826
827 let expected = vec![0xC5, 0x03, 0xD5, 0x94, 0x25];
829 unit_round_trip(&unit, &expected);
830 }
831
832 #[test]
833 fn monochrome_exact_wire_bytes() {
834 let samples = [0x10u8, 0x40, 0x80, 0xEB];
835 let field = MonochromeDataField {
836 first_segment: true,
837 last_segment: false,
838 field_parity: true,
839 line_offset: 10,
840 first_pixel_position: 0x0123,
841 samples: &samples,
842 };
843 let unit = DataUnit::monochrome(field);
844
845 let mut expected = vec![0xC6, 0x08, 0xAA, 0x01, 0x23, 0x04];
847 expected.extend_from_slice(&samples);
848 unit_round_trip(&unit, &expected);
849 }
850
851 #[test]
852 fn stuffing_exact_wire_bytes() {
853 let unit = DataUnit::stuffing(3);
854 let expected = vec![0xFF, 0x03, 0xFF, 0xFF, 0xFF];
856 unit_round_trip(&unit, &expected);
857 }
858
859 #[test]
860 fn opaque_reserved_round_trips() {
861 let body = [0xDEu8, 0xAD, 0xBE];
862 let unit = DataUnit {
863 id: DataUnitId::Reserved(0x55),
864 payload: DataUnitPayload::Opaque(&body),
865 };
866 let expected = vec![0x55, 0x03, 0xDE, 0xAD, 0xBE];
867 unit_round_trip(&unit, &expected);
868 }
869
870 #[test]
872 fn mutating_a_field_changes_wire_bytes() {
873 let block = [0u8; VPS_DATA_BLOCK_LEN];
874 let a = DataUnit::vps(VpsDataField {
875 header: LineHeader::new(true, 16),
876 vps_data_block: block,
877 });
878 let mut block_b = block;
879 block_b[0] = 0xFF;
880 let b = DataUnit::vps(VpsDataField {
881 header: LineHeader::new(true, 16),
882 vps_data_block: block_b,
883 });
884
885 let mut out_a = vec![0u8; a.serialized_len()];
886 a.serialize_into(&mut out_a).unwrap();
887 let mut out_b = vec![0u8; b.serialized_len()];
888 b.serialize_into(&mut out_b).unwrap();
889 assert_ne!(
890 out_a, out_b,
891 "different vps_data_block must change wire bytes"
892 );
893
894 let c = DataUnit::vps(VpsDataField {
896 header: LineHeader::new(false, 16),
897 vps_data_block: block,
898 });
899 let mut out_c = vec![0u8; c.serialized_len()];
900 c.serialize_into(&mut out_c).unwrap();
901 assert_ne!(out_a, out_c, "field_parity must change the header byte");
902 assert_eq!(out_a[2] & 0b0010_0000, 0b0010_0000);
903 assert_eq!(out_c[2] & 0b0010_0000, 0);
904 }
905
906 #[test]
908 fn multi_unit_data_field_round_trip() {
909 let vps = DataUnit::vps(VpsDataField {
910 header: LineHeader::new(true, 16),
911 vps_data_block: [0x11; VPS_DATA_BLOCK_LEN],
912 });
913 let wss = DataUnit::wss(WssDataField {
914 header: LineHeader::new(true, 23),
915 wss_data_block: 0x1234,
916 });
917 let block = [0x42u8; TXT_DATA_BLOCK_LEN];
918 let txt = DataUnit::teletext(
919 DataUnitId::EbuTeletextNonSubtitle,
920 TeletextDataField {
921 header: LineHeader::new(false, 9),
922 framing_code: FRAMING_CODE_EBU,
923 txt_data_block: block,
924 },
925 );
926
927 let field = DataField::new(0x10, vec![vps, wss, txt]);
928 let mut out = vec![0u8; field.serialized_len()];
929 let n = field.serialize_into(&mut out).unwrap();
930 assert_eq!(n, field.serialized_len());
931
932 assert_eq!(out[0], 0x10);
934
935 let parsed = DataField::parse(&out).unwrap();
936 assert_eq!(parsed, field, "multi-unit data field must round-trip");
937 assert_eq!(parsed.data_units.len(), 3);
938 assert_eq!(parsed.data_units[0].id, DataUnitId::Vps);
939 assert_eq!(parsed.data_units[1].id, DataUnitId::Wss);
940 assert_eq!(parsed.data_units[2].id, DataUnitId::EbuTeletextNonSubtitle);
941
942 let mut out2 = vec![0u8; parsed.serialized_len()];
944 parsed.serialize_into(&mut out2).unwrap();
945 assert_eq!(out, out2);
946 }
947
948 #[test]
949 fn rejects_truncated_data_unit() {
950 let data = [0xC3u8, 0x0E, 0x00];
952 assert!(DataUnit::parse(&data).is_err());
953 }
954
955 #[test]
956 fn rejects_length_mismatch() {
957 let data = [
959 0xC3u8, 0x0F, 0xF0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
960 ];
961 assert!(matches!(
962 DataUnit::parse(&data),
963 Err(Error::InvalidDataUnitLength { .. })
964 ));
965 }
966
967 #[test]
968 fn wss_bit_packing_recovers_value() {
969 for v in [0u16, 1, 0x3FFF, 0x2A55, 0x1FFF] {
970 let f = WssDataField {
971 header: LineHeader::new(true, 23),
972 wss_data_block: v,
973 };
974 let mut out = [0u8; WSS_FIELD_LEN];
975 f.serialize_into(&mut out).unwrap();
976 let re = WssDataField::parse(&out).unwrap();
977 assert_eq!(re.wss_data_block, v, "wss value {v:#06X}");
978 assert_eq!(out[2] & 0b11, WSS_RESERVED_TAIL);
980 }
981 }
982
983 #[test]
985 fn monochrome_rejects_zero_n_pixels() {
986 let data = [0b1110_0111u8, 0x00, 0x00, 0x00];
989 let result = MonochromeDataField::parse(&data);
990 assert!(
991 matches!(
992 result,
993 Err(crate::error::Error::InvalidField {
994 what: "n_pixels",
995 ..
996 })
997 ),
998 "n_pixels=0 must be rejected with InvalidField, got: {result:?}"
999 );
1000 }
1001
1002 #[test]
1005 fn mutating_teletext_framing_code_changes_wire_bytes() {
1006 let block = [0xBBu8; TXT_DATA_BLOCK_LEN];
1007 let a = DataUnit::teletext(
1008 DataUnitId::EbuTeletextNonSubtitle,
1009 TeletextDataField {
1010 header: LineHeader::new(true, 7),
1011 framing_code: FRAMING_CODE_EBU,
1012 txt_data_block: block,
1013 },
1014 );
1015 let b = DataUnit::teletext(
1016 DataUnitId::EbuTeletextNonSubtitle,
1017 TeletextDataField {
1018 header: LineHeader::new(true, 7),
1019 framing_code: FRAMING_CODE_INVERTED,
1020 txt_data_block: block,
1021 },
1022 );
1023 let mut out_a = vec![0u8; a.serialized_len()];
1024 a.serialize_into(&mut out_a).unwrap();
1025 let mut out_b = vec![0u8; b.serialized_len()];
1026 b.serialize_into(&mut out_b).unwrap();
1027 assert_ne!(
1028 out_a, out_b,
1029 "different framing_code must change wire bytes"
1030 );
1031 }
1032
1033 #[test]
1034 fn mutating_teletext_txt_data_block_changes_wire_bytes() {
1035 let block_a = [0x00u8; TXT_DATA_BLOCK_LEN];
1036 let mut block_b = block_a;
1037 block_b[0] = 0xFF;
1038 let a = DataUnit::teletext(
1039 DataUnitId::EbuTeletextNonSubtitle,
1040 TeletextDataField {
1041 header: LineHeader::new(true, 7),
1042 framing_code: FRAMING_CODE_EBU,
1043 txt_data_block: block_a,
1044 },
1045 );
1046 let b = DataUnit::teletext(
1047 DataUnitId::EbuTeletextNonSubtitle,
1048 TeletextDataField {
1049 header: LineHeader::new(true, 7),
1050 framing_code: FRAMING_CODE_EBU,
1051 txt_data_block: block_b,
1052 },
1053 );
1054 let mut out_a = vec![0u8; a.serialized_len()];
1055 a.serialize_into(&mut out_a).unwrap();
1056 let mut out_b = vec![0u8; b.serialized_len()];
1057 b.serialize_into(&mut out_b).unwrap();
1058 assert_ne!(
1059 out_a, out_b,
1060 "different txt_data_block must change wire bytes"
1061 );
1062 }
1063
1064 #[test]
1065 fn mutating_wss_data_block_changes_wire_bytes() {
1066 let a = DataUnit::wss(WssDataField {
1067 header: LineHeader::new(true, 23),
1068 wss_data_block: 0x0000,
1069 });
1070 let b = DataUnit::wss(WssDataField {
1071 header: LineHeader::new(true, 23),
1072 wss_data_block: 0x3FFF,
1073 });
1074 let mut out_a = vec![0u8; a.serialized_len()];
1075 a.serialize_into(&mut out_a).unwrap();
1076 let mut out_b = vec![0u8; b.serialized_len()];
1077 b.serialize_into(&mut out_b).unwrap();
1078 assert_ne!(
1079 out_a, out_b,
1080 "different wss_data_block must change wire bytes"
1081 );
1082 }
1083
1084 #[test]
1085 fn mutating_cc_data_block_changes_wire_bytes() {
1086 let a = DataUnit::closed_captioning(ClosedCaptioningDataField {
1087 header: LineHeader::new(false, 21),
1088 closed_captioning_data_block: 0x0000,
1089 });
1090 let b = DataUnit::closed_captioning(ClosedCaptioningDataField {
1091 header: LineHeader::new(false, 21),
1092 closed_captioning_data_block: 0xFFFF,
1093 });
1094 let mut out_a = vec![0u8; a.serialized_len()];
1095 a.serialize_into(&mut out_a).unwrap();
1096 let mut out_b = vec![0u8; b.serialized_len()];
1097 b.serialize_into(&mut out_b).unwrap();
1098 assert_ne!(
1099 out_a, out_b,
1100 "different closed_captioning_data_block must change wire bytes"
1101 );
1102 }
1103
1104 #[test]
1105 fn mutating_monochrome_first_segment_flag_changes_wire_bytes() {
1106 let samples = [0x10u8, 0x80];
1107 let a = DataUnit::monochrome(MonochromeDataField {
1108 first_segment: true,
1109 last_segment: false,
1110 field_parity: true,
1111 line_offset: 10,
1112 first_pixel_position: 0,
1113 samples: &samples,
1114 });
1115 let b = DataUnit::monochrome(MonochromeDataField {
1116 first_segment: false,
1117 last_segment: false,
1118 field_parity: true,
1119 line_offset: 10,
1120 first_pixel_position: 0,
1121 samples: &samples,
1122 });
1123 let mut out_a = vec![0u8; a.serialized_len()];
1124 a.serialize_into(&mut out_a).unwrap();
1125 let mut out_b = vec![0u8; b.serialized_len()];
1126 b.serialize_into(&mut out_b).unwrap();
1127 assert_ne!(
1128 out_a, out_b,
1129 "different first_segment flag must change the first wire byte"
1130 );
1131 }
1132
1133 #[test]
1134 fn mutating_monochrome_y_sample_changes_wire_bytes() {
1135 let samples_a = [0x10u8, 0x80];
1136 let mut samples_b = samples_a;
1137 samples_b[0] = 0xFF;
1138 let a = DataUnit::monochrome(MonochromeDataField {
1139 first_segment: true,
1140 last_segment: true,
1141 field_parity: true,
1142 line_offset: 10,
1143 first_pixel_position: 0,
1144 samples: &samples_a,
1145 });
1146 let b = DataUnit::monochrome(MonochromeDataField {
1147 first_segment: true,
1148 last_segment: true,
1149 field_parity: true,
1150 line_offset: 10,
1151 first_pixel_position: 0,
1152 samples: &samples_b,
1153 });
1154 let mut out_a = vec![0u8; a.serialized_len()];
1155 a.serialize_into(&mut out_a).unwrap();
1156 let mut out_b = vec![0u8; b.serialized_len()];
1157 b.serialize_into(&mut out_b).unwrap();
1158 assert_ne!(out_a, out_b, "different Y sample must change wire bytes");
1159 }
1160
1161 #[test]
1168 fn every_non_opaque_data_unit_id_has_a_typed_payload() {
1169 use crate::data_unit_id::{
1170 ID_CLOSED_CAPTIONING, ID_EBU_TELETEXT_NON_SUBTITLE, ID_EBU_TELETEXT_SUBTITLE,
1171 ID_INVERTED_TELETEXT, ID_MONOCHROME_422_SAMPLES, ID_STUFFING, ID_VPS, ID_WSS,
1172 };
1173 let typed_ids: &[u8] = &[
1175 ID_EBU_TELETEXT_NON_SUBTITLE,
1176 ID_EBU_TELETEXT_SUBTITLE,
1177 ID_INVERTED_TELETEXT,
1178 ID_VPS,
1179 ID_WSS,
1180 ID_CLOSED_CAPTIONING,
1181 ID_MONOCHROME_422_SAMPLES,
1183 ID_STUFFING,
1184 ];
1185
1186 let teletext_body = {
1188 let mut b = vec![0u8; TELETEXT_FIELD_LEN];
1189 b[0] = 0xE7;
1191 b[1] = FRAMING_CODE_EBU;
1192 b
1193 };
1194 let vps_body = {
1195 let mut b = vec![0u8; VPS_FIELD_LEN];
1196 b[0] = 0xF0; b
1198 };
1199 let wss_body = {
1200 let mut b = vec![0u8; WSS_FIELD_LEN];
1201 b[0] = 0xF7; b
1203 };
1204 let cc_body = {
1205 let mut b = vec![0u8; CC_FIELD_LEN];
1206 b[0] = 0xD5; b
1208 };
1209 let mono_body = vec![0b1110_0111u8, 0x00, 0x00, 0x01, 0x80];
1211 let stuffing_body = vec![0xFFu8; 3];
1212
1213 let bodies: &[&[u8]] = &[
1214 &teletext_body,
1215 &teletext_body,
1216 &teletext_body,
1217 &vps_body,
1218 &wss_body,
1219 &cc_body,
1220 &mono_body,
1221 &stuffing_body,
1222 ];
1223
1224 for (&id, &body) in typed_ids.iter().zip(bodies.iter()) {
1225 let du_id = DataUnitId::from_u8(id);
1226 let payload = DataUnitPayload::parse(du_id, body)
1227 .unwrap_or_else(|e| panic!("id={id:#04X} parse failed: {e}"));
1228 assert!(
1229 !matches!(payload, DataUnitPayload::Opaque(_)),
1230 "id={id:#04X} ({}) fell to Opaque — add a typed dispatch arm",
1231 DataUnitId::from_u8(id).name()
1232 );
1233 }
1234 }
1235}