bt_common/generic_audio/
metadata_ltv.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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    /// 3 byte, lower case language code as defined in ISO 639-­3
33    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    // Flag for Broadcast Audio Immediate Rendering.
47    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            // TODO(b/308483171): Consider using the Display for the inner fields instead of Debug.
184            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/// Represents recommended minimum age of the viewer.
206/// The numbering scheme aligns with Annex F of EN 300 707 v1.2.1
207/// published by ETSI.
208#[derive(Clone, Copy, Debug, PartialEq, Eq)]
209pub enum Rating {
210    NoRating,
211    AllAge,
212    // Recommended for listeners of age x years, where x is the
213    // value of u8 that's greater than or equal to 5.
214    Age(u16),
215}
216
217impl Rating {
218    // Minimum age that can be recommended.
219    const MIN_RECOMMENDED_AGE: u16 = 5;
220
221    // When recommending for listeners of age Y years,
222    // Subtract 3 from the recommended age to get the encoded value.
223    // E.g., to indicate recommended age of 8 years or older, encode 5.
224    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        // We can represent up to 255 + 3 (age 258) using this encoding.
241        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        // Encoding.
280        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        // Decoding.
289        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        // Encoding.
297        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        // Decoding.
307        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        // Encoding.
315        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        // Decoding.
324        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        // Encoding.
332        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        // Decoding.
341        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        // Encoding.
349        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        // Decoding.
358        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        // Encding.
366        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        // Decoding.
375        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        // Encoding.
383        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        // Decoding.
392        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        // Encoding.
400        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        // Decoding.
409        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        // Encoding.
417        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        // Decoding.
426        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        // Language code must be 3-lettered.
434        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        // Not enough length for Length and Type for decoding.
439        let buf = vec![0x03];
440        let _ = Metadata::decode(&buf).0.expect_err("should fail");
441
442        // Not enough length for Value field for decoding.
443        let buf = vec![0x02, 0x01, 0x02];
444        let _ = Metadata::decode(&buf).0.expect_err("should fail");
445
446        // Buffer length does not match Length value for decoding.
447        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}