[rust][le][bass] Define characteristics for Broadcast Audio Scan Service
Define Broadcast Audio Scan Control Point and Broadcast Receive State
characteristics for Broadcast Audio Scan Service. Add appropriate
methods for encoding and decoding the characteristic to raw bytes data.
Bug: b/308483171
Change-Id: Ie6c6c4a91ac0ed12624db4917c977ffe8546e703
Reviewed-on: https://bluetooth-review.git.corp.google.com/c/bluetooth/+/1180
Reviewed-by: Marie Janssen <jamuraa@google.com>
Reviewed-by: Ani Ramakrishnan <aniramakri@google.com>
diff --git a/rust/bt-bass/.gitignore b/rust/bt-bass/.gitignore
new file mode 100644
index 0000000..ada8be9
--- /dev/null
+++ b/rust/bt-bass/.gitignore
@@ -0,0 +1,14 @@
+# Generated by Cargo
+# will have compiled files and executables
+debug/
+target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# MSVC Windows builds of rustc generate these, which store debugging information
+*.pdb
\ No newline at end of file
diff --git a/rust/bt-bass/Cargo.toml b/rust/bt-bass/Cargo.toml
new file mode 100644
index 0000000..15d0ece
--- /dev/null
+++ b/rust/bt-bass/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "bt-bass"
+version = "0.0.1"
+edition = "2021"
+license = "BSD-2-Clause"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+bt-common = { path = "../bt-common" }
+thiserror = "1.0"
\ No newline at end of file
diff --git a/rust/bt-bass/LICENSE b/rust/bt-bass/LICENSE
new file mode 100644
index 0000000..e5de3c8
--- /dev/null
+++ b/rust/bt-bass/LICENSE
@@ -0,0 +1,24 @@
+Copyright 2023 Google LLC
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/rust/bt-bass/PATENTS b/rust/bt-bass/PATENTS
new file mode 100644
index 0000000..3a21f11
--- /dev/null
+++ b/rust/bt-bass/PATENTS
@@ -0,0 +1,22 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of this crate
+
+Google hereby grants to you a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this
+section) patent license to make, have made, use, offer to sell, sell,
+import, transfer, and otherwise run, modify and propagate the contents
+of this implementation, where such license applies only to
+those patent claims, both currently owned by Google and acquired in
+the future, licensable by Google that are necessarily infringed by
+this implementation. This grant does not include claims that would be
+infringed only as a consequence of further modification of this
+implementation. If you or your agent or exclusive licensee institute
+or order or agree to the institution of patent litigation or any other
+patent enforcement activity against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that this
+implementation constitutes direct or contributory patent
+infringement, or inducement of patent infringement, then any patent
+rights granted to you under this License for this implementation
+shall terminate as of the date such litigation is filed.
diff --git a/rust/bt-bass/src/lib.rs b/rust/bt-bass/src/lib.rs
new file mode 100644
index 0000000..b77366b
--- /dev/null
+++ b/rust/bt-bass/src/lib.rs
@@ -0,0 +1,5 @@
+// Copyright 2023 Google LLC
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+pub mod types;
diff --git a/rust/bt-bass/src/types.rs b/rust/bt-bass/src/types.rs
new file mode 100644
index 0000000..33dcb5f
--- /dev/null
+++ b/rust/bt-bass/src/types.rs
@@ -0,0 +1,1231 @@
+// 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 bt_common::packet_encoding::{Decodable, Encodable, Error as PacketError};
+use bt_common::generic_audio::metadata_ltv::*;
+use bt_common::core::{AdvertisingSetId, PaInterval};
+
+const ADDRESS_BYTE_SIZE: usize = 6;
+const NUM_SUBGROUPS_BYTE_SIZE: usize = 1;
+const PA_SYNC_BYTE_SIZE: usize = 1;
+const SOURCE_ID_BYTE_SIZE: usize = 1;
+
+type SourceId = u8;
+
+/// Broadcast_ID is a 3-byte data on the wire.
+/// See Broadcast Audio Scan Service (BASS) spec v1.0 Table 3.5 for details.
+#[derive(Debug, PartialEq)]
+pub struct BroadcastId(u32);
+
+impl BroadcastId {
+ // On the wire, Broadcast_ID is transported in 3 bytes.
+ const BYTE_SIZE: usize = 3;
+}
+
+impl TryFrom<u32> for BroadcastId {
+ type Error = PacketError;
+
+ fn try_from(value: u32) -> Result<Self, Self::Error> {
+ const MAX_VALUE: u32 = 0xFFFFFF;
+ if value > MAX_VALUE {
+ return Err(PacketError::InvalidParameter(format!("Broadcast ID cannot exceed 3 bytes")));
+ }
+ Ok(BroadcastId(value))
+ }
+}
+
+impl Decodable for BroadcastId {
+ type Error = PacketError;
+
+ fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ if buf.len() != Self::BYTE_SIZE {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+
+ let padded_bytes = [buf[0], buf[1], buf[2], 0x00];
+ Ok((BroadcastId(u32::from_le_bytes(padded_bytes)), Self::BYTE_SIZE))
+ }
+}
+
+impl Encodable for BroadcastId {
+ type Error = PacketError;
+
+ fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+ 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.
+ buf[0..3].copy_from_slice(&self.0.to_le_bytes()[0..3]);
+ Ok(())
+ }
+
+ fn encoded_len(&self) -> core::primitive::usize {
+ Self::BYTE_SIZE
+ }
+}
+
+/// 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,
+ RemoteScanStarted = 0x01,
+ AddSource = 0x02,
+ ModifySource = 0x03,
+ SetBroadcastCode = 0x04,
+ RemoveSource = 0x05,
+}
+
+impl ControlPointOpcode {
+ const BYTE_SIZE: usize = 1;
+}
+
+impl TryFrom<u8> for ControlPointOpcode {
+ type Error = PacketError;
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ let val = match value {
+ 0x00 => Self::RemoteScanStopped,
+ 0x01 => Self::RemoteScanStarted,
+ 0x02 => Self::AddSource,
+ 0x03 => Self::ModifySource,
+ 0x04 => Self::SetBroadcastCode,
+ 0x05 => Self::RemoveSource,
+ _ => return Err(PacketError::OutOfRange),
+ };
+ Ok(val)
+ }
+}
+
+/// 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.
+ fn check_opcode(raw_value: u8) -> Result<ControlPointOpcode, PacketError> {
+ let expected = Self::opcode();
+ let got = ControlPointOpcode::try_from(raw_value)?;
+ if got != expected {
+ return Err(PacketError::InvalidParameter(format!("got opcode {got:?}, expected {expected:?}")));
+ }
+ Ok(got)
+ }
+}
+
+/// See BASS spec v1.0 Section 3.1.1.2 for details.
+#[derive(Debug, PartialEq)]
+pub struct RemoteScanStoppedOperation;
+
+impl ControlPointOperation for RemoteScanStoppedOperation {
+ fn opcode() -> ControlPointOpcode {
+ ControlPointOpcode::RemoteScanStopped
+ }
+}
+
+impl Decodable for RemoteScanStoppedOperation {
+ type Error = PacketError;
+
+ fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ if buf.len() < ControlPointOpcode::BYTE_SIZE {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ let _ = Self::check_opcode(buf[0])?;
+ Ok((RemoteScanStoppedOperation, ControlPointOpcode::BYTE_SIZE))
+ }
+}
+
+impl Encodable for RemoteScanStoppedOperation {
+ type Error = PacketError;
+
+ fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+ if buf.len() < self.encoded_len() {
+ return Err(PacketError::BufferTooSmall);
+ }
+ buf[0] = Self::opcode() as u8;
+ Ok(())
+ }
+
+ fn encoded_len(&self) -> core::primitive::usize {
+ ControlPointOpcode::BYTE_SIZE
+ }
+}
+
+/// See BASS spec v1.0 Section 3.1.1.3 for details.
+#[derive(Debug, PartialEq)]
+pub struct RemoteScanStartedOperation;
+
+impl ControlPointOperation for RemoteScanStartedOperation {
+ fn opcode() -> ControlPointOpcode {
+ ControlPointOpcode::RemoteScanStarted
+ }
+}
+
+impl Decodable for RemoteScanStartedOperation {
+ type Error = PacketError;
+
+ fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ if buf.len() < ControlPointOpcode::BYTE_SIZE {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ let _ = Self::check_opcode(buf[0])?;
+ Ok((RemoteScanStartedOperation, ControlPointOpcode::BYTE_SIZE))
+ }
+}
+
+impl Encodable for RemoteScanStartedOperation {
+ type Error = PacketError;
+
+ fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+ if buf.len() < self.encoded_len() {
+ return Err(PacketError::BufferTooSmall);
+ }
+ buf[0] = Self::opcode() as u8;
+ Ok(())
+ }
+
+ fn encoded_len(&self) -> core::primitive::usize {
+ ControlPointOpcode::BYTE_SIZE
+ }
+}
+
+/// See BASS spec v1.0 Section 3.1.1.4 for details.
+#[derive(Debug, PartialEq)]
+pub struct AddSourceOperation {
+ advertiser_address_type: AddressType,
+ // Address in little endian.
+ advertiser_address: [u8; ADDRESS_BYTE_SIZE],
+ advertising_sid: AdvertisingSetId,
+ broadcast_id: BroadcastId,
+ pa_sync: PaSync,
+ pa_interval: PaInterval,
+ subgroups: Vec<BigSubgroup>,
+}
+
+impl AddSourceOperation {
+ const MIN_PACKET_SIZE: usize = ControlPointOpcode::BYTE_SIZE + AddressType::BYTE_SIZE + ADDRESS_BYTE_SIZE + AdvertisingSetId::BYTE_SIZE +
+ BroadcastId::BYTE_SIZE + PA_SYNC_BYTE_SIZE + PaInterval::BYTE_SIZE + NUM_SUBGROUPS_BYTE_SIZE;
+
+ pub fn new(
+ address_type: AddressType,
+ advertiser_address: [u8; ADDRESS_BYTE_SIZE],
+ sid: u8,
+ broadcast_id: u32,
+ pa_sync: PaSync,
+ pa_interval: u16,
+ subgroups: Vec<BigSubgroup>,
+ ) -> Self {
+ AddSourceOperation {
+ advertiser_address_type: address_type,
+ advertiser_address,
+ advertising_sid: AdvertisingSetId(sid),
+ broadcast_id: BroadcastId(broadcast_id),
+ pa_sync,
+ pa_interval: PaInterval(pa_interval),
+ subgroups,
+ }
+ }
+}
+
+impl ControlPointOperation for AddSourceOperation {
+ fn opcode() -> ControlPointOpcode {
+ ControlPointOpcode::AddSource
+ }
+}
+
+impl Decodable for AddSourceOperation {
+ 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);
+ }
+
+ let _ = Self::check_opcode(buf[0])?;
+ let advertiser_address_type = AddressType::try_from(buf[1])?;
+ let mut advertiser_address = [0; ADDRESS_BYTE_SIZE];
+ advertiser_address.clone_from_slice(&buf[2..8]);
+ let advertising_sid = AdvertisingSetId(buf[8]);
+ let broadcast_id = BroadcastId::decode(&buf[9..12])?.0;
+ let pa_sync = PaSync::try_from(buf[12])?;
+ let pa_interval = PaInterval(u16::from_le_bytes(buf[13..15].try_into().unwrap()));
+ let num_subgroups = buf[15] as usize;
+ let mut subgroups = Vec::new();
+
+ let mut idx: usize = 16;
+ for _i in 0..num_subgroups {
+ if buf.len() <= idx {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ let decoded = BigSubgroup::decode(&buf[idx..])?;
+ subgroups.push(decoded.0);
+ idx += decoded.1;
+ }
+ Ok((Self{
+ advertiser_address_type,
+ advertiser_address,
+ advertising_sid,
+ broadcast_id,
+ pa_sync,
+ pa_interval,
+ subgroups,
+ }, idx))
+ }
+}
+
+impl Encodable for AddSourceOperation {
+ type Error = PacketError;
+
+ fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+ if buf.len() < self.encoded_len() {
+ return Err(PacketError::BufferTooSmall);
+ }
+
+ buf[0] = Self::opcode() as u8;
+ buf[1] = self.advertiser_address_type as u8;
+ buf[2..8].copy_from_slice(&self.advertiser_address);
+ buf[8] = self.advertising_sid.0;
+ self.broadcast_id.encode(&mut buf[9..12])?;
+ buf[12] = self.pa_sync as u8;
+ buf[13..15].copy_from_slice(&self.pa_interval.0.to_le_bytes());
+ buf[15] = self
+ .subgroups
+ .len()
+ .try_into()
+ .map_err(|_| PacketError::InvalidParameter("Num_Subgroups".to_string()))?;
+ let mut idx = 16;
+ for s in &self.subgroups {
+ s.encode(&mut buf[idx..])?;
+ idx += s.encoded_len();
+ }
+ Ok(())
+ }
+
+ fn encoded_len(&self) -> core::primitive::usize {
+ Self::MIN_PACKET_SIZE + self
+ .subgroups
+ .iter()
+ .fold(0, |acc, g| acc + g.encoded_len())
+ }
+}
+
+/// See Broadcast Audio Scan Service spec v1.0 Section 3.1.1.5 for details.
+#[derive(Debug, PartialEq)]
+pub struct ModifySourceOperation {
+ source_id: SourceId,
+ pa_sync: PaSync,
+ pa_interval: PaInterval,
+ subgroups: Vec<BigSubgroup>,
+}
+
+impl ModifySourceOperation {
+ const MIN_PACKET_SIZE: usize = ControlPointOpcode::BYTE_SIZE + SOURCE_ID_BYTE_SIZE + PA_SYNC_BYTE_SIZE + PaInterval::BYTE_SIZE + NUM_SUBGROUPS_BYTE_SIZE;
+
+ pub fn new(
+ source_id: SourceId,
+ pa_sync: PaSync,
+ pa_interval: u16,
+ subgroups: Vec<BigSubgroup>,
+ ) -> Self {
+ ModifySourceOperation {
+ source_id,
+ pa_sync,
+ pa_interval: PaInterval(pa_interval),
+ subgroups,
+ }
+ }
+}
+
+impl ControlPointOperation for ModifySourceOperation {
+ fn opcode() -> ControlPointOpcode {
+ ControlPointOpcode::ModifySource
+ }
+}
+
+impl Decodable for ModifySourceOperation {
+ type Error = PacketError;
+
+ // Min size includes Source_ID, PA_Sync, PA_Interval, and Num_Subgroups params.
+ fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ if buf.len() < Self::MIN_PACKET_SIZE {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ let _ = Self::check_opcode(buf[0])?;
+ let source_id = buf[1];
+ let pa_sync = PaSync::try_from(buf[2])?;
+ let pa_interval = PaInterval(u16::from_le_bytes(buf[3..5].try_into().unwrap()));
+ let num_subgroups = buf[5] as usize;
+ let mut subgroups = Vec::new();
+
+ let mut idx = 6;
+ for _i in 0..num_subgroups {
+ if buf.len() < idx + BigSubgroup::MIN_PACKET_SIZE {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ let decoded = BigSubgroup::decode(&buf[idx..])?;
+ subgroups.push(decoded.0);
+ idx += decoded.1;
+ }
+ Ok((Self{
+ source_id,
+ pa_sync,
+ pa_interval,
+ subgroups,
+ }, idx))
+ }
+}
+
+impl Encodable for ModifySourceOperation {
+ type Error = PacketError;
+
+ fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+ if buf.len() < self.encoded_len() {
+ return Err(PacketError::BufferTooSmall);
+ }
+
+ buf[0] = Self::opcode() as u8;
+ buf[1] = self.source_id;
+ buf[2] = self.pa_sync as u8;
+ buf[3..5].copy_from_slice(&self.pa_interval.0.to_le_bytes());
+ buf[5] = self
+ .subgroups
+ .len()
+ .try_into()
+ .map_err(|_| PacketError::InvalidParameter("Num_Subgroups".to_string()))?;
+ let mut idx = 6;
+ for s in &self.subgroups {
+ s.encode(&mut buf[idx..])?;
+ idx += s.encoded_len();
+ }
+ Ok(())
+ }
+
+ fn encoded_len(&self) -> core::primitive::usize {
+ Self::MIN_PACKET_SIZE +
+ self
+ .subgroups
+ .iter()
+ .fold(0, |acc, g| acc + g.encoded_len())
+ }
+}
+
+/// See Broadcast Audio Scan Service spec v1.0 Section 3.1.1.6 for details.
+#[derive(Debug, PartialEq)]
+pub struct SetBroadcastCodeOperation {
+ source_id: SourceId,
+ broadcast_code: [u8; 16],
+}
+
+impl SetBroadcastCodeOperation {
+ const BROADCAST_CODE_LEN: usize = 16;
+ const PACKET_SIZE: usize = ControlPointOpcode::BYTE_SIZE + SOURCE_ID_BYTE_SIZE + Self::BROADCAST_CODE_LEN;
+
+ pub fn new(source_id: SourceId, broadcast_code: [u8; 16]) -> Self {
+ SetBroadcastCodeOperation{ source_id, broadcast_code }
+ }
+}
+
+impl ControlPointOperation for SetBroadcastCodeOperation {
+ fn opcode() -> ControlPointOpcode {
+ ControlPointOpcode::SetBroadcastCode
+ }
+}
+
+impl Decodable for SetBroadcastCodeOperation {
+ type Error = PacketError;
+
+ // Min size includes Source_ID, PA_Sync, PA_Interval, and Num_Subgroups params.
+ fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ if buf.len() < Self::PACKET_SIZE {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ let _ = Self::check_opcode(buf[0])?;
+ let source_id = buf[1];
+ let mut broadcast_code = [0; Self::BROADCAST_CODE_LEN];
+ broadcast_code.copy_from_slice(&buf[2..2+Self::BROADCAST_CODE_LEN]);
+ Ok((Self{
+ source_id,
+ broadcast_code,
+ }, Self::PACKET_SIZE))
+ }
+}
+
+impl Encodable for SetBroadcastCodeOperation {
+ type Error = PacketError;
+
+ fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+ if buf.len() < self.encoded_len() {
+ return Err(PacketError::BufferTooSmall);
+ }
+
+ buf[0] = Self::opcode() as u8;
+ buf[1] = self.source_id;
+ buf[2..2+Self::BROADCAST_CODE_LEN].copy_from_slice(&self.broadcast_code);
+ Ok(())
+ }
+
+ fn encoded_len(&self) -> core::primitive::usize {
+ Self::PACKET_SIZE
+ }
+}
+
+/// See Broadcast Audio Scan Service spec v1.0 Section 3.1.1.7 for details.
+#[derive(Debug, PartialEq)]
+pub struct RemoveSourceOperation(SourceId);
+
+impl RemoveSourceOperation {
+ const PACKET_SIZE: usize = ControlPointOpcode::BYTE_SIZE + SOURCE_ID_BYTE_SIZE;
+
+ pub fn new(source_id: SourceId) -> Self {
+ RemoveSourceOperation(source_id)
+ }
+}
+
+impl ControlPointOperation for RemoveSourceOperation {
+ fn opcode() -> ControlPointOpcode {
+ ControlPointOpcode::RemoveSource
+ }
+}
+
+impl Decodable for RemoveSourceOperation {
+ type Error = PacketError;
+
+ // Min size includes Source_ID, PA_Sync, PA_Interval, and Num_Subgroups params.
+ fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ if buf.len() < Self::PACKET_SIZE {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ let _ = Self::check_opcode(buf[0])?;
+ let source_id = buf[1];
+ Ok((RemoveSourceOperation(source_id), Self::PACKET_SIZE))
+ }
+}
+
+impl Encodable for RemoveSourceOperation {
+ type Error = PacketError;
+
+ fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+ if buf.len() < self.encoded_len() {
+ return Err(PacketError::BufferTooSmall);
+ }
+
+ buf[0] = Self::opcode() as u8;
+ buf[1] = self.0;
+ Ok(())
+ }
+
+ fn encoded_len(&self) -> core::primitive::usize {
+ Self::PACKET_SIZE
+ }
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum PaSync {
+ DoNotSync = 0x00,
+ SyncPastAvailable = 0x01,
+ SyncPastUnavailable = 0x02,
+}
+
+impl TryFrom<u8> for PaSync {
+ type Error = PacketError;
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ match value {
+ 0x00 => Ok(Self::DoNotSync),
+ 0x01 => Ok(Self::SyncPastAvailable),
+ 0x02 => Ok(Self::SyncPastUnavailable),
+ _ => Err(PacketError::OutOfRange),
+ }
+ }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct BigSubgroup {
+ // 4-octet bitfield. Bit 0-30 = BIS_index[1-31]
+ // 0x00000000: 0b0 = Do not synchronize to BIS_index[x]
+ // 0xxxxxxxxx: 0b1 = Synchronize to BIS_index[x]
+ // 0xFFFFFFFF: means No preference if used in BroadcastAudioScanControlPoint,
+ // Failed to sync if used in BroadcastReceiveState.
+ bis_sync_bitfield: u32,
+ metadata: Metadata,
+}
+
+impl BigSubgroup {
+ const BIS_SYNC_BYTE_SIZE: usize = 4;
+ const METADATA_LENGTH_BYTE_SIZE: usize = 1;
+
+ const MIN_PACKET_SIZE: usize = Self::BIS_SYNC_BYTE_SIZE + Self::METADATA_LENGTH_BYTE_SIZE;
+ const BIS_SYNC_NO_PREFERENCE: u32 = 0xFFFFFFFF;
+
+ pub fn new(bis_sync_bitfield: Option<u32>) -> Self {
+ Self {
+ bis_sync_bitfield: bis_sync_bitfield.unwrap_or(Self::BIS_SYNC_NO_PREFERENCE),
+ metadata: vec![],
+ }
+ }
+
+ pub fn with_metadata(self, metadata: Metadata) -> Self {
+ Self {
+ bis_sync_bitfield: self.bis_sync_bitfield,
+ metadata,
+ }
+ }
+}
+
+impl Decodable for BigSubgroup {
+ type Error = PacketError;
+
+ fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ if buf.len() < BigSubgroup::MIN_PACKET_SIZE {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ 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;
+ }
+ if start_idx != 5 + metadata_len {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ Ok((BigSubgroup { bis_sync_bitfield: bis_sync, metadata }, start_idx))
+ }
+}
+
+impl Encodable for BigSubgroup {
+ type Error = PacketError;
+
+ fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+ if buf.len() < self.encoded_len() {
+ return Err(PacketError::BufferTooSmall);
+ }
+
+ buf[0..4].copy_from_slice(&self.bis_sync_bitfield.to_le_bytes());
+ let metadata_len = self
+ .metadata
+ .iter()
+ .fold(0, |acc, m| acc + m.encoded_len())
+ .try_into()
+ .map_err(|_| PacketError::InvalidParameter("Metadata".to_string()))?;
+ buf[4] = metadata_len;
+ let mut next_idx = 5;
+ for m in &self.metadata {
+ m.encode(&mut buf[next_idx..]).map_err(|e| PacketError::InvalidParameter(format!("{e}")))?;
+ next_idx += m.encoded_len();
+ }
+ Ok(())
+ }
+
+ fn encoded_len(&self) -> core::primitive::usize {
+ Self::MIN_PACKET_SIZE + self.metadata.iter().map(Encodable::encoded_len).sum::<usize>()
+ }
+}
+
+/// See Broadcast Audio Scan Service spec v1.0 Table 3.5 for details.
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum AddressType {
+ // Public Device Address or Public Identity Address.
+ Public = 0x00,
+ // Random Device Address or Random (static) Identity Address.
+ Random = 0x01,
+}
+
+impl AddressType {
+ const BYTE_SIZE: usize = 1;
+}
+
+impl TryFrom<u8> for AddressType {
+ type Error = PacketError;
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ match value {
+ 0x00 => Ok(Self::Public),
+ 0x01 => Ok(Self::Random),
+ _ => Err(PacketError::OutOfRange),
+ }
+ }
+}
+
+/// 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.
+#[derive(Debug, PartialEq)]
+pub struct BroadcastReceiveState {
+ source_id: SourceId,
+ source_address_type: AddressType,
+ // Address in little endian.
+ source_address: [u8; ADDRESS_BYTE_SIZE],
+ source_adv_sid: AdvertisingSetId,
+ broadcast_id: BroadcastId,
+ pa_sync_state: PaSyncState,
+ // Represents BIG_Encryption param with optional Bad_Code param.
+ big_encryption: EncryptionStatus,
+ subgroups: Vec<BigSubgroup>,
+}
+
+impl BroadcastReceiveState {
+ const MIN_PACKET_SIZE: usize = SOURCE_ID_BYTE_SIZE + AddressType::BYTE_SIZE + ADDRESS_BYTE_SIZE + AdvertisingSetId::BYTE_SIZE +
+ BroadcastId::BYTE_SIZE + PA_SYNC_BYTE_SIZE + EncryptionStatus::MIN_PACKET_SIZE + NUM_SUBGROUPS_BYTE_SIZE;
+}
+
+impl Decodable for BroadcastReceiveState {
+ 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);
+ }
+
+ let source_id = buf[0];
+ let source_address_type = AddressType::try_from(buf[1])?;
+ let mut source_address = [0; ADDRESS_BYTE_SIZE];
+ source_address.clone_from_slice(&buf[2..8]);
+ let source_adv_sid = AdvertisingSetId(buf[8]);
+ let broadcast_id = BroadcastId::decode(&buf[9..12])?.0;
+ let pa_sync_state = PaSyncState::try_from(buf[12])?;
+
+ let decoded = EncryptionStatus::decode(&buf[13..])?;
+ let big_encryption = decoded.0;
+ let mut idx = 13 + decoded.1;
+ if buf.len() <= idx {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ let num_subgroups = buf[idx] as usize;
+ let mut subgroups = Vec::new();
+ idx += 1;
+ for _i in 0..num_subgroups {
+ if buf.len() <= idx {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ let (subgroup, consumed) = BigSubgroup::decode(&buf[idx..])?;
+ subgroups.push(subgroup);
+ idx += consumed;
+ }
+ Ok((BroadcastReceiveState{
+ source_id,
+ source_address_type,
+ source_address,
+ source_adv_sid,
+ broadcast_id,
+ pa_sync_state,
+ big_encryption,
+ subgroups,
+ }, idx))
+ }
+}
+
+impl Encodable for BroadcastReceiveState {
+ type Error = PacketError;
+
+ fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+ if buf.len() < self.encoded_len() {
+ return Err(PacketError::BufferTooSmall);
+ }
+
+ buf[0] = self.source_id;
+ buf[1] = self.source_address_type as u8;
+ buf[2..8].copy_from_slice(&self.source_address);
+ buf[8] = self.source_adv_sid.0;
+ self.broadcast_id.encode(&mut buf[9..12])?;
+ buf[12] = self.pa_sync_state as u8;
+ let mut idx = 13 + self.big_encryption.encoded_len();
+ self.big_encryption.encode(&mut buf[13..idx])?;
+ buf[idx] = self
+ .subgroups
+ .len()
+ .try_into()
+ .map_err(|_| PacketError::InvalidParameter("Metadata".to_string()))?;
+ idx += 1;
+ for s in &self.subgroups {
+ s.encode(&mut buf[idx..])?;
+ idx += s.encoded_len();
+ }
+ Ok(())
+ }
+
+ 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,
+ // Num_Subgroups and subgroup-related params.
+ SOURCE_ID_BYTE_SIZE + AddressType::BYTE_SIZE
+ + self.source_address.len()
+ + AdvertisingSetId::BYTE_SIZE
+ + self.broadcast_id.encoded_len()
+ + PA_SYNC_BYTE_SIZE
+ + self.big_encryption.encoded_len()
+ + NUM_SUBGROUPS_BYTE_SIZE
+ + self
+ .subgroups
+ .iter()
+ .map(Encodable::encoded_len).sum::<usize>()
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[repr(u8)]
+pub enum PaSyncState {
+ NotSynced = 0x00,
+ SyncInfoRequest = 0x01,
+ Synced = 0x02,
+ FailedToSync = 0x03,
+ NoPast = 0x04,
+}
+
+impl TryFrom<u8> for PaSyncState {
+ type Error = PacketError;
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ match value {
+ 0x00 => Ok(Self::NotSynced),
+ 0x01 => Ok(Self::SyncInfoRequest),
+ 0x02 => Ok(Self::Synced),
+ 0x03 => Ok(Self::FailedToSync),
+ 0x04 => Ok(Self::NoPast),
+ _ => Err(PacketError::OutOfRange),
+ }
+ }
+}
+
+/// 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,
+ BroadcastCodeRequired,
+ Decrypting,
+ BadCode([u8; 16]),
+}
+
+impl EncryptionStatus {
+ // Should at least include the BIG_Encryption enum value which is 1 byte long.
+ const MIN_PACKET_SIZE: usize = 1;
+
+ // Returns the u8 value that represents the status of encryption
+ // as described for BIG_Encryption parameter.
+ fn raw_value(self) -> u8 {
+ match self {
+ EncryptionStatus::NotEncrypted => 0x00,
+ EncryptionStatus::BroadcastCodeRequired => 0x01,
+ EncryptionStatus::Decrypting => 0x02,
+ EncryptionStatus::BadCode(_) => 0x03,
+ }
+ }
+}
+
+impl Decodable for EncryptionStatus {
+ type Error = PacketError;
+
+ fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ if buf.len() < 1 {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ match buf[0] {
+ 0x00 => Ok((Self::NotEncrypted, 1)),
+ 0x01 => Ok((Self::BroadcastCodeRequired, 1)),
+ 0x02 => Ok((Self::Decrypting, 1)),
+ 0x03 => {
+ if buf.len() < 17 {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ Ok((Self::BadCode(buf[1..17].try_into().unwrap()), 17))
+ }
+ _ => Err(PacketError::OutOfRange),
+ }
+ }
+}
+
+impl Encodable for EncryptionStatus {
+ type Error = PacketError;
+
+ fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+ if buf.len() < self.encoded_len() {
+ return Err(PacketError::BufferTooSmall);
+ }
+
+ buf[0] = self.raw_value();
+ match self {
+ EncryptionStatus::BadCode(code) => buf[1..17].copy_from_slice(code),
+ _ => {}
+ }
+ Ok(())
+ }
+
+ fn encoded_len(&self) -> core::primitive::usize {
+ match self {
+ // For Bad_Code value, we also have to encrypt the incorrect
+ // 16-octet Broadcast_Code. See BASS spec Table 3.9.
+ EncryptionStatus::BadCode(_) => 1 + 16,
+ _ => 1,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn broadcast_id() {
+ let id = BroadcastId(0x000A0B0C);
+
+ assert_eq!(id.encoded_len(), 3);
+ let mut buf = vec![0; id.encoded_len()];
+ let _ = id.encode(&mut buf[..]).expect("should have succeeded");
+
+ let bytes = vec![0x0C, 0x0B, 0x0A];
+ assert_eq!(buf, bytes);
+
+ let (got, bytes) = BroadcastId::decode(&bytes).expect("should succeed");
+ assert_eq!(got, id);
+ assert_eq!(bytes, BroadcastId::BYTE_SIZE);
+ let got = BroadcastId::try_from(u32::from_le_bytes([0x0C, 0x0B, 0x0A, 0x00])).expect("should succeed");
+ assert_eq!(got, id);
+ }
+
+ #[test]
+ fn encryption_status_enum() {
+ let not_encrypted = EncryptionStatus::NotEncrypted;
+ let encrypted = EncryptionStatus::BroadcastCodeRequired;
+ let decrypting = EncryptionStatus::Decrypting;
+ let bad_code = EncryptionStatus::BadCode([
+ 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03,
+ 0x02, 0x01,
+ ]);
+
+ assert_eq!(0x00, not_encrypted.raw_value());
+ assert_eq!(0x01, encrypted.raw_value());
+ assert_eq!(0x02, decrypting.raw_value());
+ assert_eq!(0x03, bad_code.raw_value());
+ }
+
+ #[test]
+ fn encryption_status() {
+ // Encoding not encrypted status.
+ let not_encrypted = EncryptionStatus::NotEncrypted;
+ assert_eq!(not_encrypted.encoded_len(), 1);
+ let mut buf = vec![0; not_encrypted.encoded_len()];
+ let _ = not_encrypted.encode(&mut buf[..]).expect("should not fail");
+
+ let bytes = vec![0x00];
+ assert_eq!(buf, bytes);
+
+ // Decoding not encrypted.
+ let decoded = EncryptionStatus::decode(&bytes).expect("should succeed");
+ assert_eq!(decoded.0, not_encrypted);
+ assert_eq!(decoded.1 ,1);
+
+ // Encoding bad code status with code.
+ let bad_code = EncryptionStatus::BadCode([
+ 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03,
+ 0x02, 0x01,
+ ]);
+ assert_eq!(bad_code.encoded_len(), 17);
+ let mut buf = vec![0; bad_code.encoded_len()];
+ let _ = bad_code.encode(&mut buf[..]).expect("should not fail");
+
+ let bytes = vec![
+ 0x03, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04,
+ 0x03, 0x02, 0x01
+ ];
+ assert_eq!(buf, bytes);
+
+ // Deocoding bad code statsu with code.
+ let decoded = EncryptionStatus::decode(&bytes).expect("should succeed");
+ assert_eq!(decoded.0, bad_code);
+ assert_eq!(decoded.1 ,17);
+ }
+
+ #[test]
+ fn invalid_encryption_status() {
+ // Cannot encode into empty buffer.
+ let not_encrypted = EncryptionStatus::NotEncrypted;
+ let mut buf = vec![];
+ let _ = not_encrypted.encode(&mut buf[..]).expect_err("should fail");
+
+ // Not enough buffer space for encoding.
+ let bad_code = EncryptionStatus::BadCode([
+ 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03,
+ 0x02, 0x01,
+ ]);
+ let mut buf = vec![0; 1];
+ let _ = bad_code.encode(&mut buf[..]).expect_err("should fail");
+
+ // Cannot decode empty buffer.
+ let buf = vec![];
+ let _ = EncryptionStatus::decode(&buf).expect_err("should fail");
+
+ // Bad code status with no code.
+ let buf = vec![0x03];
+ let _ = EncryptionStatus::decode(&buf).expect_err("should fail");
+ }
+
+ #[test]
+ fn remote_scan_stopped() {
+ // Encoding remote scan stopped.
+ let stopped = RemoteScanStoppedOperation;
+ assert_eq!(stopped.encoded_len(), 1);
+ let mut buf = vec![0u8; stopped.encoded_len()];
+ stopped.encode(&mut buf[..]).expect("shoud succeed");
+
+ let bytes = vec![0x00];
+ assert_eq!(buf, bytes);
+
+ // Decoding remote scan stopped.
+ let decoded = RemoteScanStoppedOperation::decode(&bytes).expect("should succeed");
+ assert_eq!(decoded.0, stopped);
+ assert_eq!(decoded.1 ,1);
+ }
+
+ #[test]
+ fn remote_scan_started() {
+ // Encoding remote scan started.
+ let started = RemoteScanStartedOperation;
+ assert_eq!(started.encoded_len(), 1);
+ let mut buf = vec![0u8; started.encoded_len()];
+ started.encode(&mut buf[..]).expect("shoud succeed");
+
+ let bytes = vec![0x01];
+ assert_eq!(buf, vec![0x01]);
+
+ // Decoding remote scan started.
+ let decoded = RemoteScanStartedOperation::decode(&bytes).expect("should succeed");
+ assert_eq!(decoded.0, started);
+ assert_eq!(decoded.1 ,1);
+ }
+
+ #[test]
+ fn add_source_without_subgroups() {
+ // Encoding operation with no subgroups.
+ let op = AddSourceOperation::new(
+ AddressType::Public,
+ [0x04, 0x10, 0x00, 0x00, 0x00, 0x00],
+ 1,
+ 0x11,
+ PaSync::DoNotSync,
+ PaInterval::UNKNOWN_VALUE,
+ vec![],
+ );
+ assert_eq!(op.encoded_len(), 16);
+ let mut buf = vec![0u8; op.encoded_len()];
+ op.encode(&mut buf[..]).expect("shoud succeed");
+
+ let bytes = vec![
+ 0x02, 0x00, 0x04, 0x10, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x11, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00,
+ ];
+ assert_eq!(buf, bytes);
+
+ // Decoding operation with no subgroups.
+ let decoded = AddSourceOperation::decode(&bytes).expect("should succeed");
+ assert_eq!(decoded.0, op);
+ assert_eq!(decoded.1 ,16);
+ }
+
+ #[test]
+ 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
+ ])];
+ let op = AddSourceOperation::new(
+ AddressType::Random,
+ [0x04, 0x10, 0x00, 0x00, 0x00, 0x00],
+ 1,
+ 0x11,
+ PaSync::SyncPastAvailable,
+ PaInterval::UNKNOWN_VALUE,
+ subgroups,
+ );
+ assert_eq!(op.encoded_len(), 31); // 16 for minimum params and params 15 for the subgroup.
+ let mut buf = vec![0u8; op.encoded_len()];
+ op.encode(&mut buf[..]).expect("shoud succeed");
+
+ let bytes = vec![
+ 0x02, 0x01, 0x04, 0x10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x11, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x01,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x0A, // BIS_Sync, Metdata_Length
+ 0x03, 0x01, 0x0C, 0x00, // Preferred_Audio_Contexts metdataum
+ 0x05, 0x03, 0x74, 0x65, 0x73, 0x074, // Program_Info metadatum
+ ];
+ assert_eq!(buf, bytes);
+
+ // Decoding operation with subgroups.
+ let decoded = AddSourceOperation::decode(&bytes).expect("should succeed");
+ assert_eq!(decoded.0, op);
+ assert_eq!(decoded.1, 31);
+ }
+
+ #[test]
+ fn modify_source_without_subgroups() {
+ // Encoding operation with no subgroups.
+ let op = ModifySourceOperation::new(
+ 0x0A,
+ PaSync::SyncPastAvailable,
+ 0x1004,
+ vec![],
+ );
+ assert_eq!(op.encoded_len(), 6);
+ let mut buf = vec![0u8; op.encoded_len()];
+ op.encode(&mut buf[..]).expect("shoud succeed");
+
+ let bytes = vec![0x03, 0x0A, 0x01, 0x04, 0x10, 0x00];
+ assert_eq!(buf, bytes);
+
+ // Decoding operation with no subgroups.
+ let decoded = ModifySourceOperation::decode(&bytes).expect("should succeed");
+ assert_eq!(decoded.0, op);
+ assert_eq!(decoded.1, 6);
+ }
+
+ #[test]
+ 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(Some(0x000000FE))
+ .with_metadata(vec![Metadatum::BroadcastAudioImmediateRenderingFlag]), // encoded_len = 7
+ ];
+ let op = ModifySourceOperation::new(
+ 0x0B,
+ PaSync::DoNotSync,
+ PaInterval::UNKNOWN_VALUE,
+ subgroups,
+ );
+ assert_eq!(op.encoded_len(), 21); // 6 for minimum params and params 15 for two subgroups.
+ let mut buf = vec![0u8; op.encoded_len()];
+ op.encode(&mut buf[..]).expect("shoud succeed");
+
+ let bytes = vec![
+ 0x03, 0x0B, 0x00, 0xFF, 0xFF, 0x02,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x02, 0x06, 0x01, // First subgroup.
+ 0xFE, 0x00, 0x00, 0x00, 0x02, 0x01, 0x09, // Second subgroup.
+ ];
+ assert_eq!(buf, bytes);
+
+ // Decoding operation with subgroups.
+ let decoded = ModifySourceOperation::decode(&bytes).expect("should succeed");
+ assert_eq!(decoded.0, op);
+ assert_eq!(decoded.1, 21);
+ }
+
+ #[test]
+ fn set_broadcast_code() {
+ // Encoding.
+ let op = SetBroadcastCodeOperation::new(
+ 0x0A,
+ [
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14,
+ 0x15, 0x16,
+ ],
+ );
+ assert_eq!(op.encoded_len(), 18);
+ let mut buf = vec![0; op.encoded_len()];
+ op.encode(&mut buf[..]).expect("should succeed");
+
+ let bytes = vec![
+ 0x04, 0x0A, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12,
+ 0x13, 0x14, 0x15, 0x16
+ ];
+ assert_eq!(buf, bytes);
+
+ // Decoding.
+ let decoded = SetBroadcastCodeOperation::decode(&bytes).expect("should succeed");
+ assert_eq!(decoded.0, op);
+ assert_eq!(decoded.1, 18);
+ }
+
+ #[test]
+ fn remove_source() {
+ // Encoding.
+ let op = RemoveSourceOperation::new(0x0A);
+ assert_eq!(op.encoded_len(), 2);
+ let mut buf = vec![0; op.encoded_len()];
+ op.encode(&mut buf[..]).expect("should succeed");
+
+ let bytes = vec![0x05, 0x0A];
+ assert_eq!(buf, bytes);
+
+ // Decoding.
+ let decoded = RemoveSourceOperation::decode(&bytes).expect("should succeed");
+ assert_eq!(decoded.0, op);
+ assert_eq!(decoded.1, 2);
+ }
+
+ #[test]
+ fn broadcast_receive_state_without_subgroups() {
+ // Encoding.
+ let state = BroadcastReceiveState {
+ source_id: 0x01,
+ source_address_type: AddressType::Public,
+ source_address: [0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A],
+ source_adv_sid: AdvertisingSetId(0x01),
+ broadcast_id: BroadcastId(0x00010203),
+ pa_sync_state: PaSyncState::Synced,
+ big_encryption: EncryptionStatus::BadCode([0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08, 0x7, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]),
+ subgroups: vec![],
+ };
+ assert_eq!(state.encoded_len(), 31);
+ let mut buf = vec![0; state.encoded_len()];
+ state.encode(&mut buf[..]).expect("should succeed");
+
+ let bytes = vec![
+ 0x01, 0x00, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x01, 0x03, 0x02, 0x01, 0x02,
+ 0x03, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, // Bad_Code with the code.
+ 0x00,
+ ];
+ assert_eq!(buf, bytes);
+
+ // Decoding.
+ let decoded = BroadcastReceiveState::decode(&bytes).expect("should succeed");
+ assert_eq!(decoded.0, state);
+ assert_eq!(decoded.1, 31);
+ }
+
+ #[test]
+ fn broadcast_receive_state_with_subgroups() {
+ // Encoding
+ let state = BroadcastReceiveState {
+ source_id: 0x01,
+ source_address_type: AddressType::Random,
+ source_address: [0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A],
+ source_adv_sid: AdvertisingSetId(0x01),
+ broadcast_id: BroadcastId(0x00010203),
+ pa_sync_state: PaSyncState::NotSynced,
+ big_encryption: EncryptionStatus::NotEncrypted,
+ subgroups: vec![
+ BigSubgroup::new(None).with_metadata(vec![Metadatum::ParentalRating(Rating::AllAge)]), // encoded_len = 8
+ ],
+ };
+ assert_eq!(state.encoded_len(), 23);
+ let mut buf = vec![0; state.encoded_len()];
+ state.encode(&mut buf[..]).expect("should succeed");
+
+ let bytes = vec![
+ 0x01, 0x01, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x01, 0x03, 0x02, 0x01, 0x00,
+ 0x00, 0x01, // 1 Subgroup.
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x02, 0x06, 0x01, // Subgroup.
+ ];
+ assert_eq!(buf, bytes);
+
+ // Decoding.
+ let decoded = BroadcastReceiveState::decode(&bytes).expect("should succeed");
+ assert_eq!(decoded.0, state);
+ assert_eq!(decoded.1, 23);
+
+ }
+}
diff --git a/rust/bt-common/Cargo.toml b/rust/bt-common/Cargo.toml
index 0e131a5..0b1c788 100644
--- a/rust/bt-common/Cargo.toml
+++ b/rust/bt-common/Cargo.toml
@@ -7,3 +7,7 @@
[dependencies.uuid]
version = "1.4.1"
features = ["macro-diagnostics"]
+
+[dependencies]
+bitflags = "2.4.0"
+thiserror = "1.0"
diff --git a/rust/bt-common/src/core.rs b/rust/bt-common/src/core.rs
new file mode 100644
index 0000000..16bb6ec
--- /dev/null
+++ b/rust/bt-common/src/core.rs
@@ -0,0 +1,68 @@
+// 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 crate::packet_encoding::{Encodable, Error as PacketError};
+
+/// Represents the Advertising Set ID which is 1 byte long.
+/// Follows little endian encoding.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct AdvertisingSetId(pub u8);
+
+impl AdvertisingSetId {
+ // Byte size if this is to be encoded.
+ pub const BYTE_SIZE: usize = 1;
+}
+
+/// Represents the SyncInfo Interval value which is 2 bytes long.
+/// Follows little endian encoding.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct PaInterval(pub u16);
+
+impl PaInterval {
+ pub const BYTE_SIZE: usize = 2;
+ pub const UNKNOWN_VALUE: u16 = 0xFFFF;
+
+ pub const fn unknown() -> Self {
+ Self(Self::UNKNOWN_VALUE)
+ }
+}
+
+impl Encodable for PaInterval {
+ type Error = PacketError;
+
+ /// Encodees the PaInterval to 2 byte value using little endian encoding.
+ fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+ if buf.len() < Self::BYTE_SIZE {
+ return Err(PacketError::BufferTooSmall);
+ }
+ buf[0..Self::BYTE_SIZE].copy_from_slice(&self.0.to_le_bytes());
+ Ok(())
+ }
+
+ fn encoded_len(&self) -> core::primitive::usize {
+ Self::BYTE_SIZE
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn encode_pa_interval() {
+ let mut buf = [0; PaInterval::BYTE_SIZE];
+ let interval = PaInterval(0x1004);
+
+ interval.encode(&mut buf[..]).expect("should succeed");
+ assert_eq!(buf, [0x04, 0x10]);
+ }
+
+ #[test]
+ fn encode_pa_interval_fails() {
+ let mut buf = [0; 1]; // Not enough buffer space.
+ let interval = PaInterval(0x1004);
+
+ interval.encode(&mut buf[..]).expect_err("should fail");
+ }
+}
diff --git a/rust/bt-common/src/generic_audio.rs b/rust/bt-common/src/generic_audio.rs
new file mode 100644
index 0000000..904506c
--- /dev/null
+++ b/rust/bt-common/src/generic_audio.rs
@@ -0,0 +1,5 @@
+// Copyright 2023 Google LLC
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+pub mod metadata_ltv;
diff --git a/rust/bt-common/src/generic_audio/metadata_ltv.rs b/rust/bt-common/src/generic_audio/metadata_ltv.rs
new file mode 100644
index 0000000..e29b636
--- /dev/null
+++ b/rust/bt-common/src/generic_audio/metadata_ltv.rs
@@ -0,0 +1,588 @@
+// 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");
+ }
+}
diff --git a/rust/bt-common/src/lib.rs b/rust/bt-common/src/lib.rs
index 58828f4..67e9360 100644
--- a/rust/bt-common/src/lib.rs
+++ b/rust/bt-common/src/lib.rs
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+extern crate core as rust_core;
+
/// Peers are identified by ids, which should be treated as opaque by service libraries.
/// Stack implementations should ensure that each PeerId identifies a single peer over a single
/// instance of the stack - a [`bt_gatt::Central::connect`] should always attempt to connect to the
@@ -10,11 +12,14 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PeerId(pub u64);
-impl core::fmt::Display for PeerId {
- fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
+impl rust_core::fmt::Display for PeerId {
+ fn fmt(&self, f: &mut rust_core::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(f, "{:x}", self.0)
}
}
+pub mod core;
+pub mod generic_audio;
+pub mod packet_encoding;
pub mod uuid;
pub use crate::uuid::Uuid;
diff --git a/rust/bt-common/src/packet_encoding.rs b/rust/bt-common/src/packet_encoding.rs
new file mode 100644
index 0000000..fa01309
--- /dev/null
+++ b/rust/bt-common/src/packet_encoding.rs
@@ -0,0 +1,41 @@
+// 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 thiserror::Error;
+
+/// A decodable type can be created from a byte buffer.
+/// The type returned is separate (copied) from the buffer once decoded.
+pub trait Decodable: ::core::marker::Sized {
+ type Error;
+
+ /// Decodes into a new object with the number of bytes that were decoded, or returns an error.
+ fn decode(buf: &[u8]) -> ::core::result::Result<(Self, usize), Self::Error>;
+}
+
+/// An encodable type can write itself into a byte buffer.
+pub trait Encodable: ::core::marker::Sized {
+ type Error;
+
+ /// Returns the number of bytes necessary to encode |self|.
+ fn encoded_len(&self) -> ::core::primitive::usize;
+
+ /// Writes the encoded version of |self| at the start of |buf|.
+ /// |buf| must be at least |self.encoded_len()| length.
+ fn encode(&self, buf: &mut [u8]) -> ::core::result::Result<(), Self::Error>;
+}
+
+#[derive(Error, Debug, PartialEq)]
+pub enum Error {
+ #[error("Parameter is not valid: {0}")]
+ InvalidParameter(String),
+
+ #[error("Out-of-range enum value")]
+ OutOfRange,
+
+ #[error("Encoding buffer is too small")]
+ BufferTooSmall,
+
+ #[error("Buffer being decoded is invalid length")]
+ UnexpectedDataLength,
+}