1pub const ID_EBU_TELETEXT_NON_SUBTITLE: u8 = 0x02;
14pub const ID_EBU_TELETEXT_SUBTITLE: u8 = 0x03;
16pub const ID_INVERTED_TELETEXT: u8 = 0xC0;
18pub const ID_VPS: u8 = 0xC3;
20pub const ID_WSS: u8 = 0xC4;
22pub const ID_CLOSED_CAPTIONING: u8 = 0xC5;
24pub const ID_MONOCHROME_422_SAMPLES: u8 = 0xC6;
26pub const ID_STUFFING: u8 = 0xFF;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize))]
37#[non_exhaustive]
38pub enum DataUnitId {
39 EbuTeletextNonSubtitle,
41 EbuTeletextSubtitle,
43 InvertedTeletext,
45 Vps,
47 Wss,
49 ClosedCaptioning,
51 Monochrome422Samples,
53 Stuffing,
55 Reserved(u8),
58 UserDefined(u8),
61}
62
63impl DataUnitId {
64 pub fn from_u8(raw: u8) -> Self {
66 match raw {
67 ID_EBU_TELETEXT_NON_SUBTITLE => DataUnitId::EbuTeletextNonSubtitle,
68 ID_EBU_TELETEXT_SUBTITLE => DataUnitId::EbuTeletextSubtitle,
69 ID_INVERTED_TELETEXT => DataUnitId::InvertedTeletext,
70 ID_VPS => DataUnitId::Vps,
71 ID_WSS => DataUnitId::Wss,
72 ID_CLOSED_CAPTIONING => DataUnitId::ClosedCaptioning,
73 ID_MONOCHROME_422_SAMPLES => DataUnitId::Monochrome422Samples,
74 ID_STUFFING => DataUnitId::Stuffing,
75 0x80..=0xBF => DataUnitId::UserDefined(raw),
78 other => DataUnitId::Reserved(other),
79 }
80 }
81
82 pub fn to_u8(self) -> u8 {
84 match self {
85 DataUnitId::EbuTeletextNonSubtitle => ID_EBU_TELETEXT_NON_SUBTITLE,
86 DataUnitId::EbuTeletextSubtitle => ID_EBU_TELETEXT_SUBTITLE,
87 DataUnitId::InvertedTeletext => ID_INVERTED_TELETEXT,
88 DataUnitId::Vps => ID_VPS,
89 DataUnitId::Wss => ID_WSS,
90 DataUnitId::ClosedCaptioning => ID_CLOSED_CAPTIONING,
91 DataUnitId::Monochrome422Samples => ID_MONOCHROME_422_SAMPLES,
92 DataUnitId::Stuffing => ID_STUFFING,
93 DataUnitId::Reserved(v) => v,
94 DataUnitId::UserDefined(v) => v,
95 }
96 }
97
98 pub fn name(&self) -> &'static str {
100 match self {
101 DataUnitId::EbuTeletextNonSubtitle => "EBU Teletext non-subtitle data",
102 DataUnitId::EbuTeletextSubtitle => "EBU Teletext subtitle data",
103 DataUnitId::InvertedTeletext => "Inverted Teletext",
104 DataUnitId::Vps => "VPS",
105 DataUnitId::Wss => "WSS",
106 DataUnitId::ClosedCaptioning => "Closed Captioning",
107 DataUnitId::Monochrome422Samples => "monochrome 4:2:2 samples",
108 DataUnitId::Stuffing => "stuffing",
109 DataUnitId::Reserved(_) => "reserved",
110 DataUnitId::UserDefined(_) => "user defined",
111 }
112 }
113}
114
115broadcast_common::impl_spec_display!(DataUnitId, Reserved, UserDefined);
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use alloc::string::ToString;
121
122 #[test]
123 fn all_u8_round_trip() {
124 for raw in 0u16..=0xFF {
125 let raw = raw as u8;
126 assert_eq!(DataUnitId::from_u8(raw).to_u8(), raw, "raw={raw:#04X}");
127 }
128 }
129
130 #[test]
131 fn c1_is_reserved_not_teletext() {
132 assert_eq!(DataUnitId::from_u8(0xC1), DataUnitId::Reserved(0xC1));
134 assert_eq!(DataUnitId::from_u8(0xC2), DataUnitId::Reserved(0xC2));
135 }
136
137 #[test]
138 fn ranges() {
139 assert_eq!(DataUnitId::from_u8(0x00), DataUnitId::Reserved(0x00));
140 assert_eq!(DataUnitId::from_u8(0x7F), DataUnitId::Reserved(0x7F));
141 assert_eq!(DataUnitId::from_u8(0x80), DataUnitId::UserDefined(0x80));
142 assert_eq!(DataUnitId::from_u8(0xBF), DataUnitId::UserDefined(0xBF));
143 assert_eq!(DataUnitId::from_u8(0xC7), DataUnitId::Reserved(0xC7));
144 assert_eq!(DataUnitId::from_u8(0xFE), DataUnitId::Reserved(0xFE));
145 }
146
147 #[test]
148 fn display_is_lossless_for_byte_bearing() {
149 assert_eq!(DataUnitId::Reserved(0xC1).to_string(), "reserved(0xC1)");
150 assert_eq!(
151 DataUnitId::UserDefined(0x90).to_string(),
152 "user defined(0x90)"
153 );
154 assert_eq!(DataUnitId::Vps.to_string(), "VPS");
155 }
156}