[bt-bass][bt-common] Update Metadata for LtValue Update usage in BASS library as well. Expand possible ratings to match the actual values allowed and add tests. Test: updated tests, cargo test in bt-bass and bt-common Change-Id: Ib4737b37ea4d66dda8000bad2dd85b0008984879 Reviewed-on: https://bluetooth-review.git.corp.google.com/c/bluetooth/+/1420 Reviewed-by: Dayeong Lee <dayeonglee@google.com>
diff --git a/rust/bt-bass/.gitignore b/rust/bt-bass/.gitignore index ada8be9..75e03aa 100644 --- a/rust/bt-bass/.gitignore +++ b/rust/bt-bass/.gitignore
@@ -11,4 +11,7 @@ **/*.rs.bk # MSVC Windows builds of rustc generate these, which store debugging information -*.pdb \ No newline at end of file +*.pdb + +# Vim swap files +*.swp
diff --git a/rust/bt-bass/src/types.rs b/rust/bt-bass/src/types.rs index 2844069..842b7be 100644 --- a/rust/bt-bass/src/types.rs +++ b/rust/bt-bass/src/types.rs
@@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +use bt_common::core::ltv::LtValue; use bt_common::core::{AdvertisingSetId, PaInterval}; use bt_common::generic_audio::metadata_ltv::*; use bt_common::packet_encoding::{Decodable, Encodable, Error as PacketError}; @@ -12,7 +13,8 @@ const PA_SYNC_BYTE_SIZE: usize = 1; const SOURCE_ID_BYTE_SIZE: usize = 1; -/// 16-bit UUID value for the characteristics offered by the Broadcast Audio Scan Service. +/// 16-bit UUID value for the characteristics offered by the Broadcast Audio +/// Scan Service. pub const BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID: Uuid = Uuid::from_u16(0x2BC7); pub const BROADCAST_RECEIVE_STATE_UUID: Uuid = Uuid::from_u16(0x2BC8); @@ -71,7 +73,8 @@ if buf.len() < self.encoded_len() { return Err(PacketError::BufferTooSmall); } - // Since 3-byte value is being fit into u32, we ignore the most significant byte. + // Since 3-byte value is being fit into u32, we ignore the most significant + // byte. buf[0..3].copy_from_slice(&self.0.to_le_bytes()[0..3]); Ok(()) } @@ -83,7 +86,6 @@ /// Broadcast Audio Scan Control Point characteristic opcode as defined in /// Broadcast Audio Scan Service spec v1.0 Section 3.1. -#[repr(u8)] #[derive(Debug, PartialEq)] pub enum ControlPointOpcode { RemoteScanStopped = 0x00, @@ -115,16 +117,18 @@ } } -/// Trait for objects that represent a Broadcast Audio Scan Control Point characteristic. -/// When written by a client, the Broadcast Audio Scan Control Point characteristic is defined as an -/// 8-bit enumerated value, known as the opcode, followed by zero or more parameter octets. -/// The opcode represents the operation that would be performed in the Broadcast Audio Scan Service server. -/// See BASS spec v1.0 Section 3.1 for details. +/// Trait for objects that represent a Broadcast Audio Scan Control Point +/// characteristic. When written by a client, the Broadcast Audio Scan Control +/// Point characteristic is defined as an 8-bit enumerated value, known as the +/// opcode, followed by zero or more parameter octets. The opcode represents the +/// operation that would be performed in the Broadcast Audio Scan Service +/// server. See BASS spec v1.0 Section 3.1 for details. pub trait ControlPointOperation { // Returns the expected opcode for this operation. fn opcode() -> ControlPointOpcode; - // Given the raw encoded value of the opcode, verifies it and returns the equivalent ControlPointOpcode object. + // Given the raw encoded value of the opcode, verifies it and returns the + // equivalent ControlPointOpcode object. fn check_opcode(raw_value: u8) -> Result<ControlPointOpcode, PacketError> { let expected = Self::opcode(); let got = ControlPointOpcode::try_from(raw_value)?; @@ -571,7 +575,7 @@ // 0xFFFFFFFF: means No preference if used in BroadcastAudioScanControlPoint, // Failed to sync if used in ReceiveState. bis_sync_bitfield: u32, - metadata: Metadata, + metadata: Vec<Metadata>, } impl BigSubgroup { @@ -588,7 +592,7 @@ } } - pub fn with_metadata(self, metadata: Metadata) -> Self { + pub fn with_metadata(self, metadata: Vec<Metadata>) -> Self { Self { bis_sync_bitfield: self.bis_sync_bitfield, metadata } } } @@ -602,22 +606,20 @@ } let bis_sync = u32::from_le_bytes(buf[0..4].try_into().unwrap()); let metadata_len = buf[4] as usize; - let mut metadata = Vec::new(); let mut start_idx = 5; if buf.len() < start_idx + metadata_len { return Err(PacketError::UnexpectedDataLength); } - while start_idx < 5 + metadata_len { - let (data, len) = Metadatum::decode(&buf[start_idx..]) - .map_err(|e| PacketError::InvalidParameter(format!("{e}")))?; - metadata.push(data); - start_idx += len; - } + let (results_metadata, consumed_len) = + Metadata::decode_all(&buf[start_idx..start_idx + metadata_len]); + start_idx += consumed_len; if start_idx != 5 + metadata_len { return Err(PacketError::UnexpectedDataLength); } + // Ignore any undecodable metadata types + let metadata = results_metadata.into_iter().filter_map(Result::ok).collect(); Ok((BigSubgroup { bis_sync_bitfield: bis_sync, metadata }, start_idx)) } } @@ -680,10 +682,10 @@ /// Broadcast Receive State characteristic as defined in /// Broadcast Audio Scan Service spec v1.0 Section 3.2. -/// The Broadcast Receive State characteristic is used by the server to expose information -/// about a Broadcast Source. If the server has not written a Source_ID value to the -/// Broadcast Receive State characteristic, the Broadcast Recieve State characteristic value -/// shall be empty. +/// The Broadcast Receive State characteristic is used by the server to expose +/// information about a Broadcast Source. If the server has not written a +/// Source_ID value to the Broadcast Receive State characteristic, the Broadcast +/// Recieve State characteristic value shall be empty. #[derive(Debug, PartialEq)] pub enum BroadcastReceiveState { Empty, @@ -863,7 +865,8 @@ } fn encoded_len(&self) -> core::primitive::usize { - // Length including Source_ID, Source_Address_Type, Source_Address, Source_Adv_SID, Broadcast_ID, PA_Sync_State, BIG_Encryption, Bad_Code, + // Length including Source_ID, Source_Address_Type, Source_Address, + // Source_Adv_SID, Broadcast_ID, PA_Sync_State, BIG_Encryption, Bad_Code, // Num_Subgroups and subgroup-related params. SOURCE_ID_BYTE_SIZE + AddressType::BYTE_SIZE @@ -902,7 +905,8 @@ } } -/// Represents BIG_Encryption and Bad_Code params from BASS spec v.1.0 Table 3.9. +/// Represents BIG_Encryption and Bad_Code params from BASS spec v.1.0 Table +/// 3.9. #[derive(Clone, Copy, Debug, PartialEq)] pub enum EncryptionStatus { NotEncrypted, @@ -979,6 +983,8 @@ mod tests { use super::*; + use bt_common::generic_audio::ContextType; + #[test] fn broadcast_id() { let id = BroadcastId(0x000A0B0C); @@ -1141,8 +1147,8 @@ fn add_source_with_subgroups() { // Encoding operation with subgroups. let subgroups = vec![BigSubgroup::new(None).with_metadata(vec![ - Metadatum::PreferredAudioContexts(ContextType::MEDIA | ContextType::GAME), // encoded_len = 4 - Metadatum::ProgramInfo("test".to_string()), // encoded_len = 6 + Metadata::PreferredAudioContexts(vec![ContextType::Media, ContextType::Game]), // encoded_len = 4 + Metadata::ProgramInfo("test".to_string()), // encoded_len = 6 ])]; let op = AddSourceOperation::new( AddressType::Random, @@ -1192,9 +1198,9 @@ fn modify_source_with_subgroups() { // Encoding operation with subgroups. let subgroups = vec![ - BigSubgroup::new(None).with_metadata(vec![Metadatum::ParentalRating(Rating::AllAge)]), // encoded_len = 8 + BigSubgroup::new(None).with_metadata(vec![Metadata::ParentalRating(Rating::all_age())]), /* encoded_len = 8 */ BigSubgroup::new(Some(0x000000FE)) - .with_metadata(vec![Metadatum::BroadcastAudioImmediateRenderingFlag]), // encoded_len = 7 + .with_metadata(vec![Metadata::BroadcastAudioImmediateRenderingFlag]), /* encoded_len = 7 */ ]; let op = ModifySourceOperation::new( 0x0B, @@ -1309,7 +1315,7 @@ big_encryption: EncryptionStatus::NotEncrypted, subgroups: vec![ BigSubgroup::new(None) - .with_metadata(vec![Metadatum::ParentalRating(Rating::AllAge)]), // encoded_len = 8 + .with_metadata(vec![Metadata::ParentalRating(Rating::AllAge)]), /* encoded_len = 8 */ ], }); assert_eq!(state.encoded_len(), 23);
diff --git a/rust/bt-common/Cargo.toml b/rust/bt-common/Cargo.toml index 7f8ebe3..874319b 100644 --- a/rust/bt-common/Cargo.toml +++ b/rust/bt-common/Cargo.toml
@@ -5,7 +5,6 @@ license = "BSD-2-Clause" [dependencies] -bitflags = "2.4.0" thiserror = "1.0" lazy_static = "1"
diff --git a/rust/bt-common/src/company_id.rs b/rust/bt-common/src/company_id.rs index 78addec..811ef2a 100644 --- a/rust/bt-common/src/company_id.rs +++ b/rust/bt-common/src/company_id.rs
@@ -9,6 +9,12 @@ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct CompanyId(u16); +impl CompanyId { + pub fn to_le_bytes(&self) -> [u8; 2] { + self.0.to_le_bytes() + } +} + impl From<u16> for CompanyId { fn from(value: u16) -> Self { Self(value)
diff --git a/rust/bt-common/src/generic_audio/metadata_ltv.rs b/rust/bt-common/src/generic_audio/metadata_ltv.rs index 3630f4e..8056537 100644 --- a/rust/bt-common/src/generic_audio/metadata_ltv.rs +++ b/rust/bt-common/src/generic_audio/metadata_ltv.rs
@@ -2,342 +2,245 @@ // 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::core::ltv::LtValue; +use crate::packet_encoding::Error as PacketError; +use crate::{decodable_enum, CompanyId}; -use crate::packet_encoding::{Decodable, Encodable, Error as PacketError}; +use crate::generic_audio::ContextType; -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, - } +decodable_enum! { + pub enum MetadataType<u8, PacketError, OutOfRange> { + PreferredAudioContexts = 0x01, + StreamingAudioContexts = 0x02, + ProgramInfo = 0x03, + Language = 0x04, + CCIDList = 0x05, + ParentalRating = 0x06, + ProgramInfoURI = 0x07, + AudioActiveState = 0x08, + BroadcastAudioImmediateRenderingFlag = 0x09, + ExtendedMetadata = 0xfe, + VendorSpecific = 0xff, } } -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), +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Metadata { + PreferredAudioContexts(Vec<ContextType>), + StreamingAudioContexts(Vec<ContextType>), ProgramInfo(String), /// 3 byte, lower case language code as defined in ISO 639-3 - LanguageCode(String), + Language(String), CCIDList(Vec<u8>), ParentalRating(Rating), ProgramInfoURI(String), ExtendedMetadata { type_: u16, - metadata: Vec<u8>, + data: Vec<u8>, }, VendorSpecific { - company_id: u16, - metadata: Vec<u8>, + company_id: CompanyId, + data: 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; +impl LtValue for Metadata { + type Type = MetadataType; - // 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, + const NAME: &'static str = "Metadata"; + + fn type_from_octet(x: u8) -> Option<Self::Type> { + x.try_into().ok() + } + + fn length_range_from_type(ty: Self::Type) -> std::ops::RangeInclusive<u8> { + match ty { + MetadataType::PreferredAudioContexts | MetadataType::StreamingAudioContexts => 3..=3, + MetadataType::ProgramInfo | MetadataType::CCIDList | MetadataType::ProgramInfoURI => { + 1..=u8::MAX + } + MetadataType::Language => 4..=4, + MetadataType::ParentalRating => 2..=2, + MetadataType::ExtendedMetadata | MetadataType::VendorSpecific => 3..=u8::MAX, + MetadataType::AudioActiveState => 2..=2, + MetadataType::BroadcastAudioImmediateRenderingFlag => 1..=1, } } - pub fn type_(&self) -> Type { + fn into_type(&self) -> 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 + Metadata::PreferredAudioContexts(_) => MetadataType::PreferredAudioContexts, + Metadata::StreamingAudioContexts(_) => MetadataType::StreamingAudioContexts, + Metadata::ProgramInfo(_) => MetadataType::ProgramInfo, + Metadata::Language(_) => MetadataType::Language, + Metadata::CCIDList(_) => MetadataType::CCIDList, + Metadata::ParentalRating(_) => MetadataType::ParentalRating, + Metadata::ProgramInfoURI(_) => MetadataType::ProgramInfoURI, + Metadata::ExtendedMetadata { .. } => MetadataType::ExtendedMetadata, + Metadata::VendorSpecific { .. } => MetadataType::VendorSpecific, + Metadata::AudioActiveState(_) => MetadataType::AudioActiveState, + Metadata::BroadcastAudioImmediateRenderingFlag => { + MetadataType::BroadcastAudioImmediateRenderingFlag } } } -} -impl Decodable for Metadatum { - type Error = PacketError; + fn value_encoded_len(&self) -> u8 { + match self { + Metadata::PreferredAudioContexts(_) => 2, + Metadata::StreamingAudioContexts(_) => 2, + Metadata::ProgramInfo(value) => value.len() as u8, + Metadata::Language(_) => 3, + Metadata::CCIDList(ccids) => ccids.len() as u8, + Metadata::ParentalRating(_) => 1, + Metadata::ProgramInfoURI(uri) => uri.len() as u8, + Metadata::ExtendedMetadata { type_: _, data } => 2 + data.len() as u8, + Metadata::VendorSpecific { company_id: _, data } => 2 + data.len() as u8, + Metadata::AudioActiveState(_) => 1, + Metadata::BroadcastAudioImmediateRenderingFlag => 0, + } + } - 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 => { + fn decode_value(ty: &Self::Type, buf: &[u8]) -> Result<Self, crate::packet_encoding::Error> { + let m: Metadata = match ty { + MetadataType::PreferredAudioContexts | MetadataType::StreamingAudioContexts => { let context_type = - ContextType::try_from(u16::from_le_bytes(buf[2..4].try_into().unwrap()))?; - if type_ == Type::PreferredAudioContexts { + ContextType::from_bits(u16::from_le_bytes([buf[0], buf[1]])).collect(); + if ty == &MetadataType::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()) + MetadataType::ProgramInfo | MetadataType::ProgramInfoURI => { + let value = String::from_utf8(buf[..].to_vec()) .map_err(|e| PacketError::InvalidParameter(format!("{e}")))?; - if type_ == Type::ProgramInfo { + if ty == &MetadataType::ProgramInfo { Self::ProgramInfo(value) } else { Self::ProgramInfoURI(value) } } - Type::Language => { - let code = String::from_utf8(buf[2..total_len].to_vec()) + MetadataType::Language => { + let code = String::from_utf8(buf[..].to_vec()) .map_err(|e| PacketError::InvalidParameter(format!("{e}")))?; - Self::LanguageCode(code) + Self::Language(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 } + MetadataType::CCIDList => Self::CCIDList(buf[..].to_vec()), + MetadataType::ParentalRating => Self::ParentalRating(buf[0].into()), + MetadataType::ExtendedMetadata | MetadataType::VendorSpecific => { + let type_or_id = u16::from_le_bytes(buf[0..2].try_into().unwrap()); + let data = if buf.len() >= 2 { buf[2..].to_vec() } else { vec![] }; + if ty == &MetadataType::ExtendedMetadata { + Self::ExtendedMetadata { type_: type_or_id, data } } else { - Self::VendorSpecific { company_id: type_or_id, metadata: data } + Self::VendorSpecific { company_id: type_or_id.into(), data } } } - Type::AudioActiveState => { - if buf[2] > 1 { - return Err(PacketError::UnexpectedDataLength); - } - Self::AudioActiveState(buf[2] != 0) - } - Type::BroadcastAudioImmediateRenderingFlag => { + MetadataType::AudioActiveState => Self::AudioActiveState(buf[0] != 0), + MetadataType::BroadcastAudioImmediateRenderingFlag => { Self::BroadcastAudioImmediateRenderingFlag } }; - return Ok((m, total_len)); + Ok(m) } -} -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; - + fn encode_value(&self, buf: &mut [u8]) -> Result<(), crate::packet_encoding::Error> { match self { Self::PreferredAudioContexts(type_) | Self::StreamingAudioContexts(type_) => { - buf[2..4].copy_from_slice(&type_.bits().to_le_bytes()) + [buf[0], buf[1]] = ContextType::to_bits(type_.iter()).to_le_bytes(); } Self::ProgramInfo(value) | Self::ProgramInfoURI(value) => { - buf[2..2 + value.len()].copy_from_slice(value.as_bytes()) + buf.copy_from_slice(value.as_bytes()) } - Self::LanguageCode(value) if value.len() != 3 => { + Self::Language(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::Language(value) => buf.copy_from_slice(&value.as_bytes()[..3]), + Self::CCIDList(value) => buf.copy_from_slice(&value.as_slice()), + Self::ParentalRating(value) => buf[0] = value.into(), + Self::ExtendedMetadata { type_, data } => { + buf[0..2].copy_from_slice(&type_.to_le_bytes()); + buf[2..].copy_from_slice(&data.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::VendorSpecific { company_id, data } => { + [buf[0], buf[1]] = company_id.to_le_bytes(); + buf[2..].copy_from_slice(&data.as_slice()); } - Self::AudioActiveState(value) => buf[2] = *value as u8, + Self::AudioActiveState(value) => buf[0] = *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 { +impl std::fmt::Display for Metadata { 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:?})") + Metadata::PreferredAudioContexts(v) => write!(f, "Preferred Audio Contexts: {v:?}"), + Metadata::StreamingAudioContexts(v) => write!(f, "Streaming Audio Contexts: {v:?}"), + Metadata::ProgramInfo(v) => write!(f, "Progaam Info: {v:?}"), + Metadata::Language(v) => write!(f, "Language: {v:?}"), + Metadata::CCIDList(v) => write!(f, "CCID List: {v:?}"), + Metadata::ParentalRating(v) => write!(f, "Parental Rating: {v:?}"), + Metadata::ProgramInfoURI(v) => write!(f, "Program Info URI: {v:?}"), + Metadata::ExtendedMetadata { type_, data } => { + write!(f, "Extended Metadata: type(0x{type_:02x}) data({data:?})") } - Metadatum::VendorSpecific { company_id, metadata } => { - write!(f, "Vendor Specific: company(0x{company_id:02x}) metadata({metadata:?})") + Metadata::VendorSpecific { company_id, data } => { + write!(f, "Vendor Specific: {company_id} data({data:?})") } - Metadatum::AudioActiveState(v) => write!(f, "Audio Active State: {v}"), - Metadatum::BroadcastAudioImmediateRenderingFlag => { + Metadata::AudioActiveState(v) => write!(f, "Audio Active State: {v}"), + Metadata::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)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] 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), + Age(u16), } impl Rating { // Minimum age that can be recommended. - const MIN_RECOMMENDED_AGE: u8 = 5; + const MIN_RECOMMENDED_AGE: u16 = 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 AGE_OFFSET: u16 = 3; - const fn no_rating() -> Self { + pub const fn no_rating() -> Self { Self::NoRating } - const fn all_age() -> Self { + pub const fn all_age() -> Self { Self::AllAge } - pub fn min_recommended(age: u8) -> Result<Self, PacketError> { + pub fn min_recommended(age: u16) -> Result<Self, PacketError> { if age < Self::MIN_RECOMMENDED_AGE { return Err(PacketError::InvalidParameter(format!( "minimum recommended age must be at least 5. Got {age}" ))); } + // We can represent up to 255 + 3 (age 258) using this encoding. + if age > u8::MAX as u16 + Self::AGE_OFFSET { + return Err(PacketError::OutOfRange); + } Ok(Rating::Age(age)) } } @@ -347,27 +250,21 @@ match value { Rating::NoRating => 0x00, Rating::AllAge => 0x01, - Rating::Age(a) => a - Rating::AGE_OFFSET, + Rating::Age(a) => (a - Rating::AGE_OFFSET) as u8, } } } -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] { +impl From<u8> for Rating { + fn from(value: u8) -> Self { + match value { 0x00 => Rating::NoRating, 0x01 => Rating::AllAge, value => { - let age = value.checked_add(Self::AGE_OFFSET).ok_or(PacketError::OutOfRange)?; + let age = value as u16 + Self::AGE_OFFSET; Rating::min_recommended(age).unwrap() } - }; - Ok((rating, 1)) + } } } @@ -375,10 +272,12 @@ mod tests { use super::*; + use crate::packet_encoding::{Decodable, Encodable}; + #[test] fn metadataum_preferred_audio_contexts() { // Encoding. - let test = Metadatum::PreferredAudioContexts(ContextType::CONVERSATIONAL); + let test = Metadata::PreferredAudioContexts(vec![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"); @@ -387,7 +286,7 @@ assert_eq!(buf, bytes); // Decoding. - let decoded = Metadatum::decode(&buf).expect("should succeed"); + let decoded = Metadata::decode(&buf).expect("should succeed"); assert_eq!(decoded.0, test); assert_eq!(decoded.1, 4); } @@ -395,7 +294,8 @@ #[test] fn metadatum_streaming_audio_contexts() { // Encoding. - let test = Metadatum::StreamingAudioContexts(ContextType::RINGTONE | ContextType::ALERT); + let test = + Metadata::StreamingAudioContexts(vec![ContextType::Ringtone, ContextType::Alerts]); assert_eq!(test.encoded_len(), 4); let mut buf = vec![0; test.encoded_len()]; let _ = test.encode(&mut buf[..]).expect("should not fail"); @@ -404,7 +304,7 @@ assert_eq!(buf, bytes); // Decoding. - let decoded = Metadatum::decode(&buf).expect("should succeed"); + let decoded = Metadata::decode(&buf).expect("should succeed"); assert_eq!(decoded.0, test); assert_eq!(decoded.1, 4); } @@ -412,7 +312,7 @@ #[test] fn metadatum_program_info() { // Encoding. - let test = Metadatum::ProgramInfo("a".to_string()); + let test = Metadata::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"); @@ -421,7 +321,7 @@ assert_eq!(buf, bytes); // Decoding. - let decoded = Metadatum::decode(&buf).expect("should succeed"); + let decoded = Metadata::decode(&buf).expect("should succeed"); assert_eq!(decoded.0, test); assert_eq!(decoded.1, 3); } @@ -429,7 +329,7 @@ #[test] fn metadatum_language_code() { // Encoding. - let test = Metadatum::LanguageCode("eng".to_string()); + let test = Metadata::Language("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"); @@ -438,7 +338,7 @@ assert_eq!(buf, bytes); // Decoding. - let decoded = Metadatum::decode(&buf).expect("should succeed"); + let decoded = Metadata::decode(&buf).expect("should succeed"); assert_eq!(decoded.0, test); assert_eq!(decoded.1, 5); } @@ -446,7 +346,7 @@ #[test] fn metadatum_ccid_list() { // Encoding. - let test = Metadatum::CCIDList(vec![0x01, 0x02]); + let test = Metadata::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"); @@ -455,7 +355,7 @@ assert_eq!(buf, bytes); // Decoding. - let decoded = Metadatum::decode(&buf).expect("should succeed"); + let decoded = Metadata::decode(&buf).expect("should succeed"); assert_eq!(decoded.0, test); assert_eq!(decoded.1, 4); } @@ -463,7 +363,7 @@ #[test] fn metadatum_parental_rating() { // Encding. - let test = Metadatum::ParentalRating(Rating::Age(8)); + let test = Metadata::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"); @@ -472,7 +372,7 @@ assert_eq!(buf, bytes); // Decoding. - let decoded = Metadatum::decode(&buf).expect("should succeed"); + let decoded = Metadata::decode(&buf).expect("should succeed"); assert_eq!(decoded.0, test); assert_eq!(decoded.1, 3); } @@ -480,7 +380,7 @@ #[test] fn metadatum_vendor_specific() { // Encoding. - let test = Metadatum::VendorSpecific { company_id: 0x00E0, metadata: vec![0x01, 0x02] }; + let test = Metadata::VendorSpecific { company_id: 0x00E0.into(), data: 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"); @@ -489,7 +389,7 @@ assert_eq!(buf, bytes); // Decoding. - let decoded = Metadatum::decode(&buf).expect("should succeed"); + let decoded = Metadata::decode(&buf).expect("should succeed"); assert_eq!(decoded.0, test); assert_eq!(decoded.1, 6); } @@ -497,7 +397,7 @@ #[test] fn metadatum_audio_active_state() { // Encoding. - let test = Metadatum::AudioActiveState(true); + let test = Metadata::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"); @@ -506,7 +406,7 @@ assert_eq!(buf, bytes); // Decoding. - let decoded = Metadatum::decode(&buf).expect("should succeed"); + let decoded = Metadata::decode(&buf).expect("should succeed"); assert_eq!(decoded.0, test); assert_eq!(decoded.1, 3); } @@ -514,7 +414,7 @@ #[test] fn metadatum_broadcast_audio_immediate_rendering() { // Encoding. - let test = Metadatum::BroadcastAudioImmediateRenderingFlag; + let test = Metadata::BroadcastAudioImmediateRenderingFlag; assert_eq!(test.encoded_len(), 0x02); let mut buf = vec![0; test.encoded_len()]; let _ = test.encode(&mut buf[..]).expect("should not fail"); @@ -523,7 +423,7 @@ assert_eq!(buf, bytes); // Decoding. - let decoded = Metadatum::decode(&buf).expect("should succeed"); + let decoded = Metadata::decode(&buf).expect("should succeed"); assert_eq!(decoded.0, test); assert_eq!(decoded.1, 2); } @@ -531,21 +431,21 @@ #[test] fn invalid_metadataum() { // Language code must be 3-lettered. - let test = Metadatum::LanguageCode("illegal".to_string()); + let test = Metadata::Language("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"); + let _ = Metadata::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"); + let _ = Metadata::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"); + let _ = Metadata::decode(&buf).expect_err("should fail"); } #[test] @@ -561,26 +461,25 @@ #[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::from(0); + assert_eq!(res, Rating::NoRating); - let res = Rating::decode(&[1]).expect("should not fail"); - assert_eq!(res.0, Rating::AllAge); - assert_eq!(res.1, 1); + let res = Rating::from(1); + assert_eq!(res, Rating::AllAge); - let res = Rating::decode(&[2]).expect("should not fail"); - assert_eq!(res.0, Rating::Age(5)); - assert_eq!(res.1, 1); + let res = Rating::from(2); + assert_eq!(res, Rating::Age(5)); - let res = Rating::decode(&[10]).expect("should not fail"); - assert_eq!(res.0, Rating::Age(13)); - assert_eq!(res.1, 1); + let res = Rating::from(255); + assert_eq!(res, Rating::Age(258)); + + let res = Rating::min_recommended(258).unwrap(); + assert_eq!(255u8, (&res).into()); } #[test] fn invalid_rating() { let _ = Rating::min_recommended(4).expect_err("should have failed"); - let _ = Rating::decode(&[255]).expect_err("should fail"); + let _ = Rating::min_recommended(350).expect_err("can't recommend for elves"); } }
diff --git a/rust/bt-common/src/packet_encoding.rs b/rust/bt-common/src/packet_encoding.rs index 3667ee1..8778fb2 100644 --- a/rust/bt-common/src/packet_encoding.rs +++ b/rust/bt-common/src/packet_encoding.rs
@@ -71,8 +71,8 @@ } } - impl ::core::convert::From<&$name> for $raw_type { - fn from(v: &$name) -> $raw_type { + impl ::core::convert::From<$name> for $raw_type { + fn from(v: $name) -> $raw_type { match v { $($name::$variant => $val),*, } @@ -104,7 +104,7 @@ } pub fn to_bits<'a>(it: impl Iterator<Item = &'a $type>) -> $raw_type { - it.fold(0, |acc, item| acc | Into::<$raw_type>::into(item)) + it.fold(0, |acc, item| acc | Into::<$raw_type>::into(*item)) } } };