rust/bt-broadcast-assistant: Improve debug commands and internal APIs Parse debug arguments using FromStr instead of requiring raw enum values. Improve internal types for clarity. Change-Id: I05b1380e59d544716cd0ec63cd64cdb8cce2c3da Reviewed-on: https://bluetooth-review.googlesource.com/c/bluetooth/+/2640 Reviewed-by: Marie Janssen <jamuraa@google.com> Bluetooth-Auto-Submit: Dayeong Lee <dayeonglee@google.com> Commit-Queue: Dayeong Lee <dayeonglee@google.com>
diff --git a/rust/bt-bass/src/client.rs b/rust/bt-bass/src/client.rs index f7b3f5b..7519501 100644 --- a/rust/bt-bass/src/client.rs +++ b/rust/bt-bass/src/client.rs
@@ -5,11 +5,11 @@ pub mod error; pub mod event; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::sync::Arc; -use futures::Future; use futures::stream::{BoxStream, FusedStream, SelectAll, Stream, StreamExt}; +use futures::Future; use log::warn; use parking_lot::Mutex; @@ -26,22 +26,6 @@ const READ_CHARACTERISTIC_BUFFER_SIZE: usize = 255; -/// Index into the vector of BIG subgroups. Valid value range is [0 to len of -/// BIG vector). -pub type SubgroupIndex = u8; - -/// Synchronization information for BIG groups to tell which -/// BISes it should sync to. -pub type BigToBisSync = HashSet<(SubgroupIndex, BisIndex)>; - -pub fn big_to_bis_sync_indices(info: &BigToBisSync) -> HashMap<SubgroupIndex, Vec<BisIndex>> { - let mut sync_map = HashMap::new(); - for (ith_group, bis_index) in info.iter() { - sync_map.entry(*ith_group).or_insert(Vec::new()).push(*bis_index); - } - sync_map -} - /// Keeps track of Source_ID and Broadcast_ID that are associated together. /// Source_ID is assigned by the BASS server to a Broadcast Receive State /// characteristic. If the remote peer with the BASS server autonomously @@ -321,7 +305,7 @@ broadcast_id: BroadcastId, pa_sync: PaSync, pa_interval: Option<PaInterval>, - bis_sync: Option<BigToBisSync>, + bis_sync: Option<HashMap<SubgroupIndex, BisSync>>, metadata_map: Option<HashMap<SubgroupIndex, Vec<Metadata>>>, ) -> Result<(), Error> { let op = { @@ -330,12 +314,10 @@ .ok_or(Error::UnknownBroadcastSource(broadcast_id))?; // Update BIS_Sync param for BIGs if applicable. - if let Some(m) = bis_sync { - let sync_map = big_to_bis_sync_indices(&m); - + if let Some(sync_map) = bis_sync { for (big_index, group) in state.subgroups.iter_mut().enumerate() { - if let Some(bis_indices) = sync_map.get(&(big_index as u8)) { - group.bis_sync.set_sync(bis_indices).map_err(|e| Error::Packet(e))?; + if let Some(bis_sync) = sync_map.get(&(big_index as u8)) { + group.bis_sync = bis_sync.clone(); } } } @@ -420,15 +402,15 @@ use assert_matches::assert_matches; use futures::executor::block_on; - use futures::{FutureExt, pin_mut}; + use futures::{pin_mut, FutureExt}; - use bt_common::Uuid; use bt_common::core::AdvertisingSetId; - use bt_gatt::Characteristic; + use bt_common::Uuid; use bt_gatt::test_utils::*; use bt_gatt::types::{ AttributePermissions, CharacteristicProperties, CharacteristicProperty, Handle, }; + use bt_gatt::Characteristic; const RECEIVE_STATE_1_HANDLE: Handle = Handle(1); const RECEIVE_STATE_2_HANDLE: Handle = Handle(2); @@ -833,7 +815,7 @@ vec![ 0x03, 0x11, 0x00, // opcode, source id, pa sync 0xFF, 0xFF, 0x02, // pa sync, pa interval, num of subgroups - 0x17, 0x00, 0x00, 0x00, // bis sync (0th subgroup) + 0x15, 0x00, 0x00, 0x00, // bis sync (0th subgroup) 0x02, 0x01, 0x09, // metadata len, metadata 0xFF, 0xFF, 0xFF, 0xFF, // bis sync (1th subgroup) 0x05, 0x04, 0x04, 0x65, 0x6E, 0x67, // metadata len, metadata @@ -845,7 +827,7 @@ BroadcastId::try_from(0x11).unwrap(), PaSync::DoNotSync, None, - Some(HashSet::from([(0, 1), (0, 2), (0, 3), (0, 5)])), + Some(HashMap::from([(0, BisSync::sync(vec![1, 3, 5]).unwrap())])), Some(HashMap::from([ (0, vec![Metadata::BroadcastAudioImmediateRenderingFlag]), (1, vec![Metadata::Language("eng".to_string())]),
diff --git a/rust/bt-bass/src/types.rs b/rust/bt-bass/src/types.rs index 22baa25..3f8e2d3 100644 --- a/rust/bt-bass/src/types.rs +++ b/rust/bt-bass/src/types.rs
@@ -7,7 +7,8 @@ use bt_common::core::{AddressType, AdvertisingSetId, PaInterval}; use bt_common::generic_audio::metadata_ltv::*; use bt_common::packet_encoding::{Decodable, Encodable, Error as PacketError}; -use bt_common::{Uuid, decodable_enum}; +use bt_common::{decodable_enum, Uuid}; +use std::str::FromStr; pub const ADDRESS_BYTE_SIZE: usize = 6; const NUM_SUBGROUPS_BYTE_SIZE: usize = 1; @@ -21,6 +22,10 @@ pub type SourceId = u8; +/// Index into the vector of BIG subgroups. Valid value range is [0 to len of +/// BIG vector). +pub type SubgroupIndex = u8; + /// BIS index value of a particular BIS. Valid value range is [1 to len of BIS] pub type BisIndex = u8; @@ -500,29 +505,46 @@ } } +impl FromStr for PaSync { + type Err = PacketError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "PaSyncOff" => Ok(PaSync::DoNotSync), + "PaSyncPast" => Ok(PaSync::SyncPastAvailable), + "PaSyncNoPast" => Ok(PaSync::SyncPastUnavailable), + _ => Err(PacketError::InvalidParameter(format!("invalid pa_sync: {s}"))), + } + } +} + /// 4-octet bitfield. Bit 0-30 = BIS_index[1-31] /// 0x00000000: 0b0 = Do not synchronize to BIS_index[x] /// 0xxxxxxxxx: 0b1 = Synchronize to BIS_index[x] /// 0xFFFFFFFF: means No preference if used in BroadcastAudioScanControlPoint, /// Failed to sync if used in ReceiveState. #[derive(Clone, Debug, PartialEq)] -pub struct BisSync(pub u32); +pub struct BisSync(u32); impl BisSync { const BYTE_SIZE: usize = 4; const NO_PREFERENCE: u32 = 0xFFFFFFFF; - /// Updates whether or not a particular BIS should be set to synchronize or - /// not synchronize. + /// Creates a new BisSync that doens't synchronzie to any BISes. + pub fn no_sync() -> BisSync { + BisSync(0) + } + + /// Updates the specified BIS index to be synchronized. + /// Doesn't touch the synchronize value of other BIS indices. /// /// # Arguments /// /// * `bis_index` - BIS index as defined in the spec. Range should be [1, - /// 31] - /// * `should_sync` - Whether or not to synchronize - fn set_sync_for_index(&mut self, bis_index: BisIndex) -> Result<(), PacketError> { + /// 31]. The specified BIS index will be set to synchronized (0b1). + pub fn synchronize_to_index(&mut self, bis_index: BisIndex) -> Result<(), PacketError> { if bis_index < 1 || bis_index > 31 { - return Err(PacketError::InvalidParameter(format!("Invalid BIS index ({bis_index})"))); + return Err(PacketError::OutOfRange); } let bit_mask = 0b1 << (bis_index - 1); @@ -532,24 +554,18 @@ Ok(()) } - /// Clears previous BIS_Sync params and synchronizes to specified BIS - /// indices. If the BIS index list is empty, no preference value is - /// used. + /// Creates a BisSync value with the specified BIS indices set to be + /// synchronized. /// /// # Arguments /// - /// * `sync_map` - Map of BIS index to whether or not it should be - /// synchronized - pub fn set_sync(&mut self, bis_indices: &Vec<BisIndex>) -> Result<(), PacketError> { - if bis_indices.is_empty() { - self.0 = Self::NO_PREFERENCE; - return Ok(()); - } - self.0 = 0; + /// * `bis_indices` - A vector of BIS indices to synchronize to. + pub fn sync(bis_indices: Vec<BisIndex>) -> Result<Self, PacketError> { + let mut new_sync = Self::no_sync(); for bis_index in bis_indices { - self.set_sync_for_index(*bis_index)?; + new_sync.synchronize_to_index(bis_index)?; } - Ok(()) + Ok(new_sync) } } @@ -559,6 +575,12 @@ } } +impl From<BisSync> for u32 { + fn from(bis_sync: BisSync) -> u32 { + bis_sync.0 + } +} + #[derive(Clone, Debug, PartialEq)] pub struct BigSubgroup { pub(crate) bis_sync: BisSync, @@ -620,7 +642,7 @@ return Err(PacketError::BufferTooSmall); } - buf[0..4].copy_from_slice(&self.bis_sync.0.to_le_bytes()); + buf[0..4].copy_from_slice(&u32::from(self.bis_sync.clone()).to_le_bytes()); let metadata_len = self .metadata .iter() @@ -1049,24 +1071,44 @@ } #[test] - fn bis_sync() { - let mut bis_sync = BisSync::default(); - assert_eq!(bis_sync, BisSync(BisSync::NO_PREFERENCE)); + fn bis_sync_sync() { + let bis_sync = BisSync::sync(vec![1, 6, 31]).expect("should succeed"); + assert_eq!(u32::from(bis_sync), 0x40000021); - bis_sync.set_sync(&vec![1, 6, 31]).expect("should succeed"); - assert_eq!(bis_sync, BisSync(0x40000021)); - - bis_sync.set_sync(&vec![]).expect("should succeed"); - assert_eq!(bis_sync, BisSync::default()); + let bis_sync_empty = BisSync::sync(vec![]).expect("should succeed"); + assert_eq!(u32::from(bis_sync_empty), 0); } #[test] fn invalid_bis_sync() { - let mut bis_sync = BisSync::default(); + BisSync::sync(vec![0]).expect_err("should fail"); + BisSync::sync(vec![32]).expect_err("should fail"); + } - bis_sync.set_sync(&vec![0]).expect_err("should fail"); + #[test] + fn synchronize_to_index() { + let mut bis_sync = BisSync::no_sync(); + assert_eq!(u32::from(bis_sync.clone()), 0); - bis_sync.set_sync(&vec![32]).expect_err("should fail"); + bis_sync.synchronize_to_index(1).expect("should succeed"); + assert_eq!(u32::from(bis_sync.clone()), 0x1); + + bis_sync.synchronize_to_index(31).expect("should succeed"); + assert_eq!(u32::from(bis_sync.clone()), 0x40000001); + + bis_sync.synchronize_to_index(0).expect_err("should fail"); + bis_sync.synchronize_to_index(32).expect_err("should fail"); + } + + #[test] + fn pa_sync_from_str() { + let sync = PaSync::from_str("PaSyncOff").expect("should succeed"); + assert_eq!(sync, PaSync::DoNotSync); + let sync = PaSync::from_str("PaSyncPast").expect("should succeed"); + assert_eq!(sync, PaSync::SyncPastAvailable); + let sync = PaSync::from_str("PaSyncNoPast").expect("should succeed"); + assert_eq!(sync, PaSync::SyncPastUnavailable); + PaSync::from_str("invalid").expect_err("should fail"); } #[test]
diff --git a/rust/bt-broadcast-assistant/src/assistant.rs b/rust/bt-broadcast-assistant/src/assistant.rs index 3fd08cb..b8b50af 100644 --- a/rust/bt-broadcast-assistant/src/assistant.rs +++ b/rust/bt-broadcast-assistant/src/assistant.rs
@@ -4,6 +4,7 @@ use parking_lot::Mutex; use std::collections::HashMap; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use thiserror::Error; @@ -101,7 +102,7 @@ pub struct BroadcastAssistant<T: bt_gatt::GattTypes> { central: T::Central, broadcast_sources: Arc<DiscoveredBroadcastSources>, - scan_stream: Option<T::ScanResultStream>, + broadcast_source_scan_started: Arc<AtomicBool>, } impl<T: bt_gatt::GattTypes + 'static> BroadcastAssistant<T> { @@ -109,11 +110,10 @@ // for broadcast source scanning. Clients must use the `start` // method to poll the event stream for scan results. pub fn new(central: T::Central) -> Self { - let scan_result_stream = central.scan(&Self::scan_filters()); Self { central, broadcast_sources: DiscoveredBroadcastSources::new(), - scan_stream: Some(scan_result_stream), + broadcast_source_scan_started: Arc::new(AtomicBool::new(false)), } } @@ -130,17 +130,33 @@ /// poll. Upper layer can call methods on BroadcastAssistant based on the /// events it sees. pub fn start(&mut self) -> Result<EventStream<T>, Error> { - if self.scan_stream.is_none() { + if self.is_started() { return Err(Error::AlreadyStarted); } - Ok(EventStream::<T>::new(self.scan_stream.take().unwrap(), self.broadcast_sources.clone())) + let scan_result_stream = self.central.scan(&Self::scan_filters()); + self.broadcast_source_scan_started.store(true, Ordering::Relaxed); + Ok(EventStream::<T>::new( + scan_result_stream, + self.broadcast_sources.clone(), + self.broadcast_source_scan_started.clone(), + )) } - pub fn scan_for_scan_delegators(&mut self) -> T::ScanResultStream { + /// Returns whether or not Broadcast Assistant has started. + fn is_started(&self) -> bool { + self.broadcast_source_scan_started.load(Ordering::Relaxed) + } + + pub fn scan_for_scan_delegators(&mut self) -> Result<T::ScanResultStream, Error> { + if self.is_started() { + return Err(Error::Generic(format!( + "Cannot scan for scan delegators while scanning for broadcast sources" + ))); + } // Scan for service data with Broadcast Audio Scan Service UUID to look // for Broadcast Sink collocated with the Scan Delegator (see BAP spec v1.0.1 // Section 3.9.2 for details). - self.central.scan(&vec![Filter::HasServiceData(BROADCAST_AUDIO_SCAN_SERVICE).into()]) + Ok(self.central.scan(&vec![Filter::HasServiceData(BROADCAST_AUDIO_SCAN_SERVICE).into()])) } pub async fn connect_to_scan_delegator(&self, peer_id: PeerId) -> Result<Peer<T>, Error> @@ -172,17 +188,13 @@ &self, peer_id: PeerId, address: [u8; 6], - raw_address_type: u8, - raw_advertising_sid: u8, + address_type: bt_common::core::AddressType, + advertising_sid: bt_common::core::AdvertisingSetId, ) -> Result<BroadcastSource, Error> { - use bt_common::core::{AddressType, AdvertisingSetId}; let broadcast_source = BroadcastSource { address: Some(address), - address_type: Some( - AddressType::try_from(raw_address_type) - .map_err(|e| Error::Generic(e.to_string()))?, - ), - advertising_sid: Some(AdvertisingSetId(raw_advertising_sid)), + address_type: Some(address_type), + advertising_sid: Some(advertising_sid), broadcast_id: None, pa_interval: None, endpoint: None, @@ -196,27 +208,13 @@ pub fn force_discover_broadcast_source_metadata( &self, peer_id: PeerId, - raw_metadata: Vec<Vec<u8>>, + big_metadata: Vec<Vec<bt_common::generic_audio::metadata_ltv::Metadata>>, ) -> Result<BroadcastSource, Error> { use bt_bap::types::{BroadcastAudioSourceEndpoint, BroadcastIsochronousGroup}; - use bt_common::core::ltv::LtValue; use bt_common::core::CodecId; - use bt_common::generic_audio::metadata_ltv::Metadata; let mut big = Vec::new(); - for bytes in raw_metadata { - let metadata = { - if bytes.len() > 0 { - let (decoded_metadata, consumed_len) = Metadata::decode_all(bytes.as_slice()); - if consumed_len != bytes.len() { - return Err(Error::Generic("Metadata length is not valid".to_string())); - } - decoded_metadata.into_iter().filter_map(Result::ok).collect() - } else { - vec![] - } - }; - + for metadata in big_metadata { let group = BroadcastIsochronousGroup { codec_id: CodecId::Assigned(bt_common::core::CodingFormat::ALawLog), // mock. codec_specific_configs: vec![], @@ -261,6 +259,7 @@ use bt_bap::types::*; use bt_common::core::{AddressType, AdvertisingSetId}; + use bt_common::generic_audio::metadata_ltv::Metadata; use bt_gatt::test_utils::{FakeCentral, FakeClient, FakeTypes}; use crate::assistant::peer::tests::fake_bass_service; @@ -324,10 +323,16 @@ #[test] fn start_stream() { let mut assistant = BroadcastAssistant::<FakeTypes>::new(FakeCentral::new()); - let _ = assistant.start().expect("can start stream"); + let stream = assistant.start().expect("can start stream"); // Stream can only be started once. + assert!(assistant.is_started()); assert!(assistant.start().is_err()); + + // After the stream is dropped, it can be started again. + drop(stream); + assert!(!assistant.is_started()); + assert!(assistant.start().is_ok()); } #[test] @@ -349,4 +354,34 @@ }; let _ = res.expect("should be ok"); } + + #[test] + fn force_discover_broadcast_source_test() { + let assistant = BroadcastAssistant::<FakeTypes>::new(FakeCentral::new()); + let peer_id = PeerId(1); + let address = [1, 2, 3, 4, 5, 6]; + let address_type = AddressType::Public; + let sid = AdvertisingSetId(1); + + let source = + assistant.force_discover_broadcast_source(peer_id, address, address_type, sid).unwrap(); + + assert_eq!(source.address, Some(address)); + assert_eq!(source.address_type, Some(address_type)); + assert_eq!(source.advertising_sid, Some(sid)); + } + + #[test] + fn force_discover_broadcast_source_metadata_test() { + let assistant = BroadcastAssistant::<FakeTypes>::new(FakeCentral::new()); + let peer_id = PeerId(1); + let metadata = vec![vec![Metadata::BroadcastAudioImmediateRenderingFlag]]; + + let source = + assistant.force_discover_broadcast_source_metadata(peer_id, metadata.clone()).unwrap(); + + let endpoint = source.endpoint.unwrap(); + assert_eq!(endpoint.big.len(), 1); + assert_eq!(endpoint.big[0].metadata, metadata[0]); + } }
diff --git a/rust/bt-broadcast-assistant/src/assistant/event.rs b/rust/bt-broadcast-assistant/src/assistant/event.rs index 7290e47..741c04c 100644 --- a/rust/bt-broadcast-assistant/src/assistant/event.rs +++ b/rust/bt-broadcast-assistant/src/assistant/event.rs
@@ -4,18 +4,20 @@ use core::pin::Pin; use futures::stream::{FusedStream, Stream, StreamExt}; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::task::Poll; use bt_bap::types::{BroadcastAudioSourceEndpoint, BroadcastId}; -use bt_common::PeerId; +use bt_common::core::AdvertisingSetId; use bt_common::packet_encoding::Decodable; use bt_common::packet_encoding::Error as PacketError; +use bt_common::PeerId; use bt_gatt::central::{AdvertisingDatum, ScanResult}; use crate::assistant::{ - BASIC_AUDIO_ANNOUNCEMENT_SERVICE, BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE, - DiscoveredBroadcastSources, Error, + DiscoveredBroadcastSources, Error, BASIC_AUDIO_ANNOUNCEMENT_SERVICE, + BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE, }; use crate::types::BroadcastSource; @@ -33,17 +35,20 @@ terminated: bool, broadcast_sources: Arc<DiscoveredBroadcastSources>, + broadcast_source_scan_started: Arc<AtomicBool>, } impl<T: bt_gatt::GattTypes> EventStream<T> { pub(crate) fn new( scan_result_stream: T::ScanResultStream, broadcast_sources: Arc<DiscoveredBroadcastSources>, + broadcast_source_scan_started: Arc<AtomicBool>, ) -> Self { Self { scan_result_stream: Box::pin(scan_result_stream), terminated: false, broadcast_sources, + broadcast_source_scan_started, } } @@ -67,10 +72,19 @@ source.get_or_insert(BroadcastSource::default()).with_endpoint(base); } } + if let Some(src) = &mut source { + src.advertising_sid = Some(AdvertisingSetId(scan_result.advertising_sid)); + } Ok(source) } } +impl<T: bt_gatt::GattTypes> Drop for EventStream<T> { + fn drop(&mut self) { + self.broadcast_source_scan_started.store(false, Ordering::Relaxed); + } +} + impl<T: bt_gatt::GattTypes> FusedStream for EventStream<T> { fn is_terminated(&self) -> bool { self.terminated @@ -122,6 +136,7 @@ } None | Some(Err(_)) => { self.terminated = true; + self.broadcast_source_scan_started.store(false, Ordering::Relaxed); Poll::Ready(Some(Err(Error::CentralScanTerminated))) } } @@ -143,9 +158,14 @@ fn setup_stream() -> (EventStream<FakeTypes>, ScannedResultStream) { let fake_scan_result_stream = ScannedResultStream::new(); let broadcast_sources = DiscoveredBroadcastSources::new(); + let broadcast_source_scan_started = Arc::new(AtomicBool::new(false)); ( - EventStream::<FakeTypes>::new(fake_scan_result_stream.clone(), broadcast_sources), + EventStream::<FakeTypes>::new( + fake_scan_result_stream.clone(), + broadcast_sources, + broadcast_source_scan_started, + ), fake_scan_result_stream, ) } @@ -210,7 +230,7 @@ BASIC_AUDIO_ANNOUNCEMENT_SERVICE, base_data.clone(), )], - advertising_sid: 0, + advertising_sid: 1, })); // Expect the stream to send out broadcast source found event since information @@ -218,8 +238,9 @@ let Poll::Ready(Some(Ok(event))) = stream.poll_next_unpin(&mut noop_cx) else { panic!("should have received event"); }; - assert_matches!(event, Event::FoundBroadcastSource{peer, ..} => { - assert_eq!(peer, broadcast_source_pid) + assert_matches!(event, Event::FoundBroadcastSource{peer, source} => { + assert_eq!(peer, broadcast_source_pid); + assert_eq!(source.advertising_sid, Some(AdvertisingSetId(1))); }); assert!(stream.poll_next_unpin(&mut noop_cx).is_pending()); @@ -233,7 +254,7 @@ BASIC_AUDIO_ANNOUNCEMENT_SERVICE, base_data.clone(), )], - advertising_sid: 0, + advertising_sid: 1, })); // Shouldn't have gotten the event again since the information remained the
diff --git a/rust/bt-broadcast-assistant/src/assistant/peer.rs b/rust/bt-broadcast-assistant/src/assistant/peer.rs index 5c3b5b5..73029d8 100644 --- a/rust/bt-broadcast-assistant/src/assistant/peer.rs +++ b/rust/bt-broadcast-assistant/src/assistant/peer.rs
@@ -11,15 +11,16 @@ 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::client::BroadcastAudioScanServiceClient; #[cfg(any(test, feature = "debug"))] use bt_bass::types::BroadcastReceiveState; -use bt_bass::types::PaSync; +use bt_bass::types::{BisSync, PaSync}; use bt_common::core::PaInterval; use bt_common::packet_encoding::Error as PacketError; use bt_common::PeerId; #[cfg(any(test, feature = "debug"))] use bt_gatt::types::Handle; +use std::collections::HashMap; use crate::assistant::DiscoveredBroadcastSources; @@ -104,7 +105,7 @@ source_peer_id: PeerId, address_lookup: &impl GetPeerAddr, pa_sync: PaSync, - bis_sync: BigToBisSync, + bis_sync: HashMap<u8, BisSync>, ) -> Result<(), Error> { let mut broadcast_source = self .broadcast_sources @@ -148,7 +149,7 @@ &self, broadcast_id: BroadcastId, pa_sync: PaSync, - bis_sync: BigToBisSync, + bis_sync: HashMap<u8, BisSync>, ) -> Result<(), Error> { let pa_interval = self .broadcast_sources @@ -199,7 +200,7 @@ use assert_matches::assert_matches; use bt_gatt::pii::StaticPeerAddr; use futures::{pin_mut, FutureExt}; - use std::collections::HashSet; + use std::collections::HashMap; use std::task::Poll; use bt_common::core::{AddressType, AdvertisingSetId}; @@ -285,7 +286,7 @@ PeerId(1001), &FakeGetPeerAddr, PaSync::SyncPastUnavailable, - HashSet::new(), + HashMap::new(), ); pin_mut!(fut); let polled = fut.poll_unpin(&mut noop_cx); @@ -307,7 +308,7 @@ PeerId(1001), &address_lookup, PaSync::SyncPastUnavailable, - HashSet::new(), + HashMap::new(), ); pin_mut!(fut); let polled = fut.poll_unpin(&mut noop_cx); @@ -322,7 +323,7 @@ PeerId(1001), &address_lookup, PaSync::SyncPastUnavailable, - HashSet::new(), + HashMap::new(), ); pin_mut!(fut); let polled = fut.poll_unpin(&mut noop_cx);
diff --git a/rust/bt-broadcast-assistant/src/debug.rs b/rust/bt-broadcast-assistant/src/debug.rs index 6a4f308..1fe4f9e 100644 --- a/rust/bt-broadcast-assistant/src/debug.rs +++ b/rust/bt-broadcast-assistant/src/debug.rs
@@ -5,20 +5,26 @@ 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; -use bt_bass::types::PaSync; +use bt_bass::types::{BisSync, PaSync, SubgroupIndex}; + +#[cfg(any(test, feature = "debug"))] +use bt_common::core::ltv::LtValue; +#[cfg(any(test, feature = "debug"))] +use bt_common::core::{AddressType, AdvertisingSetId}; use bt_common::debug_command::CommandRunner; use bt_common::debug_command::CommandSet; use bt_common::gen_commandset; +#[cfg(any(test, feature = "debug"))] +use bt_common::generic_audio::metadata_ltv::Metadata; use bt_common::PeerId; use bt_gatt::pii::GetPeerAddr; +use std::collections::HashMap; use futures::stream::FusedStream; use futures::Future; use futures::Stream; use num::Num; use parking_lot::Mutex; -use std::collections::HashSet; use std::num::ParseIntError; use std::sync::Arc; @@ -33,13 +39,13 @@ Connect = ("connect", [], ["peer_id"], "Attempt connection to scan delegator"), Disconnect = ("disconnect", [], [], "Disconnect from connected scan delegator"), SendBroadcastCode = ("set-broadcast-code", [], ["broadcast_id", "broadcast_code"], "Attempt to send decryption key for a particular broadcast source to the scan delegator"), - AddBroadcastSource = ("add-broadcast-source", [], ["broadcast_source_pid", "pa_sync", "[bis_sync]"], "Attempt to add a particular broadcast source to the scan delegator"), - UpdatePaSync = ("update-pa-sync", [], ["broadcast_id", "pa_sync", "[bis_sync]"], "Attempt to update the scan delegator's desired pa sync to a particular broadcast source"), + AddBroadcastSource = ("add-broadcast-source", [], ["broadcast_source_pid", "PaSyncOff|PaSyncPast|PaSyncNoPast", "[bis_sync]"], "Attempt to add a particular broadcast source to the scan delegator"), + UpdatePaSync = ("update-pa-sync", [], ["broadcast_id", "PaSyncOff|PaSyncPast|PaSyncNoPast", "[bis_sync]"], "Attempt to update the scan delegator's desired pa sync to a particular broadcast source"), RemoveBroadcastSource = ("remove-broadcast-source", [], ["broadcast_id"], "Attempt to remove a particular broadcast source to the scan delegator"), RemoteScanStarted = ("inform-scan-started", [], [], "Inform the scan delegator that we have started scanning on behalf of it"), RemoteScanStopped = ("inform-scan-stopped", [], [], "Inform the scan delegator that we have stopped scanning on behalf of it"), // TODO(http://b/433285146): Once PA scanning is implemented, remove bottom 3 commands. - ForceDiscoverBroadcastSource = ("force-discover-broadcast-source", [], ["broadcast_source_pid", "address", "address_type", "advertising_sid"], "Force the broadcast assistant to become aware of the provided broadcast source"), + ForceDiscoverBroadcastSource = ("force-discover-broadcast-source", [], ["broadcast_source_pid", "address", "Public|Random", "advertising_sid"], "Force the broadcast assistant to become aware of the provided broadcast source"), ForceDiscoverSourceMetadata = ("force-discover-source-metadata", [], ["broadcast_source_pid", "comma_separated_raw_metadata"], "Force the broadcast assistant to become aware of the provided metadata, each BIG's metadata is comma separated"), ForceDiscoverEmptySourceMetadata = ("force-discover-empty-source-metadata", [], ["broadcast_source_pid", "num_big"], "Force the broadcast assistant to become aware of the provided empty metadata, as many as # BIGs specified"), } @@ -48,7 +54,6 @@ pub struct AssistantDebug<T: bt_gatt::GattTypes, R: GetPeerAddr> { assistant: BroadcastAssistant<T>, connected_peer: Mutex<Option<Arc<Peer<T>>>>, - started: bool, peer_addr_getter: R, } @@ -60,18 +65,16 @@ Self { assistant: BroadcastAssistant::<T>::new(central), connected_peer: Mutex::new(None), - started: false, peer_addr_getter, } } pub fn start(&mut self) -> Result<EventStream<T>, Error> { let event_stream = self.assistant.start()?; - self.started = true; Ok(event_stream) } - pub fn look_for_scan_delegators(&mut self) -> T::ScanResultStream { + pub fn look_for_scan_delegators(&mut self) -> Result<T::ScanResultStream, Error> { self.assistant.scan_for_scan_delegators() } @@ -118,7 +121,7 @@ } } -fn parse_peer_id(input: &str) -> Result<PeerId, String> { +pub fn parse_peer_id(input: &str) -> Result<PeerId, String> { let raw_id = match parse_int(input) { Err(_) => return Err(format!("falied to parse int from {input}")), Ok(i) => i, @@ -128,12 +131,14 @@ } #[cfg(any(test, feature = "debug"))] -fn parse_bd_addr(input: &str) -> Result<[u8; 6], String> { - let tokens: Vec<u8> = +/// Returns the bd address in little endian ordering. +pub fn parse_bd_addr(input: &str) -> Result<[u8; 6], String> { + let mut tokens: Vec<u8> = input.split(':').map(|t| u8::from_str_radix(t, 16)).filter_map(Result::ok).collect(); if tokens.len() != 6 { return Err(format!("failed to parse bd address from {input}")); } + tokens.reverse(); tokens.try_into().map_err(|e| format!("{e:?}")) } @@ -145,17 +150,30 @@ raw_id.try_into().map_err(|e| format!("{e:?}")) } -fn parse_bis_sync(input: &str) -> BigToBisSync { - input.split(',').filter_map(|t| { +fn parse_bis_sync(input: &str) -> HashMap<SubgroupIndex, BisSync> { + let mut map = HashMap::new(); + for t in input.split(',') { let parts: Vec<_> = t.split('-').collect(); if parts.len() != 2 { - eprintln!("invalid big-bis sync info {t}. should be in <Ith_BIG>-<BIS_INDEX> format, will be ignored"); - return None; + eprintln!( + "invalid big-bis sync info {t}. should be in <Ith_BIG>-<BIS_INDEX> format, will be ignored" + ); + continue; } - let ith_big = parse_int(parts[0]).ok()?; - let bis_index = parse_int(parts[1]).ok()?; - Some((ith_big, bis_index)) - }).collect() + let Ok(ith_big) = parse_int(parts[0]) else { + eprintln!("Failed to parse big index from '{}', ignoring.", parts[0]); + continue; + }; + let Ok(bis_index) = parse_int::<u8>(parts[1]) else { + eprintln!("Failed to parse bis index from '{}', ignoring.", parts[1]); + continue; + }; + let entry = map.entry(ith_big).or_insert(BisSync::no_sync()); + if let Err(e) = entry.synchronize_to_index(bis_index) { + eprintln!("Invalid BIS index: {e:?}"); + } + } + map } impl<T: bt_gatt::GattTypes + 'static, R: GetPeerAddr> CommandRunner for AssistantDebug<T, R> @@ -249,18 +267,16 @@ return Ok(()); }; - let pa_sync = match parse_int::<u8>(&args[1]) { - Ok(raw_val) if PaSync::try_from(raw_val).is_ok() => { - PaSync::try_from(raw_val).unwrap() - } - _ => { - eprintln!("invalid pa_sync: {}", args[1]); + let pa_sync: PaSync = match args[1].parse() { + Ok(sync) => sync, + Err(e) => { + eprintln!("invalid pa_sync: {e:?}"); return Ok(()); } }; let bis_sync = - if args.len() == 3 { parse_bis_sync(&args[2]) } else { HashSet::new() }; + if args.len() == 3 { parse_bis_sync(&args[2]) } else { HashMap::new() }; self.with_peer(|peer| async move { peer.add_broadcast_source( @@ -284,18 +300,16 @@ return Ok(()); }; - let pa_sync = match parse_int::<u8>(&args[1]) { - Ok(raw_val) if PaSync::try_from(raw_val).is_ok() => { - PaSync::try_from(raw_val).unwrap() - } - _ => { - eprintln!("invalid pa_sync: {}", args[1]); + let pa_sync: PaSync = match args[1].parse() { + Ok(sync) => sync, + Err(e) => { + eprintln!("invalid pa_sync: {e:?}"); return Ok(()); } }; let bis_sync = - if args.len() == 3 { parse_bis_sync(&args[2]) } else { HashSet::new() }; + if args.len() == 3 { parse_bis_sync(&args[2]) } else { HashMap::new() }; self.with_peer(|peer| async move { peer.update_broadcast_source_sync(broadcast_id, pa_sync, bis_sync).await @@ -319,8 +333,10 @@ .await; } AssistantCmd::RemoteScanStarted => { - self.with_peer(|peer| async move { peer.inform_remote_scan_started().await }) - .await; + self.with_peer(|peer: Arc<Peer<T>>| async move { + peer.inform_remote_scan_started().await + }) + .await; } AssistantCmd::RemoteScanStopped => { self.with_peer(|peer| async move { peer.inform_remote_scan_stopped().await }) @@ -346,21 +362,25 @@ return Ok(()); }; - let Ok(raw_addr_type) = parse_int::<u8>(&args[2]) else { - eprintln!("invalid address type: {}", args[2]); - return Ok(()); + let address_type: AddressType = match args[2].parse() { + Ok(t) => t, + Err(e) => { + eprintln!("invalid address type: {e:?}"); + return Ok(()); + } }; let Ok(raw_ad_sid) = parse_int::<u8>(&args[3]) else { eprintln!("invalid advertising sid: {}", args[3]); return Ok(()); }; + let advertising_sid = AdvertisingSetId(raw_ad_sid); match self.assistant.force_discover_broadcast_source( peer_id, address, - raw_addr_type, - raw_ad_sid, + address_type, + advertising_sid, ) { Ok(source) => { eprintln!("broadcast source after additional info: {source:?}") @@ -385,19 +405,32 @@ return Ok(()); }; - let mut raw_metadata = Vec::new(); + let mut all_big_metadata = Vec::new(); for i in 1..args.len() { - let ith_metadata: Vec<u8> = args[i] + let raw_metadata: Vec<u8> = args[i] .split(',') .map(|t| parse_int(t)) .filter_map(Result::ok) .collect(); - raw_metadata.push(ith_metadata); + + if raw_metadata.len() > 0 { + let (decoded_metadata, consumed_len) = + Metadata::decode_all(raw_metadata.as_slice()); + if consumed_len != raw_metadata.len() { + eprintln!("Metadata length is not valid"); + return Ok(()); + } + all_big_metadata.push( + decoded_metadata.into_iter().filter_map(Result::ok).collect(), + ); + } else { + all_big_metadata.push(vec![]); + } } match self .assistant - .force_discover_broadcast_source_metadata(peer_id, raw_metadata) + .force_discover_broadcast_source_metadata(peer_id, all_big_metadata) { Ok(source) => eprintln!("broadcast source with metadata: {source:?}"), Err(e) => eprintln!("failed to enter in broadcast source metadata: {e:?}"), @@ -423,14 +456,14 @@ return Ok(()); }; - let mut raw_metadata = Vec::new(); + let mut all_big_metadata = Vec::new(); for _i in 0..num_big { - raw_metadata.push(vec![]); + all_big_metadata.push(vec![]); } match self .assistant - .force_discover_broadcast_source_metadata(peer_id, raw_metadata) + .force_discover_broadcast_source_metadata(peer_id, all_big_metadata) { Ok(source) => eprintln!("broadcast source with metadata: {source:?}"), Err(e) => { @@ -465,7 +498,7 @@ fn test_parse_bd_addr() { assert_eq!( parse_bd_addr("3c:80:f1:ed:32:2c").expect("should be ok"), - [0x3c, 0x80, 0xf1, 0xed, 0x32, 0x2c] + [0x2c, 0x32, 0xed, 0xf1, 0x80, 0x3c] ); // Address with 5 parts is invalid. let _ = parse_bd_addr("3c:80:f1:ed:32").expect_err("should fail"); @@ -492,16 +525,14 @@ #[test] fn test_parse_bis_sync() { let bis_sync = parse_bis_sync("0-1,0-2,1-1"); - assert_eq!(bis_sync.len(), 3); - bis_sync.contains(&(0, 1)); - bis_sync.contains(&(0, 2)); - bis_sync.contains(&(1, 1)); + assert_eq!(bis_sync.len(), 2); + assert_eq!(bis_sync.get(&0), Some(&BisSync::sync(vec![1, 2]).unwrap())); + assert_eq!(bis_sync.get(&1), Some(&BisSync::sync(vec![1]).unwrap())); // Will ignore invalid values. let bis_sync = parse_bis_sync("0-1,0-2,1:1,1-1-1,"); - assert_eq!(bis_sync.len(), 2); - bis_sync.contains(&(0, 1)); - bis_sync.contains(&(0, 2)); + assert_eq!(bis_sync.len(), 1); + assert_eq!(bis_sync.get(&0), Some(&BisSync::sync(vec![1, 2]).unwrap())); let bis_sync = parse_bis_sync("hellothisistoallynotvalid"); assert_eq!(bis_sync.len(), 0);
diff --git a/rust/bt-broadcast-assistant/src/types.rs b/rust/bt-broadcast-assistant/src/types.rs index 35d6c6e..2cd8d96 100644 --- a/rust/bt-broadcast-assistant/src/types.rs +++ b/rust/bt-broadcast-assistant/src/types.rs
@@ -3,11 +3,11 @@ // found in the LICENSE file. use bt_bap::types::*; -use bt_bass::client::BigToBisSync; use bt_bass::types::{BigSubgroup, BisSync}; use bt_common::core::{Address, AddressType}; use bt_common::core::{AdvertisingSetId, PaInterval}; use bt_common::packet_encoding::Error as PacketError; +use std::collections::HashMap; /// Broadcast source data as advertised through Basic Audio Announcement /// PA and Broadcast Audio Announcement. @@ -27,12 +27,10 @@ /// Returns whether or not this BroadcastSource has enough information /// to be added by the Broadcast Assistant. pub(crate) fn into_add_source(&self) -> bool { - // PA interval is not necessary since default value can be used. - self.address.is_some() - && self.address_type.is_some() - && self.advertising_sid.is_some() - && self.broadcast_id.is_some() - && self.endpoint.is_some() + // Address and PA interval information are not necessary since + // default value can be used for PA interval and Address is looked up + // when the add source operation is triggered. + self.advertising_sid.is_some() && self.broadcast_id.is_some() && self.endpoint.is_some() } pub fn with_address(&mut self, address: [u8; 6]) -> &mut Self { @@ -95,7 +93,7 @@ /// preference value is used for all the BIGs pub(crate) fn endpoint_to_big_subgroups( &self, - bis_sync: BigToBisSync, + bis_sync: HashMap<u8, BisSync>, ) -> Result<Vec<BigSubgroup>, PacketError> { if self.endpoint.is_none() { return Err(PacketError::InvalidParameter( @@ -104,17 +102,9 @@ )); } let mut subgroups = Vec::new(); - let sync_map = bt_bass::client::big_to_bis_sync_indices(&bis_sync); 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); - bis_sync.set_sync(bis_indices)?; - bis_sync - } - _ => BisSync::default(), - }; + let bis_sync = bis_sync.get(&(big_index as u8)).cloned().unwrap_or_default(); subgroups.push(BigSubgroup::new(Some(bis_sync)).with_metadata(group.metadata.clone())); } Ok(subgroups) @@ -125,7 +115,7 @@ mod tests { use super::*; - use std::collections::HashSet; + use std::collections::HashMap; use bt_common::core::CodecId; use bt_common::generic_audio::metadata_ltv::Metadata; @@ -135,19 +125,16 @@ let mut b = BroadcastSource::default(); assert!(!b.into_add_source()); - 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()), - ); - + b.with_advertising_sid(AdvertisingSetId(0x1)); assert!(!b.into_add_source()); - b.endpoint_to_big_subgroups(HashSet::from([(0, 1)])) + + b.with_broadcast_id(BroadcastId::try_from(0x010203).unwrap()); + assert!(!b.into_add_source()); + + b.endpoint_to_big_subgroups(HashMap::from([(0, BisSync::sync(vec![1]).unwrap())])) .expect_err("should fail no endpoint data"); - b.merge(&BroadcastSource::default().with_endpoint(BroadcastAudioSourceEndpoint { + b.with_endpoint(BroadcastAudioSourceEndpoint { presentation_delay_ms: 0x010203, big: vec![BroadcastIsochronousGroup { codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Cvsd), @@ -158,15 +145,19 @@ 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"); + let subgroups = b + .endpoint_to_big_subgroups(HashMap::from([ + (0, BisSync::sync(vec![1]).unwrap()), + (1, BisSync::sync(vec![1]).unwrap()), + ])) + .expect("should succeed"); assert_eq!(subgroups.len(), 1); assert_eq!( subgroups[0], - BigSubgroup::new(Some(BisSync(0x00000001))) + BigSubgroup::new(Some(BisSync::sync(vec![1]).unwrap())) .with_metadata(vec![Metadata::BroadcastAudioImmediateRenderingFlag]) ); }
diff --git a/rust/bt-common/src/core.rs b/rust/bt-common/src/core.rs index db4a1d9..fee3dc7 100644 --- a/rust/bt-common/src/core.rs +++ b/rust/bt-common/src/core.rs
@@ -6,6 +6,7 @@ pub mod ltv; use crate::packet_encoding::{Encodable, Error as PacketError}; +use std::str::FromStr; /// Bluetooth Device Address that uniquely identifies the device /// to another Bluetooth device. @@ -36,6 +37,18 @@ } } +impl FromStr for AddressType { + type Err = PacketError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "Public" => Ok(AddressType::Public), + "Random" => Ok(AddressType::Random), + _ => Err(PacketError::InvalidParameter(format!("invalid address type: {s}"))), + } + } +} + /// Advertising Set ID which is 1 byte long. #[derive(Debug, Clone, Copy, PartialEq)] pub struct AdvertisingSetId(pub u8); @@ -218,6 +231,16 @@ use crate::packet_encoding::Decodable; use super::*; + use std::str::FromStr; + + #[test] + fn address_type_from_str() { + let addr_type = AddressType::from_str("Public").expect("should succeed"); + assert_eq!(addr_type, AddressType::Public); + let addr_type = AddressType::from_str("Random").expect("should succeed"); + assert_eq!(addr_type, AddressType::Random); + AddressType::from_str("invalid").expect_err("should fail"); + } #[test] fn encode_pa_interval() {