1pub mod error;
6pub mod event;
7
8use std::collections::HashMap;
9use std::sync::Arc;
10
11use futures::stream::{BoxStream, FusedStream, SelectAll, Stream, StreamExt};
12use futures::Future;
13use log::warn;
14use parking_lot::Mutex;
15
16use bt_bap::types::BroadcastId;
17use bt_common::core::{AddressType, AdvertisingSetId, PeriodicAdvertisingInterval};
18use bt_common::generic_audio::metadata_ltv::Metadata;
19use bt_common::packet_encoding::Decodable;
20use bt_gatt::client::{CharacteristicNotification, PeerService, ServiceCharacteristic};
21use bt_gatt::types::{Handle, WriteMode};
22
23use crate::client::error::{Error, ServiceError};
24use crate::client::event::*;
25use crate::types::*;
26
27const READ_CHARACTERISTIC_BUFFER_SIZE: usize = 255;
28
29#[derive(Default)]
38pub(crate) struct KnownBroadcastSources(HashMap<Handle, BroadcastReceiveState>);
39
40impl KnownBroadcastSources {
41 fn new(receive_states: HashMap<Handle, BroadcastReceiveState>) -> Self {
42 KnownBroadcastSources(receive_states)
43 }
44
45 fn update_state(
48 &mut self,
49 key: Handle,
50 value: BroadcastReceiveState,
51 ) -> Option<BroadcastReceiveState> {
52 self.0.insert(key, value)
53 }
54
55 fn source_id(&self, broadcast_id: &BroadcastId) -> Option<SourceId> {
62 let Some(state) = self.state(broadcast_id) else {
63 return None;
64 };
65 Some(state.source_id)
66 }
67
68 fn state(&self, broadcast_id: &BroadcastId) -> Option<&ReceiveState> {
71 self.0.iter().find_map(|(&_k, &ref v)| match v {
72 BroadcastReceiveState::Empty => None,
73 BroadcastReceiveState::NonEmpty(rs) => {
74 if rs.broadcast_id() == *broadcast_id {
75 return Some(rs);
76 }
77 None
78 }
79 })
80 }
81}
82
83pub struct BroadcastAudioScanServiceClient<T: bt_gatt::GattTypes> {
86 gatt_client: T::PeerService,
87 audio_scan_control_point: Handle,
91 broadcast_sources: Arc<Mutex<KnownBroadcastSources>>,
94 broadcast_codes: Arc<Mutex<HashMap<SourceId, [u8; 16]>>>,
97 notification_streams: Option<
99 SelectAll<BoxStream<'static, Result<CharacteristicNotification, bt_gatt::types::Error>>>,
100 >,
101}
102
103impl<T: bt_gatt::GattTypes> BroadcastAudioScanServiceClient<T> {
104 #[cfg(any(test, feature = "test-utils"))]
105 pub fn create_for_test(gatt_client: T::PeerService, audio_scan_control_point: Handle) -> Self {
106 Self {
107 gatt_client,
108 audio_scan_control_point,
109 broadcast_sources: Default::default(),
110 broadcast_codes: Arc::new(Mutex::new(HashMap::new())),
111 notification_streams: Some(SelectAll::new()),
112 }
113 }
114
115 pub async fn create(gatt_client: T::PeerService) -> Result<Self, Error>
116 where
117 <T as bt_gatt::GattTypes>::NotificationStream: std::marker::Send,
118 {
119 let bascp =
122 ServiceCharacteristic::<T>::find(&gatt_client, BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID)
123 .await
124 .map_err(|e| Error::Gatt(e))?;
125 if bascp.len() != 1 {
126 let err = if bascp.len() == 0 {
127 Error::Service(ServiceError::MissingCharacteristic)
128 } else {
129 Error::Service(ServiceError::ExtraScanControlPointCharacteristic)
130 };
131 return Err(err);
132 }
133 let bascp_handle = *bascp[0].handle();
134 let brs_chars = Self::discover_brs_characteristics(&gatt_client).await?;
135 let mut c = Self {
136 gatt_client,
137 audio_scan_control_point: bascp_handle,
138 broadcast_sources: Arc::new(Mutex::new(KnownBroadcastSources::new(brs_chars))),
139 broadcast_codes: Arc::new(Mutex::new(HashMap::new())),
140 notification_streams: None,
141 };
142 c.register_notifications();
143 Ok(c)
144 }
145
146 async fn discover_brs_characteristics(
150 gatt_client: &T::PeerService,
151 ) -> Result<HashMap<Handle, BroadcastReceiveState>, Error> {
152 let brs = ServiceCharacteristic::<T>::find(gatt_client, BROADCAST_RECEIVE_STATE_UUID)
153 .await
154 .map_err(|e| Error::Gatt(e))?;
155 if brs.len() == 0 {
156 return Err(Error::Service(ServiceError::MissingCharacteristic));
157 }
158 let mut brs_map = HashMap::new();
159 for c in brs {
160 let mut buf = vec![0; READ_CHARACTERISTIC_BUFFER_SIZE];
163 match c.read(&mut buf[..]).await {
164 Ok(read_bytes) => match BroadcastReceiveState::decode(&buf[0..read_bytes]).0 {
165 Ok(decoded) => {
166 brs_map.insert(*c.handle(), decoded);
167 continue;
168 }
169 Err(e) => warn!(
170 "Failed to decode characteristic ({:?}) to Broadcast Receive State value: {:?}",
171 *c.handle(),
172 e
173 ),
174 },
175 Err(e) => warn!("Failed to read characteristic ({:?}) value: {:?}", *c.handle(), e),
176 }
177 brs_map.insert(*c.handle(), BroadcastReceiveState::Empty);
178 }
179 Ok(brs_map)
180 }
181
182 fn register_notifications(&mut self)
183 where
184 <T as bt_gatt::GattTypes>::NotificationStream: std::marker::Send,
185 {
186 let mut notification_streams = SelectAll::new();
187 {
188 let lock = self.broadcast_sources.lock();
189 for handle in lock.0.keys() {
190 let stream = self.gatt_client.subscribe(&handle);
191 notification_streams.push(stream.boxed());
192 }
193 }
194 self.notification_streams = Some(notification_streams);
195 }
196
197 pub fn take_event_stream(
204 &mut self,
205 ) -> Option<impl Stream<Item = Result<Event, Error>> + FusedStream> {
206 let notification_streams = self.notification_streams.take();
207 let Some(streams) = notification_streams else {
208 return None;
209 };
210 let event_stream = EventStream::new(streams, self.broadcast_sources.clone());
211 Some(event_stream)
212 }
213
214 fn write_to_bascp(
217 &self,
218 op: impl ControlPointOperation,
219 ) -> impl Future<Output = Result<(), Error>> + '_ {
220 let handle = self.audio_scan_control_point;
221 let mut buf = vec![0; op.encoded_len()];
222 let encode_res = op.encode(&mut buf[..]);
223 async move {
224 match encode_res {
225 Err(e) => Err(Error::Packet(e)),
226 Ok(_) => self
227 .gatt_client
228 .write_characteristic(&handle, WriteMode::WithoutResponse, 0, buf.as_slice())
229 .await
230 .map_err(|e| Error::Gatt(e)),
231 }
232 }
233 }
234
235 fn get_source_id(&self, broadcast_id: &BroadcastId) -> Result<SourceId, Error> {
236 self.broadcast_sources
237 .lock()
238 .source_id(broadcast_id)
239 .ok_or(Error::UnknownBroadcastSource(*broadcast_id))
240 }
241
242 fn get_broadcast_source_state(&self, broadcast_id: &BroadcastId) -> Option<ReceiveState> {
245 let lock = self.broadcast_sources.lock();
246 lock.state(broadcast_id).clone().map(|rs| rs.clone())
247 }
248
249 pub async fn remote_scan_started(&self) -> Result<(), Error> {
254 let op = RemoteScanStartedOperation;
255 self.write_to_bascp(op).await
256 }
257
258 pub async fn remote_scan_stopped(&self) -> Result<(), Error> {
261 let op = RemoteScanStoppedOperation;
262 self.write_to_bascp(op).await
263 }
264
265 pub async fn add_broadcast_source(
267 &self,
268 broadcast_id: BroadcastId,
269 address_type: AddressType,
270 advertiser_address: [u8; ADDRESS_BYTE_SIZE],
271 sid: AdvertisingSetId,
272 pa_sync: PaSync,
273 pa_interval: PeriodicAdvertisingInterval,
274 subgroups: Vec<BigSubgroup>,
275 ) -> Result<(), Error> {
276 let op = AddSourceOperation::new(
277 address_type,
278 advertiser_address,
279 sid,
280 broadcast_id,
281 pa_sync,
282 pa_interval,
283 subgroups,
284 );
285 self.write_to_bascp(op).await
286 }
287
288 pub async fn modify_broadcast_source(
309 &self,
310 broadcast_id: BroadcastId,
311 pa_sync: PaSync,
312 pa_interval: Option<PeriodicAdvertisingInterval>,
313 bis_map: HashMap<SubgroupIndex, BisSync>,
314 metadata_map: Option<HashMap<SubgroupIndex, Vec<Metadata>>>,
315 ) -> Result<(), Error> {
316 let mut state = self
317 .get_broadcast_source_state(&broadcast_id)
318 .ok_or(Error::UnknownBroadcastSource(broadcast_id))?;
319
320 for (big_index, group) in state.subgroups.iter_mut().enumerate() {
322 if let Some(bis_sync) = bis_map.get(&(big_index as u8)) {
323 group.bis_sync = bis_sync.clone();
324 }
325 }
326
327 if let Some(mut m) = metadata_map {
329 for (big_index, group) in state.subgroups.iter_mut().enumerate() {
330 if let Some(metadata) = m.remove(&(big_index as u8)) {
331 group.metadata = metadata;
332 }
333 }
334
335 let mut new_big_indices: Vec<&u8> = m.keys().collect();
339 new_big_indices.sort();
340 for big_index in new_big_indices {
341 if (*big_index as usize) != state.subgroups.len() {
342 warn!("cannot add new [{big_index}th] subgroup");
343 break;
344 }
345 let new_subgroup = BigSubgroup::new(None).with_metadata(m[big_index].clone());
346 state.subgroups.push(new_subgroup);
347 }
348 }
349
350 let op = ModifySourceOperation::new(
351 state.source_id,
352 pa_sync,
353 pa_interval.unwrap_or(PeriodicAdvertisingInterval::unknown()),
354 state.subgroups,
355 );
356 self.write_to_bascp(op).await
357 }
358
359 pub async fn remove_broadcast_source(&self, broadcast_id: BroadcastId) -> Result<(), Error> {
360 let source_id = self.get_source_id(&broadcast_id)?;
361
362 let op = RemoveSourceOperation::new(source_id);
363 self.write_to_bascp(op).await
364 }
365
366 pub async fn set_broadcast_code(
368 &self,
369 broadcast_id: BroadcastId,
370 broadcast_code: [u8; 16],
371 ) -> Result<(), Error> {
372 let source_id = self.get_source_id(&broadcast_id)?;
373
374 let op = SetBroadcastCodeOperation::new(source_id, broadcast_code.clone());
375 self.write_to_bascp(op).await?;
376
377 self.broadcast_codes.lock().insert(source_id, broadcast_code);
379 Ok(())
380 }
381
382 pub fn known_broadcast_sources(&self) -> Vec<(Handle, BroadcastReceiveState)> {
385 let lock = self.broadcast_sources.lock();
386 let mut brs = Vec::new();
387 for (k, v) in lock.0.iter() {
388 brs.push((*k, v.clone()));
389 }
390 brs
391 }
392
393 #[cfg(any(test, feature = "test-utils"))]
394 pub fn insert_broadcast_receive_state(&mut self, handle: Handle, brs: BroadcastReceiveState) {
395 self.broadcast_sources.lock().update_state(handle, brs);
396 }
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402
403 use std::task::Poll;
404
405 use assert_matches::assert_matches;
406 use futures::executor::block_on;
407 use futures::{pin_mut, FutureExt};
408
409 use bt_common::core::AdvertisingSetId;
410 use bt_common::Uuid;
411 use bt_gatt::test_utils::*;
412 use bt_gatt::types::{
413 AttributePermissions, CharacteristicProperties, CharacteristicProperty, Handle,
414 };
415 use bt_gatt::Characteristic;
416
417 const RECEIVE_STATE_1_HANDLE: Handle = Handle(1);
418 const RECEIVE_STATE_2_HANDLE: Handle = Handle(2);
419 const RECEIVE_STATE_3_HANDLE: Handle = Handle(3);
420 const RANDOME_CHAR_HANDLE: Handle = Handle(4);
421 const AUDIO_SCAN_CONTROL_POINT_HANDLE: Handle = Handle(5);
422
423 fn setup_client() -> (BroadcastAudioScanServiceClient<FakeTypes>, FakePeerService) {
424 let mut fake_peer_service = FakePeerService::new();
425 fake_peer_service.add_characteristic(
428 Characteristic {
429 handle: RECEIVE_STATE_1_HANDLE,
430 uuid: BROADCAST_RECEIVE_STATE_UUID,
431 properties: CharacteristicProperties(vec![
432 CharacteristicProperty::Broadcast,
433 CharacteristicProperty::Notify,
434 ]),
435 permissions: AttributePermissions::default(),
436 descriptors: vec![],
437 },
438 vec![],
439 );
440 fake_peer_service.add_characteristic(
441 Characteristic {
442 handle: RECEIVE_STATE_2_HANDLE,
443 uuid: BROADCAST_RECEIVE_STATE_UUID,
444 properties: CharacteristicProperties(vec![
445 CharacteristicProperty::Broadcast,
446 CharacteristicProperty::Notify,
447 ]),
448 permissions: AttributePermissions::default(),
449 descriptors: vec![],
450 },
451 vec![],
452 );
453 fake_peer_service.add_characteristic(
454 Characteristic {
455 handle: RECEIVE_STATE_3_HANDLE,
456 uuid: BROADCAST_RECEIVE_STATE_UUID,
457 properties: CharacteristicProperties(vec![
458 CharacteristicProperty::Broadcast,
459 CharacteristicProperty::Notify,
460 ]),
461 permissions: AttributePermissions::default(),
462 descriptors: vec![],
463 },
464 vec![],
465 );
466 fake_peer_service.add_characteristic(
467 Characteristic {
468 handle: RANDOME_CHAR_HANDLE,
469 uuid: Uuid::from_u16(0x1234),
470 properties: CharacteristicProperties(vec![CharacteristicProperty::Notify]),
471 permissions: AttributePermissions::default(),
472 descriptors: vec![],
473 },
474 vec![],
475 );
476 fake_peer_service.add_characteristic(
477 Characteristic {
478 handle: AUDIO_SCAN_CONTROL_POINT_HANDLE,
479 uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID,
480 properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]),
481 permissions: AttributePermissions::default(),
482 descriptors: vec![],
483 },
484 vec![],
485 );
486
487 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
488 let create_result =
489 BroadcastAudioScanServiceClient::<FakeTypes>::create(fake_peer_service.clone());
490 pin_mut!(create_result);
491 let polled = create_result.poll_unpin(&mut noop_cx);
492 let Poll::Ready(Ok(client)) = polled else {
493 panic!("Expected BroadcastAudioScanServiceClient to be succesfully created");
494 };
495
496 (client, fake_peer_service)
497 }
498
499 #[test]
500 fn create_client() {
501 let (client, _) = setup_client();
502
503 assert_eq!(client.audio_scan_control_point, AUDIO_SCAN_CONTROL_POINT_HANDLE);
505 let broadcast_sources = client.known_broadcast_sources();
506 assert_eq!(broadcast_sources.len(), 3);
507 assert!(broadcast_sources.iter().find(|v| v.0 == RECEIVE_STATE_1_HANDLE).is_some());
508 assert!(broadcast_sources.iter().find(|v| v.0 == RECEIVE_STATE_2_HANDLE).is_some());
509 assert!(broadcast_sources.iter().find(|v| v.0 == RECEIVE_STATE_3_HANDLE).is_some());
510 }
511
512 #[test]
513 fn create_client_fails_missing_characteristics() {
514 let mut fake_peer_service = FakePeerService::new();
516 fake_peer_service.add_characteristic(
517 Characteristic {
518 handle: Handle(1),
519 uuid: BROADCAST_RECEIVE_STATE_UUID,
520 properties: CharacteristicProperties(vec![
521 CharacteristicProperty::Broadcast,
522 CharacteristicProperty::Notify,
523 ]),
524 permissions: AttributePermissions::default(),
525 descriptors: vec![],
526 },
527 vec![],
528 );
529
530 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
531 let create_result =
532 BroadcastAudioScanServiceClient::<FakeTypes>::create(fake_peer_service.clone());
533
534 pin_mut!(create_result);
535 let polled = create_result.poll_unpin(&mut noop_cx);
536 let Poll::Ready(Err(_)) = polled else {
537 panic!("Expected BroadcastAudioScanServiceClient to have failed");
538 };
539
540 let mut fake_peer_service: FakePeerService = FakePeerService::new();
542 fake_peer_service.add_characteristic(
543 Characteristic {
544 handle: Handle(1),
545 uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID,
546 properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]),
547 permissions: AttributePermissions::default(),
548 descriptors: vec![],
549 },
550 vec![],
551 );
552
553 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
554 let create_result =
555 BroadcastAudioScanServiceClient::<FakeTypes>::create(fake_peer_service.clone());
556 pin_mut!(create_result);
557 let polled = create_result.poll_unpin(&mut noop_cx);
558 let Poll::Ready(Err(_)) = polled else {
559 panic!("Expected BroadcastAudioScanServiceClient to have failed");
560 };
561 }
562
563 #[test]
564 fn create_client_fails_duplicate_characteristics() {
565 let mut fake_peer_service = FakePeerService::new();
567 fake_peer_service.add_characteristic(
568 Characteristic {
569 handle: Handle(1),
570 uuid: BROADCAST_RECEIVE_STATE_UUID,
571 properties: CharacteristicProperties(vec![
572 CharacteristicProperty::Broadcast,
573 CharacteristicProperty::Notify,
574 ]),
575 permissions: AttributePermissions::default(),
576 descriptors: vec![],
577 },
578 vec![],
579 );
580 fake_peer_service.add_characteristic(
581 Characteristic {
582 handle: Handle(2),
583 uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID,
584 properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]),
585 permissions: AttributePermissions::default(),
586 descriptors: vec![],
587 },
588 vec![],
589 );
590 fake_peer_service.add_characteristic(
591 Characteristic {
592 handle: Handle(3),
593 uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID,
594 properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]),
595 permissions: AttributePermissions::default(),
596 descriptors: vec![],
597 },
598 vec![],
599 );
600
601 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
602 let create_result =
603 BroadcastAudioScanServiceClient::<FakeTypes>::create(fake_peer_service.clone());
604 pin_mut!(create_result);
605 let polled = create_result.poll_unpin(&mut noop_cx);
606 let Poll::Ready(Err(_)) = polled else {
607 panic!("Expected BroadcastAudioScanServiceClient to have failed");
608 };
609 }
610
611 #[test]
612 fn start_event_stream() {
613 let (mut client, mut fake_peer_service) = setup_client();
614 let mut event_stream = client.take_event_stream().expect("stream was created");
615
616 #[rustfmt::skip]
619 fake_peer_service.add_characteristic(
620 Characteristic {
621 handle: RECEIVE_STATE_2_HANDLE,
622 uuid: BROADCAST_RECEIVE_STATE_UUID,
623 properties: CharacteristicProperties(vec![
624 CharacteristicProperty::Broadcast,
625 CharacteristicProperty::Notify,
626 ]),
627 permissions: AttributePermissions::default(),
628 descriptors: vec![],
629 },
630 vec![
631 0x02, AddressType::Public as u8, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x02, 0x03, 0x04, PaSyncState::Synced as u8,
635 EncryptionStatus::BroadcastCodeRequired.raw_value(),
636 0x00, ],
638 );
639
640 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
642
643 let recv_fut = event_stream.select_next_some();
644 let event = block_on(recv_fut).expect("should receive event");
645 assert_eq!(
646 event,
647 Event::AddedBroadcastSource(
648 BroadcastId::try_from(0x040302).unwrap(),
649 PaSyncState::Synced,
650 EncryptionStatus::BroadcastCodeRequired
651 )
652 );
653
654 assert!(event_stream.poll_next_unpin(&mut noop_cx).is_pending());
656
657 #[rustfmt::skip]
661 fake_peer_service.add_characteristic(
662 Characteristic {
663 handle: RECEIVE_STATE_3_HANDLE,
664 uuid: BROADCAST_RECEIVE_STATE_UUID,
665 properties: CharacteristicProperties(vec![
666 CharacteristicProperty::Broadcast,
667 CharacteristicProperty::Notify,
668 ]),
669 permissions: AttributePermissions::default(),
670 descriptors: vec![],
671 },
672 vec![
673 0x03, AddressType::Public as u8, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x03, 0x04, 0x05, PaSyncState::SyncInfoRequest as u8,
677 EncryptionStatus::NotEncrypted.raw_value(),
678 0x00, ],
680 );
681
682 let recv_fut = event_stream.select_next_some();
683 let event = block_on(recv_fut).expect("should receive event");
684 assert_eq!(
685 event,
686 Event::AddedBroadcastSource(
687 BroadcastId::try_from(0x050403).unwrap(),
688 PaSyncState::SyncInfoRequest,
689 EncryptionStatus::NotEncrypted
690 )
691 );
692
693 assert!(event_stream.poll_next_unpin(&mut noop_cx).is_pending());
695 }
696
697 #[test]
698 fn remote_scan_started() {
699 let (client, mut fake_peer_service) = setup_client();
700
701 fake_peer_service.expect_characteristic_value(&AUDIO_SCAN_CONTROL_POINT_HANDLE, vec![0x01]);
702
703 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
704 let op_fut = client.remote_scan_started();
705 pin_mut!(op_fut);
706 let polled = op_fut.poll_unpin(&mut noop_cx);
707 assert_matches!(polled, Poll::Ready(Ok(_)));
708 }
709
710 #[test]
711 fn remote_scan_stopped() {
712 let (client, mut fake_peer_service) = setup_client();
713
714 fake_peer_service.expect_characteristic_value(&AUDIO_SCAN_CONTROL_POINT_HANDLE, vec![0x00]);
715
716 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
717 let op_fut = client.remote_scan_stopped();
718 pin_mut!(op_fut);
719 let polled = op_fut.poll_unpin(&mut noop_cx);
720 assert_matches!(polled, Poll::Ready(Ok(_)));
721 }
722
723 #[test]
724 fn add_broadcast_source() {
725 let (client, mut fake_peer_service) = setup_client();
726
727 fake_peer_service.expect_characteristic_value(
728 &AUDIO_SCAN_CONTROL_POINT_HANDLE,
729 vec![
730 0x02, 0x00, 0x04, 0x10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x11, 0x00, 0x00, 0x00, 0xFF,
731 0xFF, 0x00,
732 ],
733 );
734
735 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
736 let op_fut = client.add_broadcast_source(
737 BroadcastId::try_from(0x11).unwrap(),
738 AddressType::Public,
739 [0x04, 0x10, 0x00, 0x00, 0x00, 0x00],
740 AdvertisingSetId(1),
741 PaSync::DoNotSync,
742 PeriodicAdvertisingInterval::unknown(),
743 vec![],
744 );
745 pin_mut!(op_fut);
746 let polled = op_fut.poll_unpin(&mut noop_cx);
747 assert_matches!(polled, Poll::Ready(Ok(_)));
748 }
749
750 #[test]
751 fn modify_broadcast_source() {
752 let (client, mut fake_peer_service) = setup_client();
753
754 client.broadcast_sources.lock().update_state(
757 RECEIVE_STATE_1_HANDLE,
758 BroadcastReceiveState::NonEmpty(ReceiveState {
759 source_id: 0x11,
760 source_address_type: AddressType::Public,
761 source_address: [1, 2, 3, 4, 5, 6],
762 source_adv_sid: AdvertisingSetId(1),
763 broadcast_id: BroadcastId::try_from(0x11).unwrap(),
764 pa_sync_state: PaSyncState::Synced,
765 big_encryption: EncryptionStatus::BroadcastCodeRequired,
766 subgroups: vec![],
767 }),
768 );
769
770 #[rustfmt::skip]
771 fake_peer_service.expect_characteristic_value(
772 &AUDIO_SCAN_CONTROL_POINT_HANDLE,
773 vec![
774 0x03, 0x11, 0x00, 0xAA, 0xAA, 0x00, ],
777 );
778
779 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
780 let op_fut = client.modify_broadcast_source(
781 BroadcastId::try_from(0x11).unwrap(),
782 PaSync::DoNotSync,
783 Some(PeriodicAdvertisingInterval(0xAAAA)),
784 HashMap::new(),
785 None,
786 );
787 pin_mut!(op_fut);
788 let polled = op_fut.poll_unpin(&mut noop_cx);
789 assert_matches!(polled, Poll::Ready(Ok(_)));
790 }
791
792 #[test]
793 fn modify_broadcast_source_updates_groups() {
794 let (client, mut fake_peer_service) = setup_client();
795
796 client.broadcast_sources.lock().update_state(
799 RECEIVE_STATE_1_HANDLE,
800 BroadcastReceiveState::NonEmpty(ReceiveState {
801 source_id: 0x11,
802 source_address_type: AddressType::Public,
803 source_address: [1, 2, 3, 4, 5, 6],
804 source_adv_sid: AdvertisingSetId(1),
805 broadcast_id: BroadcastId::try_from(0x11).unwrap(),
806 pa_sync_state: PaSyncState::Synced,
807 big_encryption: EncryptionStatus::BroadcastCodeRequired,
808 subgroups: vec![BigSubgroup::new(None)],
809 }),
810 );
811
812 #[rustfmt::skip]
815 fake_peer_service.expect_characteristic_value(
816 &AUDIO_SCAN_CONTROL_POINT_HANDLE,
817 vec![
818 0x03, 0x11, 0x00, 0xFF, 0xFF, 0x02, 0x15, 0x00, 0x00, 0x00, 0x02, 0x01, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0x05, 0x04, 0x04, 0x65, 0x6E, 0x67, ],
825 );
826
827 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
828 let op_fut = client.modify_broadcast_source(
829 BroadcastId::try_from(0x11).unwrap(),
830 PaSync::DoNotSync,
831 None,
832 HashMap::from([(0, BisSync::sync(vec![1, 3, 5]).unwrap())]),
833 Some(HashMap::from([
834 (0, vec![Metadata::BroadcastAudioImmediateRenderingFlag]),
835 (1, vec![Metadata::Language("eng".to_string())]),
836 (5, vec![Metadata::ProgramInfoURI("this subgroup shouldn't be added".to_string())]),
837 ])),
838 );
839 pin_mut!(op_fut);
840 let polled: Poll<Result<(), Error>> = op_fut.poll_unpin(&mut noop_cx);
841 assert_matches!(polled, Poll::Ready(Ok(_)));
842 }
843
844 #[test]
845 fn modify_broadcast_source_fail() {
846 let (client, _fake_peer_service) = setup_client();
847
848 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
849 let op_fut = client.modify_broadcast_source(
851 BroadcastId::try_from(0x11).unwrap(),
852 PaSync::DoNotSync,
853 None,
854 HashMap::new(),
855 None,
856 );
857 pin_mut!(op_fut);
858 let polled = op_fut.poll_unpin(&mut noop_cx);
859 assert_matches!(polled, Poll::Ready(Err(_)));
860 }
861
862 #[test]
863 fn remove_broadcast_source() {
864 let (client, mut fake_peer_service) = setup_client();
865 let bid = BroadcastId::try_from(0x11).expect("should not fail");
866
867 client.broadcast_sources.lock().update_state(
870 RECEIVE_STATE_1_HANDLE,
871 BroadcastReceiveState::NonEmpty(ReceiveState {
872 source_id: 0x11,
873 source_address_type: AddressType::Public,
874 source_address: [1, 2, 3, 4, 5, 6],
875 source_adv_sid: AdvertisingSetId(1),
876 broadcast_id: bid,
877 pa_sync_state: PaSyncState::Synced,
878 big_encryption: EncryptionStatus::BroadcastCodeRequired,
879 subgroups: vec![],
880 }),
881 );
882
883 fake_peer_service
884 .expect_characteristic_value(&AUDIO_SCAN_CONTROL_POINT_HANDLE, vec![0x05, 0x11]);
885
886 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
887 let op_fut = client.remove_broadcast_source(bid);
889 pin_mut!(op_fut);
890 let polled = op_fut.poll_unpin(&mut noop_cx);
891 assert_matches!(polled, Poll::Ready(Ok(_)));
892 }
893
894 #[test]
895 fn remove_broadcast_source_fail() {
896 let (client, _fake_peer_service) = setup_client();
897
898 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
899 let op_fut = client.remove_broadcast_source(BroadcastId::try_from(0x11).unwrap());
901 pin_mut!(op_fut);
902 let polled = op_fut.poll_unpin(&mut noop_cx);
903 assert_matches!(polled, Poll::Ready(Err(_)));
904 }
905
906 #[test]
907 fn set_broadcast_code() {
908 let (client, mut fake_peer_service) = setup_client();
909
910 client.broadcast_sources.lock().update_state(
913 RECEIVE_STATE_1_HANDLE,
914 BroadcastReceiveState::NonEmpty(ReceiveState {
915 source_id: 0x01,
916 source_address_type: AddressType::Public,
917 source_address: [1, 2, 3, 4, 5, 6],
918 source_adv_sid: AdvertisingSetId(1),
919 broadcast_id: BroadcastId::try_from(0x030201).unwrap(),
920 pa_sync_state: PaSyncState::Synced,
921 big_encryption: EncryptionStatus::BroadcastCodeRequired,
922 subgroups: vec![],
923 }),
924 );
925
926 fake_peer_service.expect_characteristic_value(
927 &AUDIO_SCAN_CONTROL_POINT_HANDLE,
928 vec![0x04, 0x01, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
929 );
930
931 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
932 let set_code_fut =
933 client.set_broadcast_code(BroadcastId::try_from(0x030201).unwrap(), [1; 16]);
934 pin_mut!(set_code_fut);
935 let polled = set_code_fut.poll_unpin(&mut noop_cx);
936 assert_matches!(polled, Poll::Ready(Ok(_)));
937 }
938
939 #[test]
940 fn set_broadcast_code_fails() {
941 let (client, _) = setup_client();
942
943 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
944 let set_code_fut =
945 client.set_broadcast_code(BroadcastId::try_from(0x030201).unwrap(), [1; 16]);
946 pin_mut!(set_code_fut);
947 let polled = set_code_fut.poll_unpin(&mut noop_cx);
948
949 assert_matches!(polled, Poll::Ready(Err(_)));
952 }
953}