[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))
}
}
};