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:?}"); - } }