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() {