bt_broadcast_assistant/assistant/
peer.rs1use bt_gatt::pii::GetPeerAddr;
6use futures::stream::FusedStream;
7use futures::Stream;
8use std::sync::Arc;
9use thiserror::Error;
10
11use bt_bap::types::BroadcastId;
12use bt_bass::client::error::Error as BassClientError;
13use bt_bass::client::event::Event as BassEvent;
14use bt_bass::client::BroadcastAudioScanServiceClient;
15#[cfg(any(test, feature = "debug"))]
16use bt_bass::types::BroadcastReceiveState;
17use bt_bass::types::{BisSync, PaSync};
18use bt_common::core::{AdvertisingSetId, PeriodicAdvertisingInterval};
19use bt_common::packet_encoding::Error as PacketError;
20use bt_common::PeerId;
21#[cfg(any(test, feature = "debug"))]
22use bt_gatt::types::Handle;
23use std::collections::HashMap;
24
25use crate::assistant::DiscoveredBroadcastSources;
26
27#[derive(Debug, Error)]
28pub enum Error {
29 #[error("Failed to take event stream at Broadcast Audio Scan Service client")]
30 UnavailableBassEventStream,
31
32 #[error("Broadcast Audio Scan Service client error: {0:?}")]
33 BassClient(#[from] BassClientError),
34
35 #[error("Incomplete information for broadcast source with peer id ({0})")]
36 NotEnoughInfo(PeerId),
37
38 #[error("Broadcast source with peer id ({0}) does not exist")]
39 DoesNotExist(PeerId),
40
41 #[error("Failed to lookup address for peer id ({0}): {1:?}")]
42 AddressLookupError(PeerId, bt_gatt::types::Error),
43
44 #[error("Packet error: {0}")]
45 PacketError(#[from] PacketError),
46}
47
48pub struct Peer<T: bt_gatt::GattTypes> {
53 peer_id: PeerId,
54 _client: T::Client,
56 bass: BroadcastAudioScanServiceClient<T>,
57 broadcast_sources: Arc<DiscoveredBroadcastSources>,
59}
60
61impl<T: bt_gatt::GattTypes> Peer<T> {
62 pub(crate) fn new(
63 peer_id: PeerId,
64 client: T::Client,
65 bass: BroadcastAudioScanServiceClient<T>,
66 broadcast_sources: Arc<DiscoveredBroadcastSources>,
67 ) -> Self {
68 Peer { peer_id, _client: client, bass, broadcast_sources }
69 }
70
71 pub fn peer_id(&self) -> PeerId {
72 self.peer_id
73 }
74
75 pub fn take_event_stream(
78 &mut self,
79 ) -> Result<impl Stream<Item = Result<BassEvent, BassClientError>> + FusedStream, Error> {
80 self.bass.take_event_stream().ok_or(Error::UnavailableBassEventStream)
81 }
82
83 pub async fn send_broadcast_code(
85 &self,
86 broadcast_id: BroadcastId,
87 broadcast_code: [u8; 16],
88 ) -> Result<(), Error> {
89 self.bass.set_broadcast_code(broadcast_id, broadcast_code).await.map_err(Into::into)
90 }
91
92 pub async fn add_broadcast_source(
104 &self,
105 source_peer_id: PeerId,
106 advertising_sid: AdvertisingSetId,
107 address_lookup: &impl GetPeerAddr,
108 pa_sync: PaSync,
109 bis_sync: HashMap<u8, BisSync>,
110 ) -> Result<(), Error> {
111 let mut broadcast_source = self
112 .broadcast_sources
113 .get_by_key(source_peer_id, advertising_sid)
114 .ok_or(Error::DoesNotExist(source_peer_id))?;
115
116 let (broadcast_addr, broadcast_addr_type) = address_lookup
117 .get_peer_address(source_peer_id)
118 .await
119 .map_err(|err| Error::AddressLookupError(source_peer_id, err))?;
120 broadcast_source.with_address(broadcast_addr).with_address_type(broadcast_addr_type);
121
122 if !broadcast_source.is_ready_to_add() {
123 return Err(Error::NotEnoughInfo(source_peer_id));
124 }
125
126 self.bass
127 .add_broadcast_source(
128 broadcast_source.broadcast_id.unwrap(),
129 broadcast_source.address_type.unwrap(),
130 broadcast_source.address.unwrap(),
131 advertising_sid,
132 pa_sync,
133 broadcast_source
134 .periodic_advertising_interval
135 .unwrap_or(PeriodicAdvertisingInterval::unknown()),
136 broadcast_source.endpoint_to_big_subgroups(bis_sync).map_err(Error::PacketError)?,
137 )
138 .await
139 .map_err(Into::into)
140 }
141
142 pub async fn update_broadcast_source_sync(
152 &self,
153 broadcast_id: BroadcastId,
154 pa_sync: PaSync,
155 bis_sync: HashMap<u8, BisSync>,
156 ) -> Result<(), Error> {
157 let pa_interval = self
158 .broadcast_sources
159 .get_by_broadcast_id(&broadcast_id)
160 .map(|bs| bs.periodic_advertising_interval)
161 .unwrap_or(None);
162
163 self.bass
164 .modify_broadcast_source(broadcast_id, pa_sync, pa_interval, bis_sync, None)
165 .await
166 .map_err(Into::into)
167 }
168
169 pub async fn remove_broadcast_source(&self, broadcast_id: BroadcastId) -> Result<(), Error> {
176 self.bass.remove_broadcast_source(broadcast_id).await.map_err(Into::into)
177 }
178
179 pub async fn inform_remote_scan_started(&self) -> Result<(), Error> {
182 self.bass.remote_scan_started().await.map_err(Into::into)
183 }
184
185 pub async fn inform_remote_scan_stopped(&self) -> Result<(), Error> {
188 self.bass.remote_scan_stopped().await.map_err(Into::into)
189 }
190
191 #[cfg(any(test, feature = "debug"))]
194 pub fn get_broadcast_receive_states(&self) -> Vec<(Handle, BroadcastReceiveState)> {
195 self.bass.known_broadcast_sources()
196 }
197}
198
199#[cfg(test)]
200pub(crate) mod tests {
201 use super::*;
202
203 use assert_matches::assert_matches;
204 use bt_gatt::pii::StaticPeerAddr;
205 use futures::{pin_mut, FutureExt};
206 use std::collections::HashMap;
207 use std::task::Poll;
208
209 use bt_common::core::{AddressType, AdvertisingSetId};
210 use bt_gatt::test_utils::{FakeClient, FakeGetPeerAddr, FakePeerService, FakeTypes};
211 use bt_gatt::types::{
212 AttributePermissions, CharacteristicProperties, CharacteristicProperty, Handle,
213 };
214
215 use bt_gatt::Characteristic;
216
217 use crate::types::BroadcastSource;
218
219 const RECEIVE_STATE_HANDLE: Handle = Handle(0x11);
220 const AUDIO_SCAN_CONTROL_POINT_HANDLE: Handle = Handle(0x12);
221
222 pub(crate) fn fake_bass_service() -> FakePeerService {
223 let mut peer_service = FakePeerService::new();
224 peer_service.add_characteristic(
227 Characteristic {
228 handle: RECEIVE_STATE_HANDLE,
229 uuid: bt_bass::types::BROADCAST_RECEIVE_STATE_UUID,
230 properties: CharacteristicProperties(vec![
231 CharacteristicProperty::Broadcast,
232 CharacteristicProperty::Notify,
233 ]),
234 permissions: AttributePermissions::default(),
235 descriptors: vec![],
236 },
237 vec![],
238 );
239 peer_service.add_characteristic(
240 Characteristic {
241 handle: AUDIO_SCAN_CONTROL_POINT_HANDLE,
242 uuid: bt_bass::types::BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID,
243 properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]),
244 permissions: AttributePermissions::default(),
245 descriptors: vec![],
246 },
247 vec![],
248 );
249 peer_service
250 }
251
252 fn setup() -> (Peer<FakeTypes>, FakePeerService, Arc<DiscoveredBroadcastSources>) {
253 let peer_service = fake_bass_service();
254
255 let broadcast_sources = DiscoveredBroadcastSources::new();
256 (
257 Peer {
258 peer_id: PeerId(0x1),
259 _client: FakeClient::new(),
260 bass: BroadcastAudioScanServiceClient::<FakeTypes>::create_for_test(
261 peer_service.clone(),
262 Handle(0x1),
263 ),
264 broadcast_sources: broadcast_sources.clone(),
265 },
266 peer_service,
267 broadcast_sources,
268 )
269 }
270
271 #[test]
272 fn take_event_stream() {
273 let (mut peer, _peer_service, _broadcast_source) = setup();
274 let _event_stream = peer.take_event_stream().expect("should succeed");
275
276 assert!(peer.take_event_stream().is_err());
278 }
279
280 #[test]
281 fn add_broadcast_source_fail() {
282 let (peer, _peer_service, broadcast_source) = setup();
283
284 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
285
286 {
288 let fut = peer.add_broadcast_source(
289 PeerId(1001),
290 AdvertisingSetId(1),
291 &FakeGetPeerAddr,
292 PaSync::SyncPastUnavailable,
293 HashMap::new(),
294 );
295 pin_mut!(fut);
296 let polled = fut.poll_unpin(&mut noop_cx);
297 assert_matches!(polled, Poll::Ready(Err(Error::DoesNotExist(_))));
298 }
299
300 let _ = broadcast_source.merge_broadcast_source_data(
301 &(PeerId(1001), AdvertisingSetId(1)),
302 &BroadcastSource::default().with_broadcast_id(BroadcastId::try_from(1001).unwrap()),
303 );
304
305 {
307 let address_lookup =
308 StaticPeerAddr::new_for_peer(PeerId(1002), [1, 2, 3, 4, 5, 6], AddressType::Public);
309 let fut = peer.add_broadcast_source(
310 PeerId(1001),
311 AdvertisingSetId(1),
312 &address_lookup,
313 PaSync::SyncPastUnavailable,
314 HashMap::new(),
315 );
316 pin_mut!(fut);
317 let polled = fut.poll_unpin(&mut noop_cx);
318 assert_matches!(polled, Poll::Ready(Err(Error::AddressLookupError(_, _))));
319 }
320
321 {
323 let address_lookup =
324 StaticPeerAddr::new_for_peer(PeerId(1001), [1, 2, 3, 4, 5, 6], AddressType::Public);
325 let fut = peer.add_broadcast_source(
326 PeerId(1001),
327 AdvertisingSetId(1),
328 &address_lookup,
329 PaSync::SyncPastUnavailable,
330 HashMap::new(),
331 );
332 pin_mut!(fut);
333 let polled = fut.poll_unpin(&mut noop_cx);
334 assert_matches!(polled, Poll::Ready(Err(Error::NotEnoughInfo(_))));
335 }
336 }
337}