1use crate::core::ltv::LtValue;
6use crate::packet_encoding::Error as PacketError;
7use crate::{CompanyId, decodable_enum};
8
9use crate::generic_audio::ContextType;
10
11decodable_enum! {
12 pub enum MetadataType<u8, PacketError, OutOfRange> {
13 PreferredAudioContexts = 0x01,
14 StreamingAudioContexts = 0x02,
15 ProgramInfo = 0x03,
16 Language = 0x04,
17 CCIDList = 0x05,
18 ParentalRating = 0x06,
19 ProgramInfoURI = 0x07,
20 AudioActiveState = 0x08,
21 BroadcastAudioImmediateRenderingFlag = 0x09,
22 ExtendedMetadata = 0xfe,
23 VendorSpecific = 0xff,
24 }
25}
26
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub enum Metadata {
29 PreferredAudioContexts(Vec<ContextType>),
30 StreamingAudioContexts(Vec<ContextType>),
31 ProgramInfo(String),
32 Language(String),
34 CCIDList(Vec<u8>),
35 ParentalRating(Rating),
36 ProgramInfoURI(String),
37 ExtendedMetadata {
38 type_: u16,
39 data: Vec<u8>,
40 },
41 VendorSpecific {
42 company_id: CompanyId,
43 data: Vec<u8>,
44 },
45 AudioActiveState(bool),
46 BroadcastAudioImmediateRenderingFlag,
48}
49
50impl LtValue for Metadata {
51 type Type = MetadataType;
52
53 const NAME: &'static str = "Metadata";
54
55 fn type_from_octet(x: u8) -> Option<Self::Type> {
56 x.try_into().ok()
57 }
58
59 fn length_range_from_type(ty: Self::Type) -> std::ops::RangeInclusive<u8> {
60 match ty {
61 MetadataType::PreferredAudioContexts | MetadataType::StreamingAudioContexts => 3..=3,
62 MetadataType::ProgramInfo | MetadataType::CCIDList | MetadataType::ProgramInfoURI => {
63 1..=u8::MAX
64 }
65 MetadataType::Language => 4..=4,
66 MetadataType::ParentalRating => 2..=2,
67 MetadataType::ExtendedMetadata | MetadataType::VendorSpecific => 3..=u8::MAX,
68 MetadataType::AudioActiveState => 2..=2,
69 MetadataType::BroadcastAudioImmediateRenderingFlag => 1..=1,
70 }
71 }
72
73 fn into_type(&self) -> Self::Type {
74 match self {
75 Metadata::PreferredAudioContexts(_) => MetadataType::PreferredAudioContexts,
76 Metadata::StreamingAudioContexts(_) => MetadataType::StreamingAudioContexts,
77 Metadata::ProgramInfo(_) => MetadataType::ProgramInfo,
78 Metadata::Language(_) => MetadataType::Language,
79 Metadata::CCIDList(_) => MetadataType::CCIDList,
80 Metadata::ParentalRating(_) => MetadataType::ParentalRating,
81 Metadata::ProgramInfoURI(_) => MetadataType::ProgramInfoURI,
82 Metadata::ExtendedMetadata { .. } => MetadataType::ExtendedMetadata,
83 Metadata::VendorSpecific { .. } => MetadataType::VendorSpecific,
84 Metadata::AudioActiveState(_) => MetadataType::AudioActiveState,
85 Metadata::BroadcastAudioImmediateRenderingFlag => {
86 MetadataType::BroadcastAudioImmediateRenderingFlag
87 }
88 }
89 }
90
91 fn value_encoded_len(&self) -> u8 {
92 match self {
93 Metadata::PreferredAudioContexts(_) => 2,
94 Metadata::StreamingAudioContexts(_) => 2,
95 Metadata::ProgramInfo(value) => value.len() as u8,
96 Metadata::Language(_) => 3,
97 Metadata::CCIDList(ccids) => ccids.len() as u8,
98 Metadata::ParentalRating(_) => 1,
99 Metadata::ProgramInfoURI(uri) => uri.len() as u8,
100 Metadata::ExtendedMetadata { type_: _, data } => 2 + data.len() as u8,
101 Metadata::VendorSpecific { company_id: _, data } => 2 + data.len() as u8,
102 Metadata::AudioActiveState(_) => 1,
103 Metadata::BroadcastAudioImmediateRenderingFlag => 0,
104 }
105 }
106
107 fn decode_value(ty: &Self::Type, buf: &[u8]) -> Result<Self, crate::packet_encoding::Error> {
108 let m: Metadata = match ty {
109 MetadataType::PreferredAudioContexts | MetadataType::StreamingAudioContexts => {
110 let context_type =
111 ContextType::from_bits(u16::from_le_bytes([buf[0], buf[1]])).collect();
112 if ty == &MetadataType::PreferredAudioContexts {
113 Self::PreferredAudioContexts(context_type)
114 } else {
115 Self::StreamingAudioContexts(context_type)
116 }
117 }
118 MetadataType::ProgramInfo | MetadataType::ProgramInfoURI => {
119 let value = String::from_utf8(buf[..].to_vec())
120 .map_err(|e| PacketError::InvalidParameter(format!("{e}")))?;
121 if ty == &MetadataType::ProgramInfo {
122 Self::ProgramInfo(value)
123 } else {
124 Self::ProgramInfoURI(value)
125 }
126 }
127 MetadataType::Language => {
128 let code = String::from_utf8(buf[..].to_vec())
129 .map_err(|e| PacketError::InvalidParameter(format!("{e}")))?;
130 Self::Language(code)
131 }
132 MetadataType::CCIDList => Self::CCIDList(buf[..].to_vec()),
133 MetadataType::ParentalRating => Self::ParentalRating(buf[0].into()),
134 MetadataType::ExtendedMetadata | MetadataType::VendorSpecific => {
135 let type_or_id = u16::from_le_bytes(buf[0..2].try_into().unwrap());
136 let data = if buf.len() >= 2 { buf[2..].to_vec() } else { vec![] };
137 if ty == &MetadataType::ExtendedMetadata {
138 Self::ExtendedMetadata { type_: type_or_id, data }
139 } else {
140 Self::VendorSpecific { company_id: type_or_id.into(), data }
141 }
142 }
143 MetadataType::AudioActiveState => Self::AudioActiveState(buf[0] != 0),
144 MetadataType::BroadcastAudioImmediateRenderingFlag => {
145 Self::BroadcastAudioImmediateRenderingFlag
146 }
147 };
148 Ok(m)
149 }
150
151 fn encode_value(&self, buf: &mut [u8]) -> Result<(), crate::packet_encoding::Error> {
152 match self {
153 Self::PreferredAudioContexts(type_) | Self::StreamingAudioContexts(type_) => {
154 [buf[0], buf[1]] = ContextType::to_bits(type_.iter()).to_le_bytes();
155 }
156 Self::ProgramInfo(value) | Self::ProgramInfoURI(value) => {
157 buf.copy_from_slice(value.as_bytes())
158 }
159 Self::Language(value) if value.len() != 3 => {
160 return Err(PacketError::InvalidParameter(format!("{self}")));
161 }
162 Self::Language(value) => buf.copy_from_slice(&value.as_bytes()[..3]),
163 Self::CCIDList(value) => buf.copy_from_slice(&value.as_slice()),
164 Self::ParentalRating(value) => buf[0] = value.into(),
165 Self::ExtendedMetadata { type_, data } => {
166 buf[0..2].copy_from_slice(&type_.to_le_bytes());
167 buf[2..].copy_from_slice(&data.as_slice());
168 }
169 Self::VendorSpecific { company_id, data } => {
170 [buf[0], buf[1]] = company_id.to_le_bytes();
171 buf[2..].copy_from_slice(&data.as_slice());
172 }
173 Self::AudioActiveState(value) => buf[0] = *value as u8,
174 Self::BroadcastAudioImmediateRenderingFlag => {}
175 }
176 Ok(())
177 }
178}
179
180impl std::fmt::Display for Metadata {
181 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
182 match self {
183 Metadata::PreferredAudioContexts(v) => write!(f, "Preferred Audio Contexts: {v:?}"),
185 Metadata::StreamingAudioContexts(v) => write!(f, "Streaming Audio Contexts: {v:?}"),
186 Metadata::ProgramInfo(v) => write!(f, "Progaam Info: {v:?}"),
187 Metadata::Language(v) => write!(f, "Language: {v:?}"),
188 Metadata::CCIDList(v) => write!(f, "CCID List: {v:?}"),
189 Metadata::ParentalRating(v) => write!(f, "Parental Rating: {v:?}"),
190 Metadata::ProgramInfoURI(v) => write!(f, "Program Info URI: {v:?}"),
191 Metadata::ExtendedMetadata { type_, data } => {
192 write!(f, "Extended Metadata: type(0x{type_:02x}) data({data:?})")
193 }
194 Metadata::VendorSpecific { company_id, data } => {
195 write!(f, "Vendor Specific: {company_id} data({data:?})")
196 }
197 Metadata::AudioActiveState(v) => write!(f, "Audio Active State: {v}"),
198 Metadata::BroadcastAudioImmediateRenderingFlag => {
199 write!(f, "Broadcast Audio Immediate Rendering Flag")
200 }
201 }
202 }
203}
204
205#[derive(Clone, Copy, Debug, PartialEq, Eq)]
209pub enum Rating {
210 NoRating,
211 AllAge,
212 Age(u16),
215}
216
217impl Rating {
218 const MIN_RECOMMENDED_AGE: u16 = 5;
220
221 const AGE_OFFSET: u16 = 3;
225
226 pub const fn no_rating() -> Self {
227 Self::NoRating
228 }
229
230 pub const fn all_age() -> Self {
231 Self::AllAge
232 }
233
234 pub fn min_recommended(age: u16) -> Result<Self, PacketError> {
235 if age < Self::MIN_RECOMMENDED_AGE {
236 return Err(PacketError::InvalidParameter(format!(
237 "minimum recommended age must be at least 5. Got {age}"
238 )));
239 }
240 if age > u8::MAX as u16 + Self::AGE_OFFSET {
242 return Err(PacketError::OutOfRange);
243 }
244 Ok(Rating::Age(age))
245 }
246}
247
248impl From<&Rating> for u8 {
249 fn from(value: &Rating) -> Self {
250 match value {
251 Rating::NoRating => 0x00,
252 Rating::AllAge => 0x01,
253 Rating::Age(a) => (a - Rating::AGE_OFFSET) as u8,
254 }
255 }
256}
257
258impl From<u8> for Rating {
259 fn from(value: u8) -> Self {
260 match value {
261 0x00 => Rating::NoRating,
262 0x01 => Rating::AllAge,
263 value => {
264 let age = value as u16 + Self::AGE_OFFSET;
265 Rating::min_recommended(age).unwrap()
266 }
267 }
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 use crate::packet_encoding::{Decodable, Encodable};
276
277 #[test]
278 fn metadataum_preferred_audio_contexts() {
279 let test = Metadata::PreferredAudioContexts(vec![ContextType::Conversational]);
281 assert_eq!(test.encoded_len(), 4);
282 let mut buf = vec![0; test.encoded_len()];
283 let _ = test.encode(&mut buf[..]).expect("should not fail");
284
285 let bytes = vec![0x03, 0x01, 0x02, 0x00];
286 assert_eq!(buf, bytes);
287
288 let (decoded, len) = Metadata::decode(&buf);
290 assert_eq!(decoded, Ok(test));
291 assert_eq!(len, 4);
292 }
293
294 #[test]
295 fn metadatum_streaming_audio_contexts() {
296 let test =
298 Metadata::StreamingAudioContexts(vec![ContextType::Ringtone, ContextType::Alerts]);
299 assert_eq!(test.encoded_len(), 4);
300 let mut buf = vec![0; test.encoded_len()];
301 let _ = test.encode(&mut buf[..]).expect("should not fail");
302
303 let bytes = vec![0x03, 0x02, 0x00, 0x06];
304 assert_eq!(buf, bytes);
305
306 let (decoded, len) = Metadata::decode(&buf);
308 assert_eq!(decoded, Ok(test));
309 assert_eq!(len, 4);
310 }
311
312 #[test]
313 fn metadatum_program_info() {
314 let test = Metadata::ProgramInfo("a".to_string());
316 assert_eq!(test.encoded_len(), 3);
317 let mut buf = vec![0; test.encoded_len()];
318 let _ = test.encode(&mut buf[..]).expect("should not fail");
319
320 let bytes = vec![0x02, 0x03, 0x61];
321 assert_eq!(buf, bytes);
322
323 let (decoded, len) = Metadata::decode(&buf);
325 assert_eq!(decoded, Ok(test));
326 assert_eq!(len, 3);
327 }
328
329 #[test]
330 fn metadatum_language_code() {
331 let test = Metadata::Language("eng".to_string());
333 assert_eq!(test.encoded_len(), 5);
334 let mut buf = vec![0; test.encoded_len()];
335 let _ = test.encode(&mut buf[..]).expect("should not fail");
336
337 let bytes = vec![0x04, 0x04, 0x65, 0x6E, 0x67];
338 assert_eq!(buf, bytes);
339
340 let (decoded, len) = Metadata::decode(&buf);
342 assert_eq!(decoded, Ok(test));
343 assert_eq!(len, 5);
344 }
345
346 #[test]
347 fn metadatum_ccid_list() {
348 let test = Metadata::CCIDList(vec![0x01, 0x02]);
350 assert_eq!(test.encoded_len(), 4);
351 let mut buf = vec![0; test.encoded_len()];
352 let _ = test.encode(&mut buf[..]).expect("should not fail");
353
354 let bytes = vec![0x03, 0x05, 0x01, 0x02];
355 assert_eq!(buf, bytes);
356
357 let (decoded, len) = Metadata::decode(&buf);
359 assert_eq!(decoded, Ok(test));
360 assert_eq!(len, 4);
361 }
362
363 #[test]
364 fn metadatum_parental_rating() {
365 let test = Metadata::ParentalRating(Rating::Age(8));
367 assert_eq!(test.encoded_len(), 0x03);
368 let mut buf = vec![0; test.encoded_len()];
369 let _ = test.encode(&mut buf[..]).expect("should not fail");
370
371 let bytes = vec![0x02, 0x06, 0x05];
372 assert_eq!(buf, bytes);
373
374 let (decoded, len) = Metadata::decode(&buf);
376 assert_eq!(decoded, Ok(test));
377 assert_eq!(len, 3);
378 }
379
380 #[test]
381 fn metadatum_vendor_specific() {
382 let test = Metadata::VendorSpecific { company_id: 0x00E0.into(), data: vec![0x01, 0x02] };
384 assert_eq!(test.encoded_len(), 0x06);
385 let mut buf = vec![0; test.encoded_len()];
386 let _ = test.encode(&mut buf[..]).expect("should not fail");
387
388 let bytes = vec![0x05, 0xFF, 0xE0, 0x00, 0x01, 0x02];
389 assert_eq!(buf, bytes);
390
391 let (decoded, len) = Metadata::decode(&buf);
393 assert_eq!(decoded, Ok(test));
394 assert_eq!(len, 6);
395 }
396
397 #[test]
398 fn metadatum_audio_active_state() {
399 let test = Metadata::AudioActiveState(true);
401 assert_eq!(test.encoded_len(), 0x03);
402 let mut buf = vec![0; test.encoded_len()];
403 let _ = test.encode(&mut buf[..]).expect("should not fail");
404
405 let bytes = vec![0x02, 0x08, 0x01];
406 assert_eq!(buf, bytes);
407
408 let (decoded, len) = Metadata::decode(&buf);
410 assert_eq!(decoded, Ok(test));
411 assert_eq!(len, 3);
412 }
413
414 #[test]
415 fn metadatum_broadcast_audio_immediate_rendering() {
416 let test = Metadata::BroadcastAudioImmediateRenderingFlag;
418 assert_eq!(test.encoded_len(), 0x02);
419 let mut buf = vec![0; test.encoded_len()];
420 let _ = test.encode(&mut buf[..]).expect("should not fail");
421
422 let bytes = vec![0x01, 0x09];
423 assert_eq!(buf, bytes);
424
425 let (decoded, len) = Metadata::decode(&buf);
427 assert_eq!(decoded, Ok(test));
428 assert_eq!(len, 2);
429 }
430
431 #[test]
432 fn invalid_metadataum() {
433 let test = Metadata::Language("illegal".to_string());
435 let mut buf = vec![0; test.encoded_len()];
436 let _ = test.encode(&mut buf[..]).expect_err("should fail");
437
438 let buf = vec![0x03];
440 let _ = Metadata::decode(&buf).0.expect_err("should fail");
441
442 let buf = vec![0x02, 0x01, 0x02];
444 let _ = Metadata::decode(&buf).0.expect_err("should fail");
445
446 let buf = vec![0x03, 0x03, 0x61];
448 let _ = Metadata::decode(&buf).0.expect_err("should fail");
449 }
450
451 #[test]
452 fn rating() {
453 let no_rating = Rating::no_rating();
454 let all_age = Rating::all_age();
455 let for_age = Rating::min_recommended(5).expect("should succeed");
456
457 assert_eq!(<&Rating as Into<u8>>::into(&no_rating), 0x00);
458 assert_eq!(<&Rating as Into<u8>>::into(&all_age), 0x01);
459 assert_eq!(<&Rating as Into<u8>>::into(&for_age), 0x02);
460 }
461
462 #[test]
463 fn decode_rating() {
464 let res = Rating::from(0);
465 assert_eq!(res, Rating::NoRating);
466
467 let res = Rating::from(1);
468 assert_eq!(res, Rating::AllAge);
469
470 let res = Rating::from(2);
471 assert_eq!(res, Rating::Age(5));
472
473 let res = Rating::from(255);
474 assert_eq!(res, Rating::Age(258));
475
476 let res = Rating::min_recommended(258).unwrap();
477 assert_eq!(255u8, (&res).into());
478 }
479
480 #[test]
481 fn invalid_rating() {
482 let _ = Rating::min_recommended(4).expect_err("should have failed");
483 let _ = Rating::min_recommended(350).expect_err("can't recommend for elves");
484 }
485}