| // Copyright 2023 Google LLC | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | use bitflags::bitflags; | 
 |  | 
 | use crate::packet_encoding::{Decodable, Encodable, Error as PacketError}; | 
 |  | 
 | pub type Metadata = Vec<Metadatum>; | 
 |  | 
 | #[derive(Debug, PartialEq)] | 
 | #[repr(u8)] | 
 | pub enum Type { | 
 |     PreferredAudioContexts = 0x01, | 
 |     StreamingAudioContexts = 0x02, | 
 |     ProgramInfo = 0x03, | 
 |     Language = 0x04, | 
 |     CCIDList = 0x05, | 
 |     ParentalRating = 0x06, | 
 |     ProgramInfoURI = 0x07, | 
 |     ExtendedMetadata = 0xfe, | 
 |     VendorSpecific = 0xff, | 
 |     AudioActiveState = 0x08, | 
 |     BroadcastAudioImmediateRenderingFlag = 0x09, | 
 | } | 
 |  | 
 | impl Type { | 
 |     fn length_check(&self, provided: usize) -> Result<(), PacketError> { | 
 |         if self.total_size_range().contains(&provided) { | 
 |             Ok(()) | 
 |         } else { | 
 |             Err(PacketError::UnexpectedDataLength) | 
 |         } | 
 |     } | 
 |  | 
 |     // Total size range including all Length, Type, and Value parameters. | 
 |     fn total_size_range(&self) -> std::ops::Range<usize> { | 
 |         match self { | 
 |             Type::PreferredAudioContexts | Type::StreamingAudioContexts => 4..5, | 
 |             Type::ProgramInfo | Type::CCIDList | Type::ProgramInfoURI => 2..usize::MAX, | 
 |             Type::Language => 5..6, | 
 |             Type::ParentalRating => 3..4, | 
 |             Type::ExtendedMetadata | Type::VendorSpecific => 4..256, | 
 |             Type::AudioActiveState => 3..4, | 
 |             Type::BroadcastAudioImmediateRenderingFlag => 2..3, | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | impl TryFrom<u8> for Type { | 
 |     type Error = PacketError; | 
 |  | 
 |     fn try_from(value: u8) -> Result<Self, Self::Error> { | 
 |         let t = match value { | 
 |             0x01 => Type::PreferredAudioContexts, | 
 |             0x02 => Type::StreamingAudioContexts, | 
 |             0x03 => Type::ProgramInfo, | 
 |             0x04 => Type::Language, | 
 |             0x05 => Type::CCIDList, | 
 |             0x06 => Type::ParentalRating, | 
 |             0x07 => Type::ProgramInfoURI, | 
 |             0xFE => Type::ExtendedMetadata, | 
 |             0xFF => Type::VendorSpecific, | 
 |             0x08 => Type::AudioActiveState, | 
 |             0x09 => Type::BroadcastAudioImmediateRenderingFlag, | 
 |             _ => return Err(PacketError::OutOfRange), | 
 |         }; | 
 |         Ok(t) | 
 |     } | 
 | } | 
 |  | 
 | #[derive(Debug, PartialEq)] | 
 | pub enum Metadatum { | 
 |     PreferredAudioContexts(ContextType), | 
 |     StreamingAudioContexts(ContextType), | 
 |     ProgramInfo(String), | 
 |     /// 3 byte, lower case language code as defined in ISO 639-3 | 
 |     LanguageCode(String), | 
 |     CCIDList(Vec<u8>), | 
 |     ParentalRating(Rating), | 
 |     ProgramInfoURI(String), | 
 |     ExtendedMetadata { | 
 |         type_: u16, | 
 |         metadata: Vec<u8>, | 
 |     }, | 
 |     VendorSpecific { | 
 |         company_id: u16, | 
 |         metadata: Vec<u8>, | 
 |     }, | 
 |     AudioActiveState(bool), | 
 |     // Flag for Broadcast Audio Immediate Rendering. | 
 |     BroadcastAudioImmediateRenderingFlag, | 
 | } | 
 |  | 
 | impl Metadatum { | 
 |     const LENGTH_BYTE_SIZE: usize = 1; | 
 |     const TYPE_BYTE_SIZE: usize = 1; | 
 |     const MIN_PACKET_SIZE: usize = Self::LENGTH_BYTE_SIZE + Self::TYPE_BYTE_SIZE; | 
 |  | 
 |     // Size of this metadatum's Value parameter. Does not include the Length or Type | 
 |     // size. | 
 |     pub fn value_encoded_len(&self) -> usize { | 
 |         match self { | 
 |             Metadatum::PreferredAudioContexts(_) => 2, | 
 |             Metadatum::StreamingAudioContexts(_) => 2, | 
 |             Metadatum::ProgramInfo(value) => value.len(), | 
 |             Metadatum::LanguageCode(_) => 3, | 
 |             Metadatum::CCIDList(ccids) => ccids.len(), | 
 |             Metadatum::ParentalRating(_) => 1, | 
 |             Metadatum::ProgramInfoURI(uri) => uri.len(), | 
 |             Metadatum::ExtendedMetadata { type_: _, metadata } => 2 + metadata.len(), | 
 |             Metadatum::VendorSpecific { company_id: _, metadata } => 2 + metadata.len(), | 
 |             Metadatum::AudioActiveState(_) => 1, | 
 |             Metadatum::BroadcastAudioImmediateRenderingFlag => 0, | 
 |         } | 
 |     } | 
 |  | 
 |     pub fn type_(&self) -> Type { | 
 |         match self { | 
 |             Metadatum::PreferredAudioContexts(_) => Type::PreferredAudioContexts, | 
 |             Metadatum::StreamingAudioContexts(_) => Type::StreamingAudioContexts, | 
 |             Metadatum::ProgramInfo(_) => Type::ProgramInfo, | 
 |             Metadatum::LanguageCode(_) => Type::Language, | 
 |             Metadatum::CCIDList(_) => Type::CCIDList, | 
 |             Metadatum::ParentalRating(_) => Type::ParentalRating, | 
 |             Metadatum::ProgramInfoURI(_) => Type::ProgramInfoURI, | 
 |             Metadatum::ExtendedMetadata { .. } => Type::ExtendedMetadata, | 
 |             Metadatum::VendorSpecific { .. } => Type::VendorSpecific, | 
 |             Metadatum::AudioActiveState(_) => Type::AudioActiveState, | 
 |             Metadatum::BroadcastAudioImmediateRenderingFlag => { | 
 |                 Type::BroadcastAudioImmediateRenderingFlag | 
 |             } | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | impl Decodable for Metadatum { | 
 |     type Error = PacketError; | 
 |  | 
 |     fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> { | 
 |         if buf.len() < Self::MIN_PACKET_SIZE { | 
 |             return Err(PacketError::UnexpectedDataLength); | 
 |         } | 
 |         // Total length of the buffer including all Length, Type, and Value parameters. | 
 |         let total_len = 1 + buf[0] as usize; | 
 |         if buf.len() < total_len { | 
 |             return Err(PacketError::UnexpectedDataLength); | 
 |         } | 
 |         let type_ = Type::try_from(buf[1])?; | 
 |         let _ = type_.length_check(total_len)?; | 
 |         let m: Metadatum = match type_ { | 
 |             Type::PreferredAudioContexts | Type::StreamingAudioContexts => { | 
 |                 let context_type = | 
 |                     ContextType::try_from(u16::from_le_bytes(buf[2..4].try_into().unwrap()))?; | 
 |                 if type_ == Type::PreferredAudioContexts { | 
 |                     Self::PreferredAudioContexts(context_type) | 
 |                 } else { | 
 |                     Self::StreamingAudioContexts(context_type) | 
 |                 } | 
 |             } | 
 |             Type::ProgramInfo | Type::ProgramInfoURI => { | 
 |                 let value = String::from_utf8(buf[2..total_len].to_vec()) | 
 |                     .map_err(|e| PacketError::InvalidParameter(format!("{e}")))?; | 
 |                 if type_ == Type::ProgramInfo { | 
 |                     Self::ProgramInfo(value) | 
 |                 } else { | 
 |                     Self::ProgramInfoURI(value) | 
 |                 } | 
 |             } | 
 |             Type::Language => { | 
 |                 let code = String::from_utf8(buf[2..total_len].to_vec()) | 
 |                     .map_err(|e| PacketError::InvalidParameter(format!("{e}")))?; | 
 |                 Self::LanguageCode(code) | 
 |             } | 
 |             Type::CCIDList => Self::CCIDList(buf[2..total_len].to_vec()), | 
 |             Type::ParentalRating => Self::ParentalRating(Rating::decode(&buf[2..3])?.0), | 
 |             Type::ExtendedMetadata | Type::VendorSpecific => { | 
 |                 let type_or_id = u16::from_le_bytes(buf[2..4].try_into().unwrap()); | 
 |                 let data = if buf.len() >= 5 { buf[4..total_len].to_vec() } else { vec![] }; | 
 |                 if type_ == Type::ExtendedMetadata { | 
 |                     Self::ExtendedMetadata { type_: type_or_id, metadata: data } | 
 |                 } else { | 
 |                     Self::VendorSpecific { company_id: type_or_id, metadata: data } | 
 |                 } | 
 |             } | 
 |             Type::AudioActiveState => { | 
 |                 if buf[2] > 1 { | 
 |                     return Err(PacketError::UnexpectedDataLength); | 
 |                 } | 
 |                 Self::AudioActiveState(buf[2] != 0) | 
 |             } | 
 |             Type::BroadcastAudioImmediateRenderingFlag => { | 
 |                 Self::BroadcastAudioImmediateRenderingFlag | 
 |             } | 
 |         }; | 
 |         return Ok((m, total_len)); | 
 |     } | 
 | } | 
 |  | 
 | impl Encodable for Metadatum { | 
 |     type Error = PacketError; | 
 |  | 
 |     /// Encodes the object into a provided buffer in a Metadata LTV structure. | 
 |     /// Bytes are in LSO. | 
 |     // See Assigned Numbers section 6.12.6 for details about Metadata LTV | 
 |     // structures. | 
 |     fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> { | 
 |         if buf.len() < self.encoded_len() { | 
 |             return Err(PacketError::BufferTooSmall); | 
 |         } | 
 |  | 
 |         // Length parameter value excludes the length of Length parameter itself (which | 
 |         // is 1 byte). | 
 |         buf[0] = (self.encoded_len() - 1) | 
 |             .try_into() | 
 |             .map_err(|_| PacketError::InvalidParameter("Metadata too big".to_string()))?; | 
 |         buf[1] = self.type_() as u8; | 
 |  | 
 |         match self { | 
 |             Self::PreferredAudioContexts(type_) | Self::StreamingAudioContexts(type_) => { | 
 |                 buf[2..4].copy_from_slice(&type_.bits().to_le_bytes()) | 
 |             } | 
 |             Self::ProgramInfo(value) | Self::ProgramInfoURI(value) => { | 
 |                 buf[2..2 + value.len()].copy_from_slice(value.as_bytes()) | 
 |             } | 
 |             Self::LanguageCode(value) if value.len() != 3 => { | 
 |                 return Err(PacketError::InvalidParameter(format!("{self}"))); | 
 |             } | 
 |             Self::LanguageCode(value) => buf[2..5].copy_from_slice(&value.as_bytes()[..3]), | 
 |             Self::CCIDList(value) => buf[2..2 + value.len()].copy_from_slice(&value.as_slice()), | 
 |             Self::ParentalRating(value) => buf[2] = value.into(), | 
 |             Self::ExtendedMetadata { type_, metadata } => { | 
 |                 buf[2..4].copy_from_slice(&type_.to_le_bytes()); | 
 |                 buf[4..4 + metadata.len()].copy_from_slice(&metadata.as_slice()); | 
 |             } | 
 |             Self::VendorSpecific { company_id, metadata } => { | 
 |                 buf[2..4].copy_from_slice(&company_id.to_le_bytes()); | 
 |                 buf[4..4 + metadata.len()].copy_from_slice(&metadata.as_slice()); | 
 |             } | 
 |             Self::AudioActiveState(value) => buf[2] = *value as u8, | 
 |             Self::BroadcastAudioImmediateRenderingFlag => {} | 
 |         } | 
 |         Ok(()) | 
 |     } | 
 |  | 
 |     fn encoded_len(&self) -> core::primitive::usize { | 
 |         Self::MIN_PACKET_SIZE + self.value_encoded_len() | 
 |     } | 
 | } | 
 |  | 
 | impl std::fmt::Display for Metadatum { | 
 |     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | 
 |         match self { | 
 |             // TODO(b/308483171): Consider using the Display for the inner fields instead of Debug. | 
 |             Metadatum::PreferredAudioContexts(v) => write!(f, "Preferred Audio Contexts: {v:?}"), | 
 |             Metadatum::StreamingAudioContexts(v) => write!(f, "Streaming Audio Contexts: {v:?}"), | 
 |             Metadatum::ProgramInfo(v) => write!(f, "Progaam Info: {v:?}"), | 
 |             Metadatum::LanguageCode(v) => write!(f, "Language: {v:?}"), | 
 |             Metadatum::CCIDList(v) => write!(f, "CCID List: {v:?}"), | 
 |             Metadatum::ParentalRating(v) => write!(f, "Parental Rating: {v:?}"), | 
 |             Metadatum::ProgramInfoURI(v) => write!(f, "Program Info URI: {v:?}"), | 
 |             Metadatum::ExtendedMetadata { type_, metadata } => { | 
 |                 write!(f, "Extended Metadata: type(0x{type_:02x}) metadata({metadata:?})") | 
 |             } | 
 |             Metadatum::VendorSpecific { company_id, metadata } => { | 
 |                 write!(f, "Vendor Specific: company(0x{company_id:02x}) metadata({metadata:?})") | 
 |             } | 
 |             Metadatum::AudioActiveState(v) => write!(f, "Audio Active State: {v}"), | 
 |             Metadatum::BroadcastAudioImmediateRenderingFlag => { | 
 |                 write!(f, "Broadcast Audio Immediate Rendering Flag") | 
 |             } | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | bitflags! { | 
 |     #[derive(Clone, Copy, Debug, PartialEq)] | 
 |     pub struct ContextType: u16 { | 
 |         // 0x0000 value is prohibited. | 
 |         const UNSPECIFIED      = 0x0001; | 
 |         const CONVERSATIONAL   = 0x0002; | 
 |         const MEDIA            = 0x0004; | 
 |         const GAME             = 0x0008; | 
 |         const INSTRUCTIONAL    = 0x0010; | 
 |         const VOICE_ASSISTANTS = 0x0020; | 
 |         const LIVE             = 0x0040; | 
 |         const SOUND_EFFECTS    = 0x0080; | 
 |         const NOTIFICATIONS    = 0x0100; | 
 |         const RINGTONE         = 0x0200; | 
 |         const ALERT            = 0x0400; | 
 |         const EMERGENCY_ALARM  = 0x0800; | 
 |     } | 
 | } | 
 |  | 
 | impl TryFrom<u16> for ContextType { | 
 |     type Error = PacketError; | 
 |  | 
 |     fn try_from(value: u16) -> Result<Self, Self::Error> { | 
 |         if u16::count_ones(value) == 0 { | 
 |             return Err(PacketError::InvalidParameter(format!("ContextType 0x0000 is prohibited"))); | 
 |         } | 
 |         Ok(ContextType::from_bits_truncate(value)) | 
 |     } | 
 | } | 
 |  | 
 | /// Represents recommended minimum age of the viewer. | 
 | /// The numbering scheme aligns with Annex F of EN 300 707 v1.2.1 | 
 | /// published by ETSI. | 
 | #[derive(Clone, Copy, Debug, PartialEq)] | 
 | pub enum Rating { | 
 |     NoRating, | 
 |     AllAge, | 
 |     // Recommended for listeners of age x years, where x is the | 
 |     // value of u8 that's greater than or equal to 5. | 
 |     Age(u8), | 
 | } | 
 |  | 
 | impl Rating { | 
 |     // Minimum age that can be recommended. | 
 |     const MIN_RECOMMENDED_AGE: u8 = 5; | 
 |  | 
 |     // When recommending for listeners of age Y years, | 
 |     // Subtract 3 from the recommended age to get the encoded value. | 
 |     // E.g., to indicate recommended age of 8 years or older, encode 5. | 
 |     const AGE_OFFSET: u8 = 3; | 
 |  | 
 |     const fn no_rating() -> Self { | 
 |         Self::NoRating | 
 |     } | 
 |  | 
 |     const fn all_age() -> Self { | 
 |         Self::AllAge | 
 |     } | 
 |  | 
 |     pub fn min_recommended(age: u8) -> Result<Self, PacketError> { | 
 |         if age < Self::MIN_RECOMMENDED_AGE { | 
 |             return Err(PacketError::InvalidParameter(format!( | 
 |                 "minimum recommended age must be at least 5. Got {age}" | 
 |             ))); | 
 |         } | 
 |         Ok(Rating::Age(age)) | 
 |     } | 
 | } | 
 |  | 
 | impl From<&Rating> for u8 { | 
 |     fn from(value: &Rating) -> Self { | 
 |         match value { | 
 |             Rating::NoRating => 0x00, | 
 |             Rating::AllAge => 0x01, | 
 |             Rating::Age(a) => a - Rating::AGE_OFFSET, | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | impl Decodable for Rating { | 
 |     type Error = PacketError; | 
 |  | 
 |     fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> { | 
 |         if buf.len() == 0 { | 
 |             return Err(PacketError::UnexpectedDataLength); | 
 |         } | 
 |         let rating = match buf[0] { | 
 |             0x00 => Rating::NoRating, | 
 |             0x01 => Rating::AllAge, | 
 |             value => { | 
 |                 let age = value.checked_add(Self::AGE_OFFSET).ok_or(PacketError::OutOfRange)?; | 
 |                 Rating::min_recommended(age).unwrap() | 
 |             } | 
 |         }; | 
 |         Ok((rating, 1)) | 
 |     } | 
 | } | 
 |  | 
 | #[cfg(test)] | 
 | mod tests { | 
 |     use super::*; | 
 |  | 
 |     #[test] | 
 |     fn metadataum_preferred_audio_contexts() { | 
 |         // Encoding. | 
 |         let test = Metadatum::PreferredAudioContexts(ContextType::CONVERSATIONAL); | 
 |         assert_eq!(test.encoded_len(), 4); | 
 |         let mut buf = vec![0; test.encoded_len()]; | 
 |         let _ = test.encode(&mut buf[..]).expect("should not fail"); | 
 |  | 
 |         let bytes = vec![0x03, 0x01, 0x02, 0x00]; | 
 |         assert_eq!(buf, bytes); | 
 |  | 
 |         // Decoding. | 
 |         let decoded = Metadatum::decode(&buf).expect("should succeed"); | 
 |         assert_eq!(decoded.0, test); | 
 |         assert_eq!(decoded.1, 4); | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn metadatum_streaming_audio_contexts() { | 
 |         // Encoding. | 
 |         let test = Metadatum::StreamingAudioContexts(ContextType::RINGTONE | ContextType::ALERT); | 
 |         assert_eq!(test.encoded_len(), 4); | 
 |         let mut buf = vec![0; test.encoded_len()]; | 
 |         let _ = test.encode(&mut buf[..]).expect("should not fail"); | 
 |  | 
 |         let bytes = vec![0x03, 0x02, 0x00, 0x06]; | 
 |         assert_eq!(buf, bytes); | 
 |  | 
 |         // Decoding. | 
 |         let decoded = Metadatum::decode(&buf).expect("should succeed"); | 
 |         assert_eq!(decoded.0, test); | 
 |         assert_eq!(decoded.1, 4); | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn metadatum_program_info() { | 
 |         // Encoding. | 
 |         let test = Metadatum::ProgramInfo("a".to_string()); | 
 |         assert_eq!(test.encoded_len(), 3); | 
 |         let mut buf = vec![0; test.encoded_len()]; | 
 |         let _ = test.encode(&mut buf[..]).expect("should not fail"); | 
 |  | 
 |         let bytes = vec![0x02, 0x03, 0x61]; | 
 |         assert_eq!(buf, bytes); | 
 |  | 
 |         // Decoding. | 
 |         let decoded = Metadatum::decode(&buf).expect("should succeed"); | 
 |         assert_eq!(decoded.0, test); | 
 |         assert_eq!(decoded.1, 3); | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn metadatum_language_code() { | 
 |         // Encoding. | 
 |         let test = Metadatum::LanguageCode("eng".to_string()); | 
 |         assert_eq!(test.encoded_len(), 5); | 
 |         let mut buf = vec![0; test.encoded_len()]; | 
 |         let _ = test.encode(&mut buf[..]).expect("should not fail"); | 
 |  | 
 |         let bytes = vec![0x04, 0x04, 0x65, 0x6E, 0x67]; | 
 |         assert_eq!(buf, bytes); | 
 |  | 
 |         // Decoding. | 
 |         let decoded = Metadatum::decode(&buf).expect("should succeed"); | 
 |         assert_eq!(decoded.0, test); | 
 |         assert_eq!(decoded.1, 5); | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn metadatum_ccid_list() { | 
 |         // Encoding. | 
 |         let test = Metadatum::CCIDList(vec![0x01, 0x02]); | 
 |         assert_eq!(test.encoded_len(), 4); | 
 |         let mut buf = vec![0; test.encoded_len()]; | 
 |         let _ = test.encode(&mut buf[..]).expect("should not fail"); | 
 |  | 
 |         let bytes = vec![0x03, 0x05, 0x01, 0x02]; | 
 |         assert_eq!(buf, bytes); | 
 |  | 
 |         // Decoding. | 
 |         let decoded = Metadatum::decode(&buf).expect("should succeed"); | 
 |         assert_eq!(decoded.0, test); | 
 |         assert_eq!(decoded.1, 4); | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn metadatum_parental_rating() { | 
 |         // Encding. | 
 |         let test = Metadatum::ParentalRating(Rating::Age(8)); | 
 |         assert_eq!(test.encoded_len(), 0x03); | 
 |         let mut buf = vec![0; test.encoded_len()]; | 
 |         let _ = test.encode(&mut buf[..]).expect("should not fail"); | 
 |  | 
 |         let bytes = vec![0x02, 0x06, 0x05]; | 
 |         assert_eq!(buf, bytes); | 
 |  | 
 |         // Decoding. | 
 |         let decoded = Metadatum::decode(&buf).expect("should succeed"); | 
 |         assert_eq!(decoded.0, test); | 
 |         assert_eq!(decoded.1, 3); | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn metadatum_vendor_specific() { | 
 |         // Encoding. | 
 |         let test = Metadatum::VendorSpecific { company_id: 0x00E0, metadata: vec![0x01, 0x02] }; | 
 |         assert_eq!(test.encoded_len(), 0x06); | 
 |         let mut buf = vec![0; test.encoded_len()]; | 
 |         let _ = test.encode(&mut buf[..]).expect("should not fail"); | 
 |  | 
 |         let bytes = vec![0x05, 0xFF, 0xE0, 0x00, 0x01, 0x02]; | 
 |         assert_eq!(buf, bytes); | 
 |  | 
 |         // Decoding. | 
 |         let decoded = Metadatum::decode(&buf).expect("should succeed"); | 
 |         assert_eq!(decoded.0, test); | 
 |         assert_eq!(decoded.1, 6); | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn metadatum_audio_active_state() { | 
 |         // Encoding. | 
 |         let test = Metadatum::AudioActiveState(true); | 
 |         assert_eq!(test.encoded_len(), 0x03); | 
 |         let mut buf = vec![0; test.encoded_len()]; | 
 |         let _ = test.encode(&mut buf[..]).expect("should not fail"); | 
 |  | 
 |         let bytes = vec![0x02, 0x08, 0x01]; | 
 |         assert_eq!(buf, bytes); | 
 |  | 
 |         // Decoding. | 
 |         let decoded = Metadatum::decode(&buf).expect("should succeed"); | 
 |         assert_eq!(decoded.0, test); | 
 |         assert_eq!(decoded.1, 3); | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn metadatum_broadcast_audio_immediate_rendering() { | 
 |         // Encoding. | 
 |         let test = Metadatum::BroadcastAudioImmediateRenderingFlag; | 
 |         assert_eq!(test.encoded_len(), 0x02); | 
 |         let mut buf = vec![0; test.encoded_len()]; | 
 |         let _ = test.encode(&mut buf[..]).expect("should not fail"); | 
 |  | 
 |         let bytes = vec![0x01, 0x09]; | 
 |         assert_eq!(buf, bytes); | 
 |  | 
 |         // Decoding. | 
 |         let decoded = Metadatum::decode(&buf).expect("should succeed"); | 
 |         assert_eq!(decoded.0, test); | 
 |         assert_eq!(decoded.1, 2); | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn invalid_metadataum() { | 
 |         // Language code must be 3-lettered. | 
 |         let test = Metadatum::LanguageCode("illegal".to_string()); | 
 |         let mut buf = vec![0; test.encoded_len()]; | 
 |         let _ = test.encode(&mut buf[..]).expect_err("should fail"); | 
 |  | 
 |         // Not enough length for Length and Type for decoding. | 
 |         let buf = vec![0x03]; | 
 |         let _ = Metadatum::decode(&buf).expect_err("should fail"); | 
 |  | 
 |         // Not enough length for Value field for decoding. | 
 |         let buf = vec![0x02, 0x01, 0x02]; | 
 |         let _ = Metadatum::decode(&buf).expect_err("should fail"); | 
 |  | 
 |         // Buffer length does not match Length value for decoding. | 
 |         let buf = vec![0x03, 0x03, 0x61]; | 
 |         let _ = Metadatum::decode(&buf).expect_err("should fail"); | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn rating() { | 
 |         let no_rating = Rating::no_rating(); | 
 |         let all_age = Rating::all_age(); | 
 |         let for_age = Rating::min_recommended(5).expect("should succeed"); | 
 |  | 
 |         assert_eq!(<&Rating as Into<u8>>::into(&no_rating), 0x00); | 
 |         assert_eq!(<&Rating as Into<u8>>::into(&all_age), 0x01); | 
 |         assert_eq!(<&Rating as Into<u8>>::into(&for_age), 0x02); | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn decode_rating() { | 
 |         let res = Rating::decode(&[0]).expect("should not fail"); | 
 |         assert_eq!(res.0, Rating::NoRating); | 
 |         assert_eq!(res.1, 1); | 
 |  | 
 |         let res = Rating::decode(&[1]).expect("should not fail"); | 
 |         assert_eq!(res.0, Rating::AllAge); | 
 |         assert_eq!(res.1, 1); | 
 |  | 
 |         let res = Rating::decode(&[2]).expect("should not fail"); | 
 |         assert_eq!(res.0, Rating::Age(5)); | 
 |         assert_eq!(res.1, 1); | 
 |  | 
 |         let res = Rating::decode(&[10]).expect("should not fail"); | 
 |         assert_eq!(res.0, Rating::Age(13)); | 
 |         assert_eq!(res.1, 1); | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn invalid_rating() { | 
 |         let _ = Rating::min_recommended(4).expect_err("should have failed"); | 
 |         let _ = Rating::decode(&[255]).expect_err("should fail"); | 
 |     } | 
 | } |