1use super::descriptor_body;
4use crate::error::{Error, Result};
5use crate::text::LangCode;
6use dvb_common::{Parse, Serialize};
7
8pub const TAG: u8 = 0x0A;
10const HEADER_LEN: usize = 2;
11const ENTRY_LEN: usize = 4;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize))]
16#[non_exhaustive]
17pub enum AudioType {
18 Undefined,
20 CleanEffects,
22 HearingImpaired,
24 VisualImpairedCommentary,
26 UserPrivate(u8),
28 Primary,
30 Native,
32 Emergency,
34 PrimaryCommentary,
36 AlternateCommentary,
38 Reserved(u8),
40}
41
42impl AudioType {
43 #[must_use]
44 pub fn from_u8(v: u8) -> Self {
47 match v {
48 0x00 => Self::Undefined,
49 0x01 => Self::CleanEffects,
50 0x02 => Self::HearingImpaired,
51 0x03 => Self::VisualImpairedCommentary,
52 0x04..=0x7F => Self::UserPrivate(v),
53 0x80 => Self::Primary,
54 0x81 => Self::Native,
55 0x82 => Self::Emergency,
56 0x83 => Self::PrimaryCommentary,
57 0x84 => Self::AlternateCommentary,
58 v => Self::Reserved(v),
59 }
60 }
61
62 #[must_use]
63 pub fn to_u8(self) -> u8 {
65 match self {
66 Self::Undefined => 0x00,
67 Self::CleanEffects => 0x01,
68 Self::HearingImpaired => 0x02,
69 Self::VisualImpairedCommentary => 0x03,
70 Self::UserPrivate(v) => v,
71 Self::Primary => 0x80,
72 Self::Native => 0x81,
73 Self::Emergency => 0x82,
74 Self::PrimaryCommentary => 0x83,
75 Self::AlternateCommentary => 0x84,
76 Self::Reserved(v) => v,
77 }
78 }
79
80 #[must_use]
81 pub fn name(self) -> &'static str {
83 match self {
84 Self::Undefined => "undefined",
85 Self::CleanEffects => "clean effects",
86 Self::HearingImpaired => "hearing impaired",
87 Self::VisualImpairedCommentary => "visual impaired commentary",
88 Self::UserPrivate(_) => "user private",
89 Self::Primary => "primary",
90 Self::Native => "native",
91 Self::Emergency => "emergency",
92 Self::PrimaryCommentary => "primary commentary",
93 Self::AlternateCommentary => "alternate commentary",
94 Self::Reserved(_) => "reserved",
95 }
96 }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
101#[cfg_attr(feature = "serde", derive(serde::Serialize))]
102pub struct LanguageEntry {
103 pub language_code: LangCode,
105 pub audio_type: AudioType,
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
111#[cfg_attr(feature = "serde", derive(serde::Serialize))]
112pub struct Iso639LanguageDescriptor {
113 pub entries: Vec<LanguageEntry>,
115}
116
117impl<'a> Parse<'a> for Iso639LanguageDescriptor {
118 type Error = crate::error::Error;
119 fn parse(bytes: &'a [u8]) -> Result<Self> {
120 let body = descriptor_body(
121 bytes,
122 TAG,
123 "Iso639LanguageDescriptor",
124 "unexpected tag for iso_639_language_descriptor",
125 )?;
126 if body.len() % ENTRY_LEN != 0 {
127 return Err(Error::InvalidDescriptor {
128 tag: TAG,
129 reason: "iso_639_language_descriptor length not a multiple of 4",
130 });
131 }
132 let mut entries = Vec::with_capacity(body.len() / ENTRY_LEN);
133 for chunk in body.chunks_exact(ENTRY_LEN) {
134 entries.push(LanguageEntry {
135 language_code: LangCode([chunk[0], chunk[1], chunk[2]]),
136 audio_type: AudioType::from_u8(chunk[3]),
137 });
138 }
139 Ok(Self { entries })
140 }
141}
142
143impl Serialize for Iso639LanguageDescriptor {
144 type Error = crate::error::Error;
145 fn serialized_len(&self) -> usize {
146 HEADER_LEN + self.entries.len() * ENTRY_LEN
147 }
148
149 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
150 let len = self.serialized_len();
151 if buf.len() < len {
152 return Err(Error::OutputBufferTooSmall {
153 need: len,
154 have: buf.len(),
155 });
156 }
157 buf[0] = TAG;
158 buf[1] = (self.entries.len() * ENTRY_LEN) as u8;
159 let mut pos = HEADER_LEN;
160 for e in &self.entries {
161 buf[pos..pos + 3].copy_from_slice(&e.language_code.0);
162 buf[pos + 3] = e.audio_type.to_u8();
163 pos += ENTRY_LEN;
164 }
165 Ok(len)
166 }
167}
168impl<'a> crate::traits::DescriptorDef<'a> for Iso639LanguageDescriptor {
169 const TAG: u8 = TAG;
170 const NAME: &'static str = "ISO_639_LANGUAGE";
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn parse_single_language_entry() {
179 let bytes = [TAG, 4, b'e', b'n', b'g', 0x00];
180 let d = Iso639LanguageDescriptor::parse(&bytes).unwrap();
181 assert_eq!(d.entries.len(), 1);
182 assert_eq!(d.entries[0].language_code, LangCode(*b"eng"));
183 assert_eq!(d.entries[0].audio_type, AudioType::Undefined);
184 }
185
186 #[test]
187 fn parse_multiple_entries() {
188 let bytes = [TAG, 8, b'e', b'n', b'g', 1, b'f', b'r', b'a', 2];
189 let d = Iso639LanguageDescriptor::parse(&bytes).unwrap();
190 assert_eq!(d.entries.len(), 2);
191 assert_eq!(d.entries[1].language_code, LangCode(*b"fra"));
192 assert_eq!(d.entries[1].audio_type, AudioType::HearingImpaired);
193 }
194
195 #[test]
196 fn parse_rejects_wrong_tag() {
197 let err = Iso639LanguageDescriptor::parse(&[0x0B, 0]).unwrap_err();
198 assert!(matches!(err, Error::InvalidDescriptor { tag: 0x0B, .. }));
199 }
200
201 #[test]
202 fn parse_rejects_short_header() {
203 let err = Iso639LanguageDescriptor::parse(&[TAG]).unwrap_err();
204 assert!(matches!(err, Error::BufferTooShort { .. }));
205 }
206
207 #[test]
208 fn parse_rejects_length_not_multiple_of_4() {
209 let bytes = [TAG, 5, b'e', b'n', b'g', 0, 0];
210 let err = Iso639LanguageDescriptor::parse(&bytes).unwrap_err();
211 assert!(matches!(err, Error::InvalidDescriptor { .. }));
212 }
213
214 #[test]
215 fn serialize_round_trip() {
216 let d = Iso639LanguageDescriptor {
217 entries: vec![
218 LanguageEntry {
219 language_code: LangCode(*b"eng"),
220 audio_type: AudioType::Undefined,
221 },
222 LanguageEntry {
223 language_code: LangCode(*b"fra"),
224 audio_type: AudioType::CleanEffects,
225 },
226 ],
227 };
228 let mut buf = vec![0u8; d.serialized_len()];
229 d.serialize_into(&mut buf).unwrap();
230 let re = Iso639LanguageDescriptor::parse(&buf).unwrap();
231 assert_eq!(d, re);
232 }
233
234 #[test]
235 fn descriptor_length_matches_payload() {
236 let d = Iso639LanguageDescriptor {
237 entries: vec![LanguageEntry {
238 language_code: LangCode(*b"eng"),
239 audio_type: AudioType::Undefined,
240 }],
241 };
242 assert_eq!(d.serialized_len() - 2, 4);
243 }
244
245 #[test]
246 fn audio_type_full_range_round_trip() {
247 for b in 0..=0xFF_u8 {
248 let at = AudioType::from_u8(b);
249 assert_eq!(at.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
250 }
251 }
252
253 #[test]
254 fn audio_type_name_for_known() {
255 assert_eq!(AudioType::Undefined.name(), "undefined");
256 assert_eq!(AudioType::CleanEffects.name(), "clean effects");
257 assert_eq!(AudioType::HearingImpaired.name(), "hearing impaired");
258 assert_eq!(
259 AudioType::VisualImpairedCommentary.name(),
260 "visual impaired commentary"
261 );
262 assert_eq!(AudioType::Reserved(0x55).name(), "reserved");
263 }
264
265 #[test]
266 fn audio_type_round_trip_known_values() {
267 for b in [0x00u8, 0x03, 0x40, 0x80, 0x84, 0x85, 0xFF] {
268 assert_eq!(
269 AudioType::from_u8(b).to_u8(),
270 b,
271 "round-trip failed for byte 0x{b:02X}"
272 );
273 }
274 }
275
276 #[test]
277 fn audio_type_primary_name() {
278 assert_eq!(AudioType::from_u8(0x80).name(), "primary");
279 }
280}