rust/bt-bap: Define common data types in Basic Audio Profile
Define common data types that are used and referenced in various
services of Basic Audio Profiles for consistency.
Change-Id: Iaeb56f1dbfc937aec6db6f038a1d8f3e0ce0947a
Reviewed-on: https://bluetooth-review.git.corp.google.com/c/bluetooth/+/1560
Reviewed-by: Marie Janssen <jamuraa@google.com>
Reviewed-by: Ani Ramakrishnan <aniramakri@google.com>
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index f4da3a7..8b01f6f 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -11,6 +11,7 @@
[workspace.dependencies]
## Local path dependencies (keep sorted)
+bt-bap = { path = "bt-bap" }
bt-bass = { path = "bt-bass" }
bt-broadcast-assistant = { path = "bt-broadcast-assistant" }
bt-common = { path = "bt-common" }
@@ -19,6 +20,7 @@
## External dependencies
assert_matches = "1.5.0"
+bitfield = "0.14.0" # 0.14.0 in Fuchsia
futures = "0.3.19" # 0.3.19 in Fuchsia
lazy_static = "1"
parking_lot = "0.12.0" # 0.12.0 in Fuchsia
diff --git a/rust/bt-bap/Cargo.toml b/rust/bt-bap/Cargo.toml
new file mode 100644
index 0000000..ca7566f
--- /dev/null
+++ b/rust/bt-bap/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "bt-bap"
+version = "0.0.1"
+edition.workspace = true
+license.workspace = true
+
+[dependencies]
+bt-common.workspace = true
diff --git a/rust/bt-bap/src/lib.rs b/rust/bt-bap/src/lib.rs
new file mode 100644
index 0000000..fd96ce3
--- /dev/null
+++ b/rust/bt-bap/src/lib.rs
@@ -0,0 +1,12 @@
+// Copyright 2023 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Broadcast Audio Profile Common Library
+//!
+//! Contains common structs and implenmentations for implementing:
+//! - Broadcast Audio Scan Service
+//! - Basic Audio Profile Scan Delegator
+//! - Basic Audio Profile Broadcast Assistant
+
+pub mod types;
diff --git a/rust/bt-bap/src/types.rs b/rust/bt-bap/src/types.rs
new file mode 100644
index 0000000..cdb3c7e
--- /dev/null
+++ b/rust/bt-bap/src/types.rs
@@ -0,0 +1,483 @@
+// Copyright 2023 The Fuchsia Authors. All rights reserved.
+// 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::CodecId;
+use bt_common::generic_audio::codec_configuration::CodecConfiguration;
+use bt_common::generic_audio::metadata_ltv::Metadata;
+use bt_common::packet_encoding::{Decodable, Encodable, Error as PacketError};
+
+/// Broadcast_ID is a 3-byte data on the wire.
+/// Defined in BAP spec v1.0.1 section 3.7.2.1.
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+pub struct BroadcastId(u32);
+
+impl BroadcastId {
+ // On the wire, Broadcast_ID is transported in 3 bytes.
+ pub const BYTE_SIZE: usize = 3;
+
+ pub fn new(raw_value: u32) -> Self {
+ Self(raw_value)
+ }
+}
+
+impl std::fmt::Display for BroadcastId {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{:#08x}", self.0)
+ }
+}
+
+impl From<BroadcastId> for u32 {
+ fn from(value: BroadcastId) -> u32 {
+ value.0
+ }
+}
+
+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
+ }
+}
+
+/// To associate a PA, used to expose broadcast Audio Stream parameters, with a
+/// broadcast Audio Stream, the Broadcast Source shall transmit EA PDUs that
+/// include the following data. This struct represents the AD data value
+/// excluding the 2-octet Service UUID. See BAP v1.0.1 Section 3.7.2.1 for more
+/// details.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct BroadcastAudioAnnouncement {
+ pub broadcast_id: BroadcastId,
+}
+
+impl BroadcastAudioAnnouncement {
+ const PACKET_SIZE: usize = BroadcastId::BYTE_SIZE;
+}
+
+impl Decodable for BroadcastAudioAnnouncement {
+ type Error = PacketError;
+
+ fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ if buf.len() < Self::PACKET_SIZE {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+
+ let (broadcast_id, _) = BroadcastId::decode(&buf[0..3])?;
+ // According to the spec, broadcast audio announcement service data inlcudes
+ // broadcast id and any additional service data. We don't store any
+ // additional parameters, so for now we just "consume" all of data buffer
+ // without doing anything.
+ Ok((Self { broadcast_id }, buf.len()))
+ }
+}
+
+/// Parameters exposed as part of Basic Audio Announcement from Broadcast
+/// Sources. See BAP v1.0.1 Section 3.7.2.2 for more details.
+// TODO(b/308481381): Fill out the struct.
+#[derive(Clone, Debug, PartialEq)]
+pub struct BroadcastAudioSourceEndpoint {
+ // Actual value is 3 bytes long.
+ pub presentation_delay_ms: u32,
+ pub big: Vec<BroadcastIsochronousGroup>,
+}
+
+impl BroadcastAudioSourceEndpoint {
+ // Should contain presentation delay, num BIG, and at least one BIG praram.
+ const MIN_PACKET_SIZE: usize = 3 + 1 + BroadcastIsochronousGroup::MIN_PACKET_SIZE;
+}
+
+impl Decodable for BroadcastAudioSourceEndpoint {
+ 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 mut idx = 0 as usize;
+ let presentation_delay = u32::from_le_bytes([buf[idx], buf[idx + 1], buf[idx + 2], 0x00]);
+ idx += 3;
+
+ let num_big: usize = buf[idx] as usize;
+ idx += 1;
+ if num_big < 1 {
+ return Err(PacketError::InvalidParameter(format!(
+ "num of subgroups shall be at least 1 got {num_big}"
+ )));
+ }
+
+ let mut big = Vec::new();
+ while big.len() < num_big {
+ let (group, len) = BroadcastIsochronousGroup::decode(&buf[idx..])
+ .map_err(|e| PacketError::InvalidParameter(format!("{e}")))?;
+ big.push(group);
+ idx += len;
+ }
+
+ Ok((Self { presentation_delay_ms: presentation_delay, big }, idx))
+ }
+}
+
+/// A single subgroup in a Basic Audio Announcement as outlined in
+/// BAP spec v1.0.1 Section 3.7.2.2. Each subgroup is used to
+/// group BISes present in the broadcast isochronous group.
+#[derive(Clone, Debug, PartialEq)]
+pub struct BroadcastIsochronousGroup {
+ pub codec_id: CodecId,
+ pub codec_specific_configs: Vec<CodecConfiguration>,
+ pub metadata: Vec<Metadata>,
+ pub bis: Vec<BroadcastIsochronousStream>,
+}
+
+impl BroadcastIsochronousGroup {
+ // Should contain num BIS, codec id, codec specific config len, metadata len,
+ // and at least one BIS praram.
+ const MIN_PACKET_SIZE: usize =
+ 1 + CodecId::BYTE_SIZE + 1 + 1 + BroadcastIsochronousStream::MIN_PACKET_SIZE;
+}
+
+impl Decodable for BroadcastIsochronousGroup {
+ type Error = PacketError;
+
+ fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ if buf.len() < BroadcastIsochronousGroup::MIN_PACKET_SIZE {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+
+ let mut idx = 0;
+ let num_bis = buf[idx] as usize;
+ idx += 1;
+ if num_bis < 1 {
+ return Err(PacketError::InvalidParameter(format!(
+ "num of BIS shall be at least 1 got {num_bis}"
+ )));
+ }
+
+ let (codec_id, read_bytes) = CodecId::decode(&buf[idx..])?;
+ idx += read_bytes;
+
+ let codec_config_len = buf[idx] as usize;
+ idx += 1;
+ if idx + codec_config_len > buf.len() {
+ return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+ }
+ let (results, consumed) = CodecConfiguration::decode_all(&buf[idx..idx + codec_config_len]);
+ if consumed != codec_config_len {
+ return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+ }
+
+ let codec_specific_configs = results.into_iter().filter_map(Result::ok).collect();
+ idx += codec_config_len;
+
+ let metadata_len = buf[idx] as usize;
+ idx += 1;
+ if idx + metadata_len > buf.len() {
+ return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+ }
+
+ let (results_metadata, consumed_len) = Metadata::decode_all(&buf[idx..idx + metadata_len]);
+ if consumed_len != metadata_len {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ // Ignore any undecodable metadata types
+ let metadata = results_metadata.into_iter().filter_map(Result::ok).collect();
+ idx += consumed_len;
+
+ let mut bis = Vec::new();
+ while bis.len() < num_bis {
+ let (stream, len) = BroadcastIsochronousStream::decode(&buf[idx..])
+ .map_err(|e| PacketError::InvalidParameter(format!("{e}")))?;
+ bis.push(stream);
+ idx += len;
+ }
+
+ Ok((BroadcastIsochronousGroup { codec_id, codec_specific_configs, metadata, bis }, idx))
+ }
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct BroadcastIsochronousStream {
+ pub bis_index: u8,
+ pub codec_specific_config: Vec<CodecConfiguration>,
+}
+
+impl BroadcastIsochronousStream {
+ const MIN_PACKET_SIZE: usize = 1 + 1;
+}
+
+impl Decodable for BroadcastIsochronousStream {
+ type Error = PacketError;
+
+ fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ if buf.len() < BroadcastIsochronousStream::MIN_PACKET_SIZE {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+
+ let mut idx = 0;
+
+ let bis_index = buf[idx];
+ idx += 1;
+
+ let codec_config_len = buf[idx] as usize;
+ idx += 1;
+
+ let (results, consumed) = CodecConfiguration::decode_all(&buf[idx..idx + codec_config_len]);
+ if consumed != codec_config_len {
+ return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+ }
+ let codec_specific_configs = results.into_iter().filter_map(Result::ok).collect();
+ idx += codec_config_len;
+
+ Ok((
+ BroadcastIsochronousStream { bis_index, codec_specific_config: codec_specific_configs },
+ idx,
+ ))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use std::collections::HashSet;
+
+ use bt_common::generic_audio::codec_configuration::{FrameDuration, SamplingFrequency};
+ use bt_common::generic_audio::AudioLocation;
+
+ #[test]
+ fn broadcast_id() {
+ // Value bigger than 3 bytes is not a valid broadcast ID.
+ let _ = BroadcastId::try_from(0x010A0B0C).expect_err("should fail");
+
+ let id = BroadcastId::try_from(0x000A0B0C).expect("should succeed");
+
+ 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 broadcast_audio_announcement() {
+ let bytes = vec![0x0C, 0x0B, 0x0A];
+ let broadcast_id = BroadcastId::try_from(0x000A0B0C).unwrap();
+
+ let (got, consumed) = BroadcastAudioAnnouncement::decode(&bytes).expect("should succeed");
+ assert_eq!(got, BroadcastAudioAnnouncement { broadcast_id });
+ assert_eq!(consumed, 3);
+
+ let bytes = vec![
+ 0x0C, 0x0B, 0x0A, 0x01, 0x02, 0x03, 0x04, 0x05, /* some other additional data */
+ ];
+ let (got, consumed) = BroadcastAudioAnnouncement::decode(&bytes).expect("should succeed");
+ assert_eq!(got, BroadcastAudioAnnouncement { broadcast_id });
+ assert_eq!(consumed, 8);
+ }
+
+ #[test]
+ fn decode_bis() {
+ #[rustfmt::skip]
+ let buf = [
+ 0x01, 0x09, // bis index and codec specific config len
+ 0x02, 0x01, 0x06, // sampling frequency LTV
+ 0x05, 0x03, 0x03, 0x00, 0x00, 0x0C, // audio location LTV
+ ];
+
+ let (bis, _read_bytes) =
+ BroadcastIsochronousStream::decode(&buf[..]).expect("should not fail");
+ assert_eq!(
+ bis,
+ BroadcastIsochronousStream {
+ bis_index: 0x01,
+ codec_specific_config: vec![
+ CodecConfiguration::SamplingFrequency(SamplingFrequency::F32000Hz),
+ CodecConfiguration::AudioChannelAllocation(HashSet::from([
+ AudioLocation::FrontLeft,
+ AudioLocation::FrontRight,
+ AudioLocation::LeftSurround,
+ AudioLocation::RightSurround
+ ])),
+ ],
+ }
+ );
+ }
+
+ #[test]
+ fn decode_big() {
+ #[rustfmt::skip]
+ let buf = [
+ 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, // num of bis, codec id
+ 0x04, 0x03, 0x04, 0x04, 0x10, // codec specific config len, octets per codec frame LTV
+ 0x03, 0x02, 0x08, 0x01, // metadata len, audio active state LTV
+ 0x01, 0x00, // bis index, codec specific config len (bis #1)
+ 0x02, 0x03, 0x02, 0x02, 0x01, // bis index, codec specific config len, frame duration LTV (bis #2)
+ ];
+
+ let (big, _read_bytes) =
+ BroadcastIsochronousGroup::decode(&buf[..]).expect("should not fail");
+ assert_eq!(
+ big,
+ BroadcastIsochronousGroup {
+ codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Transparent),
+ codec_specific_configs: vec![CodecConfiguration::OctetsPerCodecFrame(0x1004),],
+ metadata: vec![Metadata::AudioActiveState(true)],
+ bis: vec![
+ BroadcastIsochronousStream { bis_index: 0x01, codec_specific_config: vec![] },
+ BroadcastIsochronousStream {
+ bis_index: 0x02,
+ codec_specific_config: vec![CodecConfiguration::FrameDuration(
+ FrameDuration::TenMs
+ )],
+ },
+ ],
+ }
+ );
+ }
+
+ #[test]
+ fn decode_base() {
+ #[rustfmt::skip]
+ let buf = [
+ 0x10, 0x20, 0x30, 0x02, // presentation delay, num of subgroups
+ 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, // num of bis, codec id (big #1)
+ 0x00, // codec specific config len
+ 0x00, // metadata len,
+ 0x01, 0x00, // bis index, codec specific config len (big #1 / bis #1)
+ 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, // num of bis, codec id (big #2)
+ 0x00, // codec specific config len
+ 0x00, // metadata len,
+ 0x01, 0x03, 0x02, 0x05, 0x08, // bis index, codec specific config len, codec frame blocks LTV (big #2 / bis #2)
+ ];
+
+ let (base, _read_bytes) =
+ BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail");
+ assert_eq!(base.presentation_delay_ms, 0x00302010);
+ assert_eq!(base.big.len(), 2);
+ assert_eq!(
+ base.big[0],
+ BroadcastIsochronousGroup {
+ codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Transparent),
+ codec_specific_configs: vec![],
+ metadata: vec![],
+ bis: vec![BroadcastIsochronousStream {
+ bis_index: 0x01,
+ codec_specific_config: vec![],
+ },],
+ }
+ );
+ assert_eq!(
+ base.big[1],
+ BroadcastIsochronousGroup {
+ codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Cvsd),
+ codec_specific_configs: vec![],
+ metadata: vec![],
+ bis: vec![BroadcastIsochronousStream {
+ bis_index: 0x01,
+ codec_specific_config: vec![CodecConfiguration::CodecFramesPerSdu(0x08)],
+ },],
+ }
+ );
+ }
+
+ #[test]
+ fn decode_base_complex() {
+ #[rustfmt::skip]
+ let buf = [
+ 0x20, 0x4e, 0x00, // presentation_delay_ms: 20000 (little-endian)
+ 0x01, // # of subgroups
+ 0x02, // # of BIS in group 1
+ 0x06, 0x00, 0x00, 0x00, 0x00, // Codec ID (Lc3)
+ 0x10, // codec_specific_configuration_length of group 1
+ 0x02, 0x01, 0x05, // sampling frequency 24 kHz
+ 0x02, 0x02, 0x01, // 10 ms frame duration
+ 0x05, 0x03, 0x03, 0x00, 0x00, 0x00, // front left audio channel
+ 0x03, 0x04, 0x3c, 0x00, // 60 octets per codec frame
+ 0x04, // metadata_length of group 1
+ 0x03, 0x02, 0x04, 0x00, // media streaming audio context
+ 0x01, // BIS index of the 1st BIS in group 1
+ 0x06, // codec_specific_configuration_length of the 1st BIS
+ 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, // front left audio channel
+ 0x02, // BIS index of the 2nd BIS in group 1
+ 0x06, // codec_specific_configuration_length of the 2nd BIS
+ 0x05, 0x03, 0x02, 0x00, 0x00, 0x00, // front right audio channel
+ ];
+ let (base, read_bytes) =
+ BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail");
+ assert_eq!(read_bytes, buf.len());
+ assert_eq!(base.presentation_delay_ms, 20000);
+ assert_eq!(base.big.len(), 1);
+ assert_eq!(base.big[0].bis.len(), 2);
+
+ #[rustfmt::skip]
+ let buf = [
+ 0x20, 0x4e, 0x00, // presentation_delay_ms: 20000 (little-endian)
+ 0x01, // # of subgroups
+ 0x01, // # of BIS in group 1
+ 0x06, 0x00, 0x00, 0x00, 0x00, // Codec ID (Lc3)
+ 0x10, // codec_specific_configuration_length of group 1
+ 0x02, 0x01, 0x05, // sampling frequency 24 kHz
+ 0x02, 0x02, 0x01, // 10 ms frame duration
+ 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, // front left audio channel
+ 0x03, 0x04, 0x3c, 0x00, // 60 octets per codec frame
+ 0x02, // metadata_length of group 1
+ 0x01, 0x09, // broadcast audio immediate rendering flag
+ 0x01, // BIS index of the 1st BIS in group 1
+ 0x03, // codec_specific_configuration_length of the 1st BIS
+ 0x02, 0x01, 0x05, // sampling frequency 24 kHz
+ ];
+ let (base, read_bytes) =
+ BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail");
+ assert_eq!(read_bytes, buf.len());
+ assert_eq!(base.presentation_delay_ms, 20000);
+ assert_eq!(base.big.len(), 1);
+ assert_eq!(base.big[0].bis.len(), 1);
+ }
+}
diff --git a/rust/bt-bass/.gitignore b/rust/bt-bass/.gitignore
deleted file mode 100644
index 75e03aa..0000000
--- a/rust/bt-bass/.gitignore
+++ /dev/null
@@ -1,17 +0,0 @@
-# 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
-
-# Vim swap files
-*.swp
diff --git a/rust/bt-bass/Cargo.toml b/rust/bt-bass/Cargo.toml
index 2240624..555d6ea 100644
--- a/rust/bt-bass/Cargo.toml
+++ b/rust/bt-bass/Cargo.toml
@@ -9,6 +9,7 @@
test-utils = []
[dependencies]
+bt-bap.workspace = true
bt-common.workspace = true
bt-gatt = { workspace = true, features = ["test-utils"] }
futures.workspace = true
diff --git a/rust/bt-bass/src/client.rs b/rust/bt-bass/src/client.rs
index 3479c66..f7e4038 100644
--- a/rust/bt-bass/src/client.rs
+++ b/rust/bt-bass/src/client.rs
@@ -13,6 +13,7 @@
use parking_lot::Mutex;
use tracing::warn;
+use bt_bap::types::BroadcastId;
use bt_common::core::{AddressType, AdvertisingSetId, PaInterval};
use bt_common::generic_audio::metadata_ltv::Metadata;
use bt_common::packet_encoding::Decodable;
@@ -663,7 +664,7 @@
assert_eq!(
event,
Event::AddedBroadcastSource(
- BroadcastId(0x040302),
+ BroadcastId::try_from(0x040302).unwrap(),
PaSyncState::Synced,
EncryptionStatus::BroadcastCodeRequired
)
@@ -702,7 +703,7 @@
assert_eq!(
event,
Event::AddedBroadcastSource(
- BroadcastId(0x050403),
+ BroadcastId::try_from(0x050403).unwrap(),
PaSyncState::SyncInfoRequest,
EncryptionStatus::NotEncrypted
)
@@ -752,7 +753,7 @@
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
let op_fut = client.add_broadcast_source(
- BroadcastId(0x11),
+ BroadcastId::try_from(0x11).unwrap(),
AddressType::Public,
[0x04, 0x10, 0x00, 0x00, 0x00, 0x00],
AdvertisingSetId(1),
@@ -778,7 +779,7 @@
source_address_type: AddressType::Public,
source_address: [1, 2, 3, 4, 5, 6],
source_adv_sid: AdvertisingSetId(1),
- broadcast_id: BroadcastId(0x11),
+ broadcast_id: BroadcastId::try_from(0x11).unwrap(),
pa_sync_state: PaSyncState::Synced,
big_encryption: EncryptionStatus::BroadcastCodeRequired,
subgroups: vec![],
@@ -796,7 +797,7 @@
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
let op_fut = client.modify_broadcast_source(
- BroadcastId(0x11),
+ BroadcastId::try_from(0x11).unwrap(),
PaSync::DoNotSync,
Some(PaInterval(0xAAAA)),
None,
@@ -820,7 +821,7 @@
source_address_type: AddressType::Public,
source_address: [1, 2, 3, 4, 5, 6],
source_adv_sid: AdvertisingSetId(1),
- broadcast_id: BroadcastId(0x11),
+ broadcast_id: BroadcastId::try_from(0x11).unwrap(),
pa_sync_state: PaSyncState::Synced,
big_encryption: EncryptionStatus::BroadcastCodeRequired,
subgroups: vec![BigSubgroup::new(None)],
@@ -844,7 +845,7 @@
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
let op_fut = client.modify_broadcast_source(
- BroadcastId(0x11),
+ BroadcastId::try_from(0x11).unwrap(),
PaSync::DoNotSync,
None,
Some(HashSet::from([(0, 1), (0, 2), (0, 3), (0, 5)])),
@@ -865,8 +866,13 @@
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
// Broadcast source wasn't previously added.
- let op_fut =
- client.modify_broadcast_source(BroadcastId(0x11), PaSync::DoNotSync, None, None, None);
+ let op_fut = client.modify_broadcast_source(
+ BroadcastId::try_from(0x11).unwrap(),
+ PaSync::DoNotSync,
+ None,
+ None,
+ None,
+ );
pin_mut!(op_fut);
let polled = op_fut.poll_unpin(&mut noop_cx);
assert_matches!(polled, Poll::Ready(Err(_)));
@@ -875,6 +881,7 @@
#[test]
fn remove_broadcast_source() {
let (mut client, mut fake_peer_service) = setup_client();
+ let bid = BroadcastId::try_from(0x11).expect("should not fail");
// Manually update the broadcast source tracker for testing purposes.
// In practice, this would have been updated from BRS value change notification.
@@ -885,7 +892,7 @@
source_address_type: AddressType::Public,
source_address: [1, 2, 3, 4, 5, 6],
source_adv_sid: AdvertisingSetId(1),
- broadcast_id: BroadcastId(0x11),
+ broadcast_id: bid,
pa_sync_state: PaSyncState::Synced,
big_encryption: EncryptionStatus::BroadcastCodeRequired,
subgroups: vec![],
@@ -897,7 +904,7 @@
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
// Broadcast source wasn't previously added.
- let op_fut = client.remove_broadcast_source(BroadcastId(0x11));
+ let op_fut = client.remove_broadcast_source(bid);
pin_mut!(op_fut);
let polled = op_fut.poll_unpin(&mut noop_cx);
assert_matches!(polled, Poll::Ready(Ok(_)));
@@ -909,7 +916,7 @@
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
// Broadcast source wasn't previously added.
- let op_fut = client.remove_broadcast_source(BroadcastId(0x11));
+ let op_fut = client.remove_broadcast_source(BroadcastId::try_from(0x11).unwrap());
pin_mut!(op_fut);
let polled = op_fut.poll_unpin(&mut noop_cx);
assert_matches!(polled, Poll::Ready(Err(_)));
@@ -928,7 +935,7 @@
source_address_type: AddressType::Public,
source_address: [1, 2, 3, 4, 5, 6],
source_adv_sid: AdvertisingSetId(1),
- broadcast_id: BroadcastId(0x030201),
+ broadcast_id: BroadcastId::try_from(0x030201).unwrap(),
pa_sync_state: PaSyncState::Synced,
big_encryption: EncryptionStatus::BroadcastCodeRequired,
subgroups: vec![],
@@ -941,7 +948,8 @@
);
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
- let set_code_fut = client.set_broadcast_code(BroadcastId(0x030201), [1; 16]);
+ let set_code_fut =
+ client.set_broadcast_code(BroadcastId::try_from(0x030201).unwrap(), [1; 16]);
pin_mut!(set_code_fut);
let polled = set_code_fut.poll_unpin(&mut noop_cx);
assert_matches!(polled, Poll::Ready(Ok(_)));
@@ -952,7 +960,8 @@
let (mut client, _) = setup_client();
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
- let set_code_fut = client.set_broadcast_code(BroadcastId(0x030201), [1; 16]);
+ let set_code_fut =
+ client.set_broadcast_code(BroadcastId::try_from(0x030201).unwrap(), [1; 16]);
pin_mut!(set_code_fut);
let polled = set_code_fut.poll_unpin(&mut noop_cx);
diff --git a/rust/bt-bass/src/client/error.rs b/rust/bt-bass/src/client/error.rs
index 1f6e4bd..1173f4f 100644
--- a/rust/bt-bass/src/client/error.rs
+++ b/rust/bt-bass/src/client/error.rs
@@ -4,11 +4,10 @@
use thiserror::Error;
+use bt_bap::types::BroadcastId;
use bt_common::packet_encoding::Error as PacketError;
use bt_gatt::types::{Error as BTGattError, Handle};
-use crate::types::BroadcastId;
-
#[derive(Debug, Error)]
pub enum Error {
#[error(
diff --git a/rust/bt-bass/src/client/event.rs b/rust/bt-bass/src/client/event.rs
index 4077373..ca48fa8 100644
--- a/rust/bt-bass/src/client/event.rs
+++ b/rust/bt-bass/src/client/event.rs
@@ -10,6 +10,7 @@
use futures::{Stream, StreamExt};
use parking_lot::Mutex;
+use bt_bap::types::BroadcastId;
use bt_common::packet_encoding::Decodable;
use bt_gatt::client::CharacteristicNotification;
use bt_gatt::types::Error as BtGattError;
@@ -273,15 +274,14 @@
// Events should have been generated from notifications.
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
-
let polled = event_streams.poll_next_unpin(&mut noop_cx);
assert_matches!(polled, Poll::Ready(Some(Ok(event))) => {
- assert_eq!(event, Event::AddedBroadcastSource(BroadcastId(0x030201), PaSyncState::FailedToSync, EncryptionStatus::BadCode([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16])));
+ assert_eq!(event, Event::AddedBroadcastSource(BroadcastId::try_from(0x030201).unwrap(), PaSyncState::FailedToSync, EncryptionStatus::BadCode([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16])));
});
let polled = event_streams.poll_next_unpin(&mut noop_cx);
assert_matches!(polled, Poll::Ready(Some(Ok(event))) => {
- assert_eq!(event, Event::AddedBroadcastSource(BroadcastId(0x040302), PaSyncState::NoPast, EncryptionStatus::NotEncrypted));
+ assert_eq!(event, Event::AddedBroadcastSource(BroadcastId::try_from(0x040302).unwrap(), PaSyncState::NoPast, EncryptionStatus::NotEncrypted));
});
// Should be pending because no more events generated from notifications.
@@ -306,7 +306,7 @@
// Event should have been generated from notification.
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
- assert_matches!(event_streams.poll_next_unpin(&mut noop_cx), Poll::Ready(Some(Ok(event))) => { assert_eq!(event, Event::SyncedToPa(BroadcastId(0x040302))) });
+ assert_matches!(event_streams.poll_next_unpin(&mut noop_cx), Poll::Ready(Some(Ok(event))) => { assert_eq!(event, Event::SyncedToPa(BroadcastId::try_from(0x040302).unwrap())) });
// Should be pending because no more events generated from notifications.
assert!(event_streams.poll_next_unpin(&mut noop_cx).is_pending());
@@ -348,7 +348,7 @@
let polled = event_streams.poll_next_unpin(&mut noop_cx);
assert_matches!(polled, Poll::Ready(Some(Ok(event))) => {
- assert_eq!(event, Event::AddedBroadcastSource(BroadcastId(0x040302), PaSyncState::Synced, EncryptionStatus::NotEncrypted));
+ assert_eq!(event, Event::AddedBroadcastSource(BroadcastId::try_from(0x040302).unwrap(), PaSyncState::Synced, EncryptionStatus::NotEncrypted));
});
// Should be pending because no more events generated from notifications.
@@ -366,7 +366,7 @@
// Event should have been generated from notification.
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
- assert_matches!(event_streams.poll_next_unpin(&mut noop_cx), Poll::Ready(Some(Ok(event))) => { assert_eq!(event, Event::RemovedBroadcastSource(BroadcastId(0x040302))) });
+ assert_matches!(event_streams.poll_next_unpin(&mut noop_cx), Poll::Ready(Some(Ok(event))) => { assert_eq!(event, Event::RemovedBroadcastSource(BroadcastId::try_from(0x040302).unwrap())) });
// Should be pending because no more events generated from notifications.
assert!(event_streams.poll_next_unpin(&mut noop_cx).is_pending());
diff --git a/rust/bt-bass/src/tests.rs b/rust/bt-bass/src/tests.rs
index 64d9107..4e3c0ab 100644
--- a/rust/bt-bass/src/tests.rs
+++ b/rust/bt-bass/src/tests.rs
@@ -11,7 +11,7 @@
use crate::client::error::Error;
use crate::client::event::*;
use crate::test_utils::*;
-use crate::types::BroadcastId;
+use bt_bap::types::BroadcastId;
#[test]
fn fake_bass_event_stream() {
@@ -22,9 +22,11 @@
assert!(stream.poll_next_unpin(&mut noop_cx).is_pending());
// Add events.
- sender.unbounded_send(Ok(Event::SyncedToPa(BroadcastId(1)))).expect("should succeed");
sender
- .unbounded_send(Ok(Event::BroadcastCodeRequired(BroadcastId(1))))
+ .unbounded_send(Ok(Event::SyncedToPa(BroadcastId::try_from(1).unwrap())))
+ .expect("should succeed");
+ sender
+ .unbounded_send(Ok(Event::BroadcastCodeRequired(BroadcastId::try_from(1).unwrap())))
.expect("should succeed");
let polled = stream.poll_next_unpin(&mut noop_cx);
diff --git a/rust/bt-bass/src/types.rs b/rust/bt-bass/src/types.rs
index 31c8b2d..0ca916a 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_bap::types::BroadcastId;
use bt_common::core::ltv::LtValue;
use bt_common::core::{AddressType, AdvertisingSetId, PaInterval};
use bt_common::generic_audio::metadata_ltv::*;
@@ -23,75 +24,6 @@
/// BIS index value of a particular BIS. Valid value range is [1 to len of BIS]
pub type BisIndex = 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(Clone, Copy, Debug, Eq, Hash, PartialEq)]
-pub struct BroadcastId(pub u32);
-
-impl BroadcastId {
- // On the wire, Broadcast_ID is transported in 3 bytes.
- pub const BYTE_SIZE: usize = 3;
-
- pub fn new(raw_value: u32) -> Self {
- Self(raw_value)
- }
-
- pub fn raw_value(&self) -> u32 {
- self.0
- }
-}
-
-impl std::fmt::Display for BroadcastId {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(f, "{:#08x}", self.0)
- }
-}
-
-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
- }
-}
-
decodable_enum! {
pub enum ControlPointOpcode<u8, bt_common::packet_encoding::Error, OutOfRange> {
RemoteScanStopped = 0x00,
@@ -614,8 +546,9 @@
Self { bis_sync: bis_sync.unwrap_or_default(), metadata: vec![] }
}
- pub fn with_metadata(self, metadata: Vec<Metadata>) -> Self {
- Self { bis_sync: self.bis_sync, metadata }
+ pub fn with_metadata(mut self, metadata: Vec<Metadata>) -> Self {
+ self.metadata = metadata;
+ self
}
}
@@ -771,7 +704,7 @@
source_address_type: AddressType,
source_address: [u8; ADDRESS_BYTE_SIZE],
source_adv_sid: u8,
- broadcast_id: u32,
+ broadcast_id: BroadcastId,
pa_sync_state: PaSyncState,
big_encryption: EncryptionStatus,
subgroups: Vec<BigSubgroup>,
@@ -781,7 +714,7 @@
source_address_type,
source_address,
source_adv_sid: AdvertisingSetId(source_adv_sid),
- broadcast_id: BroadcastId(broadcast_id),
+ broadcast_id,
pa_sync_state,
big_encryption,
subgroups,
@@ -990,25 +923,6 @@
use bt_common::generic_audio::ContextType;
#[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;
@@ -1147,7 +1061,7 @@
AddressType::Public,
[0x04, 0x10, 0x00, 0x00, 0x00, 0x00],
AdvertisingSetId(1),
- BroadcastId(0x11),
+ BroadcastId::try_from(0x11).unwrap(),
PaSync::DoNotSync,
PaInterval::unknown(),
vec![],
@@ -1179,7 +1093,7 @@
AddressType::Random,
[0x04, 0x10, 0x00, 0x00, 0x00, 0x00],
AdvertisingSetId(1),
- BroadcastId(0x11),
+ BroadcastId::try_from(0x11).unwrap(),
PaSync::SyncPastAvailable,
PaInterval::unknown(),
subgroups,
@@ -1298,7 +1212,7 @@
source_address_type: AddressType::Public,
source_address: [0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A],
source_adv_sid: AdvertisingSetId(0x01),
- broadcast_id: BroadcastId(0x00010203),
+ broadcast_id: BroadcastId::try_from(0x00010203).unwrap(),
pa_sync_state: PaSyncState::Synced,
big_encryption: EncryptionStatus::BadCode([
0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08, 0x7, 0x06, 0x05, 0x04, 0x03,
@@ -1332,7 +1246,7 @@
source_address_type: AddressType::Random,
source_address: [0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A],
source_adv_sid: AdvertisingSetId(0x01),
- broadcast_id: BroadcastId(0x00010203),
+ broadcast_id: BroadcastId::try_from(0x00010203).unwrap(),
pa_sync_state: PaSyncState::NotSynced,
big_encryption: EncryptionStatus::NotEncrypted,
subgroups: vec![
diff --git a/rust/bt-broadcast-assistant/Cargo.toml b/rust/bt-broadcast-assistant/Cargo.toml
index a3ccfa6..824bb18 100644
--- a/rust/bt-broadcast-assistant/Cargo.toml
+++ b/rust/bt-broadcast-assistant/Cargo.toml
@@ -5,6 +5,7 @@
license.workspace = true
[dependencies]
+bt-bap.workspace = true
bt-bass.workspace = true
bt-common.workspace = true
bt-gatt.workspace = true
diff --git a/rust/bt-broadcast-assistant/src/assistant.rs b/rust/bt-broadcast-assistant/src/assistant.rs
index 3d3e61e..19102c7 100644
--- a/rust/bt-broadcast-assistant/src/assistant.rs
+++ b/rust/bt-broadcast-assistant/src/assistant.rs
@@ -7,9 +7,9 @@
use std::sync::Arc;
use thiserror::Error;
+use bt_bap::types::BroadcastId;
use bt_bass::client::error::Error as BassClientError;
use bt_bass::client::BroadcastAudioScanServiceClient;
-use bt_bass::types::BroadcastId;
use bt_common::{PeerId, Uuid};
use bt_gatt::central::*;
use bt_gatt::client::PeerServiceHandle;
@@ -180,6 +180,7 @@
use futures::{pin_mut, FutureExt};
use std::task::Poll;
+ use bt_bap::types::*;
use bt_common::core::{AddressType, AdvertisingSetId};
use bt_gatt::test_utils::{FakeCentral, FakeClient, FakeTypes};
@@ -188,14 +189,14 @@
#[test]
fn merge_broadcast_source() {
let discovered = DiscoveredBroadcastSources::new();
-
+ let bid = BroadcastId::try_from(1001).unwrap();
let (bs, changed) = discovered.merge_broadcast_source_data(
&PeerId(1001),
&BroadcastSource::default()
.with_address([1, 2, 3, 4, 5, 6])
.with_address_type(AddressType::Public)
.with_advertising_sid(AdvertisingSetId(1))
- .with_broadcast_id(BroadcastId(1001)),
+ .with_broadcast_id(bid),
);
assert!(changed);
assert_eq!(
@@ -204,7 +205,7 @@
address: Some([1, 2, 3, 4, 5, 6]),
address_type: Some(AddressType::Public),
advertising_sid: Some(AdvertisingSetId(1)),
- broadcast_id: Some(BroadcastId(1001)),
+ broadcast_id: Some(bid),
pa_interval: None,
endpoint: None,
}
@@ -223,7 +224,7 @@
address: Some([1, 2, 3, 4, 5, 6]),
address_type: Some(AddressType::Random),
advertising_sid: Some(AdvertisingSetId(1)),
- broadcast_id: Some(BroadcastId(1001)),
+ broadcast_id: Some(bid),
pa_interval: None,
endpoint: Some(BroadcastAudioSourceEndpoint {
presentation_delay_ms: 32,
diff --git a/rust/bt-broadcast-assistant/src/assistant/event.rs b/rust/bt-broadcast-assistant/src/assistant/event.rs
index 993ba6f..4990e9c 100644
--- a/rust/bt-broadcast-assistant/src/assistant/event.rs
+++ b/rust/bt-broadcast-assistant/src/assistant/event.rs
@@ -6,7 +6,7 @@
use std::sync::Arc;
use std::task::Poll;
-use bt_bass::types::BroadcastId;
+use bt_bap::types::{BroadcastAudioSourceEndpoint, BroadcastId};
use bt_common::packet_encoding::Decodable;
use bt_common::packet_encoding::Error as PacketError;
use bt_common::PeerId;
@@ -17,7 +17,7 @@
DiscoveredBroadcastSources, Error, BASIC_AUDIO_ANNOUNCEMENT_SERVICE,
BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE,
};
-use crate::types::{BroadcastAudioSourceEndpoint, BroadcastSource};
+use crate::types::BroadcastSource;
#[derive(Debug)]
pub enum Event {
diff --git a/rust/bt-broadcast-assistant/src/assistant/peer.rs b/rust/bt-broadcast-assistant/src/assistant/peer.rs
index 3369a4d..ad101cf 100644
--- a/rust/bt-broadcast-assistant/src/assistant/peer.rs
+++ b/rust/bt-broadcast-assistant/src/assistant/peer.rs
@@ -7,10 +7,11 @@
use std::sync::Arc;
use thiserror::Error;
+use bt_bap::types::BroadcastId;
use bt_bass::client::error::Error as BassClientError;
use bt_bass::client::event::Event as BassEvent;
use bt_bass::client::{BigToBisSync, BroadcastAudioScanServiceClient};
-use bt_bass::types::{BroadcastId, PaSync};
+use bt_bass::types::PaSync;
use bt_common::core::PaInterval;
use bt_common::packet_encoding::Error as PacketError;
use bt_common::PeerId;
@@ -110,11 +111,7 @@
broadcast_source.advertising_sid.unwrap(),
pa_sync,
broadcast_source.pa_interval.unwrap_or(PaInterval::unknown()),
- broadcast_source
- .endpoint
- .unwrap()
- .get_bass_subgroups(bis_sync)
- .map_err(Error::PacketError)?,
+ broadcast_source.endpoint_to_big_subgroups(bis_sync).map_err(Error::PacketError)?,
)
.await
.map_err(Into::into)
@@ -187,6 +184,7 @@
use bt_gatt::types::{
AttributePermissions, CharacteristicProperties, CharacteristicProperty, Handle,
};
+
use bt_gatt::Characteristic;
use crate::types::BroadcastSource;
@@ -276,7 +274,7 @@
.with_address([1, 2, 3, 4, 5, 6])
.with_address_type(AddressType::Public)
.with_advertising_sid(AdvertisingSetId(1))
- .with_broadcast_id(BroadcastId(1001)),
+ .with_broadcast_id(BroadcastId::try_from(1001).unwrap()),
);
// Should fail because not enough information.
diff --git a/rust/bt-broadcast-assistant/src/types.rs b/rust/bt-broadcast-assistant/src/types.rs
index 8c6074e..35d6c6e 100644
--- a/rust/bt-broadcast-assistant/src/types.rs
+++ b/rust/bt-broadcast-assistant/src/types.rs
@@ -2,14 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+use bt_bap::types::*;
use bt_bass::client::BigToBisSync;
-use bt_bass::types::{BigSubgroup, BisSync, BroadcastId};
-use bt_common::core::ltv::LtValue;
-use bt_common::core::{Address, AddressType, CodecId};
+use bt_bass::types::{BigSubgroup, BisSync};
+use bt_common::core::{Address, AddressType};
use bt_common::core::{AdvertisingSetId, PaInterval};
-use bt_common::generic_audio::codec_configuration::CodecConfiguration;
-use bt_common::generic_audio::metadata_ltv::Metadata;
-use bt_common::packet_encoding::{Decodable, Error as PacketError};
+use bt_common::packet_encoding::Error as PacketError;
/// Broadcast source data as advertised through Basic Audio Announcement
/// PA and Broadcast Audio Announcement.
@@ -86,37 +84,29 @@
self.endpoint = Some(endpoint.clone());
}
}
-}
-/// Parameters exposed as part of Basic Audio Announcement from Broadcast
-/// Sources. See BAP spec v1.0.1 Section 3.7.2.2 for more details.
-// TODO(b/308481381): Fill out the struct.
-#[derive(Clone, Debug, PartialEq)]
-pub struct BroadcastAudioSourceEndpoint {
- // Delay is 3 bytes.
- pub(crate) presentation_delay_ms: u32,
- pub(crate) big: Vec<BroadcastIsochronousGroup>,
-}
-
-impl BroadcastAudioSourceEndpoint {
- // Should contain presentation delay, num BIG, and at least one BIG praram.
- const MIN_PACKET_SIZE: usize = 3 + 1 + BroadcastIsochronousGroup::MIN_PACKET_SIZE;
-
- /// Returns the representation of this object's broadcast isochronous groups
- /// that's usable with Broadcast Audio Scan Service operations.
+ /// Returns the representation of this object's endpoint field to
+ /// broadcast isochronous groups presetation that's usable with
+ /// Broadcast Audio Scan Service operations.
///
/// # Arguments
///
/// * `bis_sync` - BIG to BIS sync information. If the set is empty, no
/// preference value is used for all the BIGs
- pub(crate) fn get_bass_subgroups(
+ pub(crate) fn endpoint_to_big_subgroups(
&self,
bis_sync: BigToBisSync,
) -> Result<Vec<BigSubgroup>, PacketError> {
+ if self.endpoint.is_none() {
+ return Err(PacketError::InvalidParameter(
+ "cannot convert empty Broadcast Audio Source Endpoint data to BIG subgroups data"
+ .to_string(),
+ ));
+ }
let mut subgroups = Vec::new();
let sync_map = bt_bass::client::big_to_bis_sync_indices(&bis_sync);
- for (big_index, group) in self.big.iter().enumerate() {
+ for (big_index, group) in self.endpoint.as_ref().unwrap().big.iter().enumerate() {
let bis_sync = match sync_map.get(&(big_index as u8)) {
Some(bis_indices) => {
let mut bis_sync: BisSync = BisSync(0);
@@ -131,305 +121,53 @@
}
}
-impl Decodable for BroadcastAudioSourceEndpoint {
- 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 mut idx = 0 as usize;
- let presentation_delay = u32::from_le_bytes([buf[idx], buf[idx + 1], buf[idx + 2], 0x00]);
- idx += 3;
-
- let num_big: usize = buf[idx] as usize;
- idx += 1;
- if num_big < 1 {
- return Err(PacketError::InvalidParameter(format!(
- "num of subgroups shall be at least 1 got {num_big}"
- )));
- }
-
- let mut big = Vec::new();
- while big.len() < num_big {
- let (group, len) = BroadcastIsochronousGroup::decode(&buf[idx..])
- .map_err(|e| PacketError::InvalidParameter(format!("{e}")))?;
- big.push(group);
- idx += len;
- }
-
- Ok((Self { presentation_delay_ms: presentation_delay, big }, idx))
- }
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub struct BroadcastIsochronousGroup {
- pub(crate) codec_id: CodecId,
- pub(crate) codec_specific_config: Vec<CodecConfiguration>,
- pub(crate) metadata: Vec<Metadata>,
- pub(crate) bis: Vec<BroadcastIsochronousStream>,
-}
-
-impl BroadcastIsochronousGroup {
- // Should contain num BIS, codec id, codec specific config len, metadata len,
- // and at least one BIS praram.
- const MIN_PACKET_SIZE: usize =
- 1 + CodecId::BYTE_SIZE + 1 + 1 + BroadcastIsochronousStream::MIN_PACKET_SIZE;
-}
-
-impl Decodable for BroadcastIsochronousGroup {
- type Error = PacketError;
-
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
- if buf.len() < BroadcastIsochronousGroup::MIN_PACKET_SIZE {
- return Err(PacketError::UnexpectedDataLength);
- }
-
- let mut idx = 0;
- let num_bis = buf[idx] as usize;
- idx += 1;
- if num_bis < 1 {
- return Err(PacketError::InvalidParameter(format!(
- "num of BIS shall be at least 1 got {num_bis}"
- )));
- }
-
- let (codec_id, read_bytes) = CodecId::decode(&buf[idx..])?;
- idx += read_bytes;
-
- let codec_config_len = buf[idx] as usize;
- idx += 1;
-
- let (results, consumed) = CodecConfiguration::decode_all(&buf[idx..idx + codec_config_len]);
- if consumed != codec_config_len {
- return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
- }
-
- let codec_specific_configs = results.into_iter().filter_map(Result::ok).collect();
- idx += codec_config_len;
-
- let metadata_len = buf[idx] as usize;
- idx += 1;
-
- let (results_metadata, consumed_len) = Metadata::decode_all(&buf[idx..idx + metadata_len]);
- if consumed_len != metadata_len {
- return Err(PacketError::UnexpectedDataLength);
- }
- // Ignore any undecodable metadata types
- let metadata = results_metadata.into_iter().filter_map(Result::ok).collect();
- idx += consumed_len;
-
- let mut bis = Vec::new();
- while bis.len() < num_bis {
- let (stream, len) = BroadcastIsochronousStream::decode(&buf[idx..])
- .map_err(|e| PacketError::InvalidParameter(e.to_string()))?;
- bis.push(stream);
- idx += len;
- }
-
- Ok((
- BroadcastIsochronousGroup {
- codec_id,
- codec_specific_config: codec_specific_configs,
- metadata,
- bis,
- },
- idx,
- ))
- }
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub struct BroadcastIsochronousStream {
- pub(crate) bis_index: u8,
- pub(crate) codec_specific_config: Vec<CodecConfiguration>,
-}
-
-impl BroadcastIsochronousStream {
- const MIN_PACKET_SIZE: usize = 1 + 1;
-}
-
-impl Decodable for BroadcastIsochronousStream {
- type Error = PacketError;
-
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
- if buf.len() < BroadcastIsochronousStream::MIN_PACKET_SIZE {
- return Err(PacketError::UnexpectedDataLength);
- }
-
- let mut idx = 0;
-
- let bis_index = buf[idx];
- idx += 1;
-
- let codec_config_len = buf[idx] as usize;
- idx += 1;
-
- let (results, consumed) = CodecConfiguration::decode_all(&buf[idx..idx + codec_config_len]);
- if consumed != codec_config_len {
- return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
- }
- let codec_specific_configs = results.into_iter().filter_map(Result::ok).collect();
- idx += codec_config_len;
-
- Ok((
- BroadcastIsochronousStream { bis_index, codec_specific_config: codec_specific_configs },
- idx,
- ))
- }
-}
-
#[cfg(test)]
mod tests {
- use std::collections::HashSet;
-
use super::*;
- use bt_common::generic_audio::codec_configuration::{FrameDuration, SamplingFrequency};
- use bt_common::generic_audio::AudioLocation;
+ use std::collections::HashSet;
+
+ use bt_common::core::CodecId;
+ use bt_common::generic_audio::metadata_ltv::Metadata;
#[test]
- fn decode_bis() {
- #[rustfmt::skip]
- let buf = [
- 0x01, 0x09, // bis index and codec specific config len
- 0x02, 0x01, 0x06, // sampling frequency LTV
- 0x05, 0x03, 0x03, 0x00, 0x00, 0x0C, // audio location LTV
- ];
+ fn broadcast_source() {
+ let mut b = BroadcastSource::default();
+ assert!(!b.into_add_source());
- let (bis, _read_bytes) =
- BroadcastIsochronousStream::decode(&buf[..]).expect("should not fail");
- assert_eq!(
- bis,
- BroadcastIsochronousStream {
- bis_index: 0x01,
- codec_specific_config: vec![
- CodecConfiguration::SamplingFrequency(SamplingFrequency::F32000Hz),
- CodecConfiguration::AudioChannelAllocation(HashSet::from([
- AudioLocation::FrontLeft,
- AudioLocation::FrontRight,
- AudioLocation::LeftSurround,
- AudioLocation::RightSurround
- ])),
- ],
- }
+ b.merge(
+ &BroadcastSource::default()
+ .with_address([0x1, 0x2, 0x3, 0x4, 0x5, 0x6])
+ .with_address_type(AddressType::Public)
+ .with_advertising_sid(AdvertisingSetId(0x1))
+ .with_broadcast_id(BroadcastId::try_from(0x010203).unwrap()),
);
- }
- #[test]
- fn decode_big() {
- #[rustfmt::skip]
- let buf = [
- 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, // num of bis, codec id
- 0x04, 0x03, 0x04, 0x04, 0x10, // codec specific config len, octets per codec frame LTV
- 0x03, 0x02, 0x08, 0x01, // metadata len, audio active state LTV
- 0x01, 0x00, // bis index, codec specific config len (bis #1)
- 0x02, 0x03, 0x02, 0x02, 0x01, // bis index, codec specific config len, frame duration LTV (bis #2)
- ];
+ assert!(!b.into_add_source());
+ b.endpoint_to_big_subgroups(HashSet::from([(0, 1)]))
+ .expect_err("should fail no endpoint data");
- let (big, _read_bytes) =
- BroadcastIsochronousGroup::decode(&buf[..]).expect("should not fail");
- assert_eq!(
- big,
- BroadcastIsochronousGroup {
- codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Transparent),
- codec_specific_config: vec![CodecConfiguration::OctetsPerCodecFrame(0x1004),],
- metadata: vec![Metadata::AudioActiveState(true)],
- bis: vec![
- BroadcastIsochronousStream { bis_index: 0x01, codec_specific_config: vec![] },
- BroadcastIsochronousStream {
- bis_index: 0x02,
- codec_specific_config: vec![CodecConfiguration::FrameDuration(
- FrameDuration::TenMs
- )],
- },
- ],
- }
- );
- }
-
- #[test]
- fn decode_base() {
- #[rustfmt::skip]
- let buf = [
- 0x10, 0x20, 0x30, 0x02, // presentation delay, num of subgroups
- 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, // num of bis, codec id (big #1)
- 0x00, // codec specific config len
- 0x00, // metadata len,
- 0x01, 0x00, // bis index, codec specific config len (big #1 / bis #1)
- 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, // num of bis, codec id (big #2)
- 0x00, // codec specific config len
- 0x00, // metadata len,
- 0x01, 0x03, 0x02, 0x05, 0x08, // bis index, codec specific config len, codec frame blocks LTV (big #2 / bis #2)
- ];
-
- let (base, _read_bytes) =
- BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail");
- assert_eq!(base.presentation_delay_ms, 0x00302010);
- assert_eq!(base.big.len(), 2);
- assert_eq!(
- base.big[0],
- BroadcastIsochronousGroup {
- codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Transparent),
- codec_specific_config: vec![],
- metadata: vec![],
- bis: vec![BroadcastIsochronousStream {
- bis_index: 0x01,
- codec_specific_config: vec![],
- },],
- }
- );
- assert_eq!(
- base.big[1],
- BroadcastIsochronousGroup {
+ b.merge(&BroadcastSource::default().with_endpoint(BroadcastAudioSourceEndpoint {
+ presentation_delay_ms: 0x010203,
+ big: vec![BroadcastIsochronousGroup {
codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Cvsd),
- codec_specific_config: vec![],
- metadata: vec![],
+ codec_specific_configs: vec![],
+ metadata: vec![Metadata::BroadcastAudioImmediateRenderingFlag],
bis: vec![BroadcastIsochronousStream {
- bis_index: 0x01,
- codec_specific_config: vec![CodecConfiguration::CodecFramesPerSdu(0x08)],
- },],
- }
+ bis_index: 1,
+ codec_specific_config: vec![],
+ }],
+ }],
+ }));
+
+ assert!(b.into_add_source());
+ let subgroups =
+ b.endpoint_to_big_subgroups(HashSet::from([(0, 1), (1, 1)])).expect("should succeed");
+ assert_eq!(subgroups.len(), 1);
+ assert_eq!(
+ subgroups[0],
+ BigSubgroup::new(Some(BisSync(0x00000001)))
+ .with_metadata(vec![Metadata::BroadcastAudioImmediateRenderingFlag])
);
}
-
- #[test]
- fn decode_base_complex() {
- // BroadcastAudioSourceEndpoint { presentation_delay_ms: 20000, big:
- // [BroadcastIsochronousGroup { codec_id: Assigned(Lc3), codec_specific_config:
- // [SamplingFrequency(F24000Hz), FrameDuration(TenMs),
- // AudioChannelAllocation({FrontLeft, FrontRight}), OctetsPerCodecFrame(60)],
- // metadata: [StreamingAudioContexts([Media])], bis:
- // [BroadcastIsochronousStream { bis_index: 1, codec_specific_config:
- // [AudioChannelAllocation({FrontLeft})] }, BroadcastIsochronousStream {
- // bis_index: 2, codec_specific_con$
- // ig: [AudioChannelAllocation({FrontRight})] }] }] }
- #[rustfmt::skip]
- let buf = [
- 0x20, 0x4e, 0x00, 0x01, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x01, 0x05, 0x02, 0x02, 0x01, 0x05,
- 0x03, 0x03, 0x00, 0x00, 0x00, 0x03, 0x04, 0x3c, 0x00, 0x04, 0x03, 0x02, 0x04, 0x00, 0x01, 0x06, 0x05, 0x03, 0x01,
- 0x00, 0x00, 0x00, 0x02, 0x06, 0x05, 0x03, 0x02, 0x00, 0x00, 0x00
- ];
- let (base, _read_bytes) =
- BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail");
- println!("{base:?}");
-
- // BroadcastAudioSourceEndpoint { presentation_delay_ms: 20000, big:
- // [BroadcastIsochronousGroup { codec_id: Assigned(Lc3), codec_specific_config:
- // [SamplingFrequency(F24000Hz), FrameDuration(TenMs),
- // AudioChannelAllocation({FrontLeft}), OctetsPerCodecFrame(60)],
- // metadata: [BroadcastAudioImmediateRenderingFlag], bis:
- // [BroadcastIsochronousStream { bis_index: 1, codec_specific_config:
- // [SamplingFrequency(F24000Hz)] }] }] }
- #[rustfmt::skip]
- let buf = [
- 0x20, 0x4e, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x01, 0x05,
- 0x02, 0x02, 0x01, 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, 0x03, 0x04, 0x3c, 0x00, 0x02,
- 0x01, 0x09, 0x01, 0x03, 0x02, 0x01, 0x05,
- ];
- let (base, _read_bytes) =
- BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail");
- println!("{base:?}");
- }
}