bt_bass/
client.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5pub 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, PaInterval};
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/// Keeps track of Source_ID and Broadcast_ID that are associated together.
30/// Source_ID is assigned by the BASS server to a Broadcast Receive State
31/// characteristic. If the remote peer with the BASS server autonomously
32/// synchronized to a PA or accepted the Add Source operation, the server
33/// selects an empty Broadcast Receive State characteristic to update or deletes
34/// one of the existing one to update. However, because the concept of Source_ID
35/// is unqiue to BASS, we track the Broadcast_ID that a Source_ID is associated
36/// so that it can be used by upper layers.
37#[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    /// Updates the value of the specified broadcast receive state
46    /// characteristic. Returns the old value if it existed.
47    fn update_state(
48        &mut self,
49        key: Handle,
50        value: BroadcastReceiveState,
51    ) -> Option<BroadcastReceiveState> {
52        self.0.insert(key, value)
53    }
54
55    /// Given the broadcast ID, find the corresponding source ID.
56    /// Returns none if the server doesn't know the specified broadcast source
57    /// because a) the broadcast source was never added or discovered; or,
58    /// b) the broadcast source was removed from remove operation; or,
59    /// c) the broadcast source was removed by the server to add a different
60    ///    broadcast source.
61    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    /// Gets the last updated broadcast receive state value.
69    /// Returns none if the server doesn't know the specified broadcast source.
70    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
83/// Manages connection to the Broadcast Audio Scan Service at the
84/// remote Scan Delegator and writes/reads characteristics to/from it.
85pub struct BroadcastAudioScanServiceClient<T: bt_gatt::GattTypes> {
86    gatt_client: T::PeerService,
87    /// Broadcast Audio Scan Service only has one Broadcast Audio Scan Control
88    /// Point characteristic according to BASS Section 3. There shall
89    /// be one or more Broadcast Receive State characteristics.
90    audio_scan_control_point: Handle,
91    /// Broadcast Receive State characteristics can be used to determine the
92    /// BASS status.
93    broadcast_sources: Arc<Mutex<KnownBroadcastSources>>,
94    /// Keeps track of the broadcast codes that were sent to the remote BASS
95    /// server.
96    broadcast_codes: Arc<Mutex<HashMap<SourceId, [u8; 16]>>>,
97    // GATT notification streams for BRS characteristic value changes.
98    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        // BASS server should have a single Broadcast Audio Scan Control Point
120        // Characteristic.
121        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    // Discover all the Broadcast Receive State characteristics.
147    // On success, returns the HashMap of all Broadcast Received State
148    // Characteristics.
149    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            // Read the value of the Broadcast Recieve State at the time of discovery for
161            // record.
162            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    /// Returns a stream that can be used by the upper layer to poll for
198    /// BroadcastAudioScanServiceEvent. BroadcastAudioScanServiceEvents are
199    /// generated based on BRS characteristic change received from GATT
200    /// notification that are processed by BroadcastAudioScanServiceClient.
201    /// This method should only be called once.
202    /// Returns an error if the method is called for a second time.
203    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    /// Write to the Broadcast Audio Scan Control Point characteristic in
215    /// without response mode.
216    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    /// Returns a clone of the latest known broadcast audio receive state of the
243    /// specified broadcast source given its broadcast id.
244    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    /// Indicates to the remote BASS server that we have started scanning for
250    /// broadcast sources on behalf of it. If the scan delegator that serves
251    /// the BASS server is collocated with a broadcast sink, this may or may
252    /// not change the scanning behaviour of the the broadcast sink.
253    pub async fn remote_scan_started(&self) -> Result<(), Error> {
254        let op = RemoteScanStartedOperation;
255        self.write_to_bascp(op).await
256    }
257
258    /// Indicates to the remote BASS server that we have stopped scanning for
259    /// broadcast sources on behalf of it.
260    pub async fn remote_scan_stopped(&self) -> Result<(), Error> {
261        let op = RemoteScanStoppedOperation;
262        self.write_to_bascp(op).await
263    }
264
265    /// Provides the BASS server with information regarding a Broadcast Source.
266    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: PaInterval,
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    /// Requests the BASS server to add or update Metadata for the Broadcast
289    /// Source, and/or to request the server to synchronize to, or to stop
290    /// synchronization to, a PA and/or a BIS.
291    ///
292    /// # Arguments
293    ///
294    /// * `broadcast_id` - id of the broadcast source to modify
295    /// * `pa_sync` - pa sync mode the scan delegator peer should attempt to be
296    ///   in
297    /// * `pa_interval` - updated PA interval value. If none, unknown value is
298    ///   used
299    /// * `bis_sync` - desired BIG to BIS synchronization information. If empty,
300    ///   it's not updated
301    /// * `metadata_map` - map of updated metadata for BIGs. If a mapping does
302    ///   not exist for a BIG, that BIG's metadata is not updated
303    pub async fn modify_broadcast_source(
304        &self,
305        broadcast_id: BroadcastId,
306        pa_sync: PaSync,
307        pa_interval: Option<PaInterval>,
308        bis_sync: Option<HashMap<SubgroupIndex, BisSync>>,
309        metadata_map: Option<HashMap<SubgroupIndex, Vec<Metadata>>>,
310    ) -> Result<(), Error> {
311        let op = {
312            let mut state = self
313                .get_broadcast_source_state(&broadcast_id)
314                .ok_or(Error::UnknownBroadcastSource(broadcast_id))?;
315
316            // Update BIS_Sync param for BIGs if applicable.
317            if let Some(sync_map) = bis_sync {
318                for (big_index, group) in state.subgroups.iter_mut().enumerate() {
319                    if let Some(bis_sync) = sync_map.get(&(big_index as u8)) {
320                        group.bis_sync = bis_sync.clone();
321                    }
322                }
323            }
324            // Update metadata for BIGs if applicable.
325            if let Some(mut m) = metadata_map {
326                for (big_index, group) in state.subgroups.iter_mut().enumerate() {
327                    if let Some(metadata) = m.remove(&(big_index as u8)) {
328                        group.metadata = metadata;
329                    }
330                }
331
332                // Left over metadata values are new subgroups that are to be added. New
333                // subgroups can only be added if the subgroup index is
334                // contiguous to existing subgroups.
335                let mut new_big_indices: Vec<&u8> = m.keys().collect();
336                new_big_indices.sort();
337                for big_index in new_big_indices {
338                    if (*big_index as usize) != state.subgroups.len() {
339                        warn!("cannot add new [{big_index}th] subgroup");
340                        break;
341                    }
342                    let new_subgroup = BigSubgroup::new(None).with_metadata(m[big_index].clone());
343                    state.subgroups.push(new_subgroup);
344                }
345            }
346
347            ModifySourceOperation::new(
348                state.source_id,
349                pa_sync,
350                pa_interval.unwrap_or(PaInterval::unknown()),
351                state.subgroups,
352            )
353        };
354        self.write_to_bascp(op).await
355    }
356
357    pub async fn remove_broadcast_source(&self, broadcast_id: BroadcastId) -> Result<(), Error> {
358        let source_id = self.get_source_id(&broadcast_id)?;
359
360        let op = RemoveSourceOperation::new(source_id);
361        self.write_to_bascp(op).await
362    }
363
364    /// Sets the broadcast code for a particular broadcast stream.
365    pub async fn set_broadcast_code(
366        &self,
367        broadcast_id: BroadcastId,
368        broadcast_code: [u8; 16],
369    ) -> Result<(), Error> {
370        let source_id = self.get_source_id(&broadcast_id)?;
371
372        let op = SetBroadcastCodeOperation::new(source_id, broadcast_code.clone());
373        self.write_to_bascp(op).await?;
374
375        // Save the broadcast code we sent.
376        self.broadcast_codes.lock().insert(source_id, broadcast_code);
377        Ok(())
378    }
379
380    /// Returns a list of currently known broadcast sources at the time
381    /// this method was called.
382    pub fn known_broadcast_sources(&self) -> Vec<(Handle, BroadcastReceiveState)> {
383        let lock = self.broadcast_sources.lock();
384        let mut brs = Vec::new();
385        for (k, v) in lock.0.iter() {
386            brs.push((*k, v.clone()));
387        }
388        brs
389    }
390
391    #[cfg(any(test, feature = "test-utils"))]
392    pub fn insert_broadcast_receive_state(&mut self, handle: Handle, brs: BroadcastReceiveState) {
393        self.broadcast_sources.lock().update_state(handle, brs);
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use super::*;
400
401    use std::task::Poll;
402
403    use assert_matches::assert_matches;
404    use futures::executor::block_on;
405    use futures::{pin_mut, FutureExt};
406
407    use bt_common::core::AdvertisingSetId;
408    use bt_common::Uuid;
409    use bt_gatt::test_utils::*;
410    use bt_gatt::types::{
411        AttributePermissions, CharacteristicProperties, CharacteristicProperty, Handle,
412    };
413    use bt_gatt::Characteristic;
414
415    const RECEIVE_STATE_1_HANDLE: Handle = Handle(1);
416    const RECEIVE_STATE_2_HANDLE: Handle = Handle(2);
417    const RECEIVE_STATE_3_HANDLE: Handle = Handle(3);
418    const RANDOME_CHAR_HANDLE: Handle = Handle(4);
419    const AUDIO_SCAN_CONTROL_POINT_HANDLE: Handle = Handle(5);
420
421    fn setup_client() -> (BroadcastAudioScanServiceClient<FakeTypes>, FakePeerService) {
422        let mut fake_peer_service = FakePeerService::new();
423        // Add 3 Broadcast Receive State Characteristics, 1 Broadcast Audio Scan Control
424        // Point Characteristic, and 1 random one.
425        fake_peer_service.add_characteristic(
426            Characteristic {
427                handle: RECEIVE_STATE_1_HANDLE,
428                uuid: BROADCAST_RECEIVE_STATE_UUID,
429                properties: CharacteristicProperties(vec![
430                    CharacteristicProperty::Broadcast,
431                    CharacteristicProperty::Notify,
432                ]),
433                permissions: AttributePermissions::default(),
434                descriptors: vec![],
435            },
436            vec![],
437        );
438        fake_peer_service.add_characteristic(
439            Characteristic {
440                handle: RECEIVE_STATE_2_HANDLE,
441                uuid: BROADCAST_RECEIVE_STATE_UUID,
442                properties: CharacteristicProperties(vec![
443                    CharacteristicProperty::Broadcast,
444                    CharacteristicProperty::Notify,
445                ]),
446                permissions: AttributePermissions::default(),
447                descriptors: vec![],
448            },
449            vec![],
450        );
451        fake_peer_service.add_characteristic(
452            Characteristic {
453                handle: RECEIVE_STATE_3_HANDLE,
454                uuid: BROADCAST_RECEIVE_STATE_UUID,
455                properties: CharacteristicProperties(vec![
456                    CharacteristicProperty::Broadcast,
457                    CharacteristicProperty::Notify,
458                ]),
459                permissions: AttributePermissions::default(),
460                descriptors: vec![],
461            },
462            vec![],
463        );
464        fake_peer_service.add_characteristic(
465            Characteristic {
466                handle: RANDOME_CHAR_HANDLE,
467                uuid: Uuid::from_u16(0x1234),
468                properties: CharacteristicProperties(vec![CharacteristicProperty::Notify]),
469                permissions: AttributePermissions::default(),
470                descriptors: vec![],
471            },
472            vec![],
473        );
474        fake_peer_service.add_characteristic(
475            Characteristic {
476                handle: AUDIO_SCAN_CONTROL_POINT_HANDLE,
477                uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID,
478                properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]),
479                permissions: AttributePermissions::default(),
480                descriptors: vec![],
481            },
482            vec![],
483        );
484
485        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
486        let create_result =
487            BroadcastAudioScanServiceClient::<FakeTypes>::create(fake_peer_service.clone());
488        pin_mut!(create_result);
489        let polled = create_result.poll_unpin(&mut noop_cx);
490        let Poll::Ready(Ok(client)) = polled else {
491            panic!("Expected BroadcastAudioScanServiceClient to be succesfully created");
492        };
493
494        (client, fake_peer_service)
495    }
496
497    #[test]
498    fn create_client() {
499        let (client, _) = setup_client();
500
501        // Check that all the characteristics have been discovered.
502        assert_eq!(client.audio_scan_control_point, AUDIO_SCAN_CONTROL_POINT_HANDLE);
503        let broadcast_sources = client.known_broadcast_sources();
504        assert_eq!(broadcast_sources.len(), 3);
505        assert!(broadcast_sources.iter().find(|v| v.0 == RECEIVE_STATE_1_HANDLE).is_some());
506        assert!(broadcast_sources.iter().find(|v| v.0 == RECEIVE_STATE_2_HANDLE).is_some());
507        assert!(broadcast_sources.iter().find(|v| v.0 == RECEIVE_STATE_3_HANDLE).is_some());
508    }
509
510    #[test]
511    fn create_client_fails_missing_characteristics() {
512        // Missing scan control point characteristic.
513        let mut fake_peer_service = FakePeerService::new();
514        fake_peer_service.add_characteristic(
515            Characteristic {
516                handle: Handle(1),
517                uuid: BROADCAST_RECEIVE_STATE_UUID,
518                properties: CharacteristicProperties(vec![
519                    CharacteristicProperty::Broadcast,
520                    CharacteristicProperty::Notify,
521                ]),
522                permissions: AttributePermissions::default(),
523                descriptors: vec![],
524            },
525            vec![],
526        );
527
528        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
529        let create_result =
530            BroadcastAudioScanServiceClient::<FakeTypes>::create(fake_peer_service.clone());
531
532        pin_mut!(create_result);
533        let polled = create_result.poll_unpin(&mut noop_cx);
534        let Poll::Ready(Err(_)) = polled else {
535            panic!("Expected BroadcastAudioScanServiceClient to have failed");
536        };
537
538        // Missing receive state characteristic.
539        let mut fake_peer_service: FakePeerService = FakePeerService::new();
540        fake_peer_service.add_characteristic(
541            Characteristic {
542                handle: Handle(1),
543                uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID,
544                properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]),
545                permissions: AttributePermissions::default(),
546                descriptors: vec![],
547            },
548            vec![],
549        );
550
551        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
552        let create_result =
553            BroadcastAudioScanServiceClient::<FakeTypes>::create(fake_peer_service.clone());
554        pin_mut!(create_result);
555        let polled = create_result.poll_unpin(&mut noop_cx);
556        let Poll::Ready(Err(_)) = polled else {
557            panic!("Expected BroadcastAudioScanServiceClient to have failed");
558        };
559    }
560
561    #[test]
562    fn create_client_fails_duplicate_characteristics() {
563        // More than one scan control point characteristics.
564        let mut fake_peer_service = FakePeerService::new();
565        fake_peer_service.add_characteristic(
566            Characteristic {
567                handle: Handle(1),
568                uuid: BROADCAST_RECEIVE_STATE_UUID,
569                properties: CharacteristicProperties(vec![
570                    CharacteristicProperty::Broadcast,
571                    CharacteristicProperty::Notify,
572                ]),
573                permissions: AttributePermissions::default(),
574                descriptors: vec![],
575            },
576            vec![],
577        );
578        fake_peer_service.add_characteristic(
579            Characteristic {
580                handle: Handle(2),
581                uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID,
582                properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]),
583                permissions: AttributePermissions::default(),
584                descriptors: vec![],
585            },
586            vec![],
587        );
588        fake_peer_service.add_characteristic(
589            Characteristic {
590                handle: Handle(3),
591                uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID,
592                properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]),
593                permissions: AttributePermissions::default(),
594                descriptors: vec![],
595            },
596            vec![],
597        );
598
599        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
600        let create_result =
601            BroadcastAudioScanServiceClient::<FakeTypes>::create(fake_peer_service.clone());
602        pin_mut!(create_result);
603        let polled = create_result.poll_unpin(&mut noop_cx);
604        let Poll::Ready(Err(_)) = polled else {
605            panic!("Expected BroadcastAudioScanServiceClient to have failed");
606        };
607    }
608
609    #[test]
610    fn start_event_stream() {
611        let (mut client, mut fake_peer_service) = setup_client();
612        let mut event_stream = client.take_event_stream().expect("stream was created");
613
614        // Send notification for updating BRS characteristic to indicate it's synced and
615        // requires broadcast code.
616        #[rustfmt::skip]
617        fake_peer_service.add_characteristic(
618            Characteristic {
619                handle: RECEIVE_STATE_2_HANDLE,
620                uuid: BROADCAST_RECEIVE_STATE_UUID,
621                properties: CharacteristicProperties(vec![
622                    CharacteristicProperty::Broadcast,
623                    CharacteristicProperty::Notify,
624                ]),
625                permissions: AttributePermissions::default(),
626                descriptors: vec![],
627            },
628            vec![
629                0x02, AddressType::Public as u8,                      // source id and address type
630                0x02, 0x03, 0x04, 0x05, 0x06, 0x07,                   // address
631                0x01, 0x02, 0x03, 0x04,                               // ad set id and broadcast id
632                PaSyncState::Synced as u8,
633                EncryptionStatus::BroadcastCodeRequired.raw_value(),
634                0x00,                                                 // no subgroups
635            ],
636        );
637
638        // Check that synced and broadcast code required events were sent out.
639        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
640
641        let recv_fut = event_stream.select_next_some();
642        let event = block_on(recv_fut).expect("should receive event");
643        assert_eq!(
644            event,
645            Event::AddedBroadcastSource(
646                BroadcastId::try_from(0x040302).unwrap(),
647                PaSyncState::Synced,
648                EncryptionStatus::BroadcastCodeRequired
649            )
650        );
651
652        // Stream should be pending since no more notifications.
653        assert!(event_stream.poll_next_unpin(&mut noop_cx).is_pending());
654
655        // Send notification for updating BRS characteristic to indicate it requires
656        // sync info. Notification for updating the BRS characteristic value for
657        // characteristic with handle 3.
658        #[rustfmt::skip]
659        fake_peer_service.add_characteristic(
660            Characteristic {
661                handle: RECEIVE_STATE_3_HANDLE,
662                uuid: BROADCAST_RECEIVE_STATE_UUID,
663                properties: CharacteristicProperties(vec![
664                    CharacteristicProperty::Broadcast,
665                    CharacteristicProperty::Notify,
666                ]),
667                permissions: AttributePermissions::default(),
668                descriptors: vec![],
669            },
670            vec![
671                0x03, AddressType::Public as u8,             // source id and address type
672                0x03, 0x04, 0x05, 0x06, 0x07, 0x08,          // address
673                0x01, 0x03, 0x04, 0x05,                      // ad set id and broadcast id
674                PaSyncState::SyncInfoRequest as u8,
675                EncryptionStatus::NotEncrypted.raw_value(),
676                0x00,                                        // no subgroups
677            ],
678        );
679
680        let recv_fut = event_stream.select_next_some();
681        let event = block_on(recv_fut).expect("should receive event");
682        assert_eq!(
683            event,
684            Event::AddedBroadcastSource(
685                BroadcastId::try_from(0x050403).unwrap(),
686                PaSyncState::SyncInfoRequest,
687                EncryptionStatus::NotEncrypted
688            )
689        );
690
691        // Stream should be pending since no more notifications.
692        assert!(event_stream.poll_next_unpin(&mut noop_cx).is_pending());
693    }
694
695    #[test]
696    fn remote_scan_started() {
697        let (client, mut fake_peer_service) = setup_client();
698
699        fake_peer_service.expect_characteristic_value(&AUDIO_SCAN_CONTROL_POINT_HANDLE, vec![0x01]);
700
701        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
702        let op_fut = client.remote_scan_started();
703        pin_mut!(op_fut);
704        let polled = op_fut.poll_unpin(&mut noop_cx);
705        assert_matches!(polled, Poll::Ready(Ok(_)));
706    }
707
708    #[test]
709    fn remote_scan_stopped() {
710        let (client, mut fake_peer_service) = setup_client();
711
712        fake_peer_service.expect_characteristic_value(&AUDIO_SCAN_CONTROL_POINT_HANDLE, vec![0x00]);
713
714        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
715        let op_fut = client.remote_scan_stopped();
716        pin_mut!(op_fut);
717        let polled = op_fut.poll_unpin(&mut noop_cx);
718        assert_matches!(polled, Poll::Ready(Ok(_)));
719    }
720
721    #[test]
722    fn add_broadcast_source() {
723        let (client, mut fake_peer_service) = setup_client();
724
725        fake_peer_service.expect_characteristic_value(
726            &AUDIO_SCAN_CONTROL_POINT_HANDLE,
727            vec![
728                0x02, 0x00, 0x04, 0x10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x11, 0x00, 0x00, 0x00, 0xFF,
729                0xFF, 0x00,
730            ],
731        );
732
733        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
734        let op_fut = client.add_broadcast_source(
735            BroadcastId::try_from(0x11).unwrap(),
736            AddressType::Public,
737            [0x04, 0x10, 0x00, 0x00, 0x00, 0x00],
738            AdvertisingSetId(1),
739            PaSync::DoNotSync,
740            PaInterval::unknown(),
741            vec![],
742        );
743        pin_mut!(op_fut);
744        let polled = op_fut.poll_unpin(&mut noop_cx);
745        assert_matches!(polled, Poll::Ready(Ok(_)));
746    }
747
748    #[test]
749    fn modify_broadcast_source() {
750        let (client, mut fake_peer_service) = setup_client();
751
752        // Manually update the broadcast source tracker for testing purposes.
753        // In practice, this would have been updated from BRS value change notification.
754        client.broadcast_sources.lock().update_state(
755            RECEIVE_STATE_1_HANDLE,
756            BroadcastReceiveState::NonEmpty(ReceiveState {
757                source_id: 0x11,
758                source_address_type: AddressType::Public,
759                source_address: [1, 2, 3, 4, 5, 6],
760                source_adv_sid: AdvertisingSetId(1),
761                broadcast_id: BroadcastId::try_from(0x11).unwrap(),
762                pa_sync_state: PaSyncState::Synced,
763                big_encryption: EncryptionStatus::BroadcastCodeRequired,
764                subgroups: vec![],
765            }),
766        );
767
768        #[rustfmt::skip]
769        fake_peer_service.expect_characteristic_value(
770            &AUDIO_SCAN_CONTROL_POINT_HANDLE,
771            vec![
772                0x03, 0x11, 0x00,  // opcode, source id, pa sync
773                0xAA, 0xAA, 0x00,  // pa sync, pa interval, num of subgroups
774            ],
775        );
776
777        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
778        let op_fut = client.modify_broadcast_source(
779            BroadcastId::try_from(0x11).unwrap(),
780            PaSync::DoNotSync,
781            Some(PaInterval(0xAAAA)),
782            None,
783            None,
784        );
785        pin_mut!(op_fut);
786        let polled = op_fut.poll_unpin(&mut noop_cx);
787        assert_matches!(polled, Poll::Ready(Ok(_)));
788    }
789
790    #[test]
791    fn modify_broadcast_source_updates_groups() {
792        let (client, mut fake_peer_service) = setup_client();
793
794        // Manually update the broadcast source tracker for testing purposes.
795        // In practice, this would have been updated from BRS value change notification.
796        client.broadcast_sources.lock().update_state(
797            RECEIVE_STATE_1_HANDLE,
798            BroadcastReceiveState::NonEmpty(ReceiveState {
799                source_id: 0x11,
800                source_address_type: AddressType::Public,
801                source_address: [1, 2, 3, 4, 5, 6],
802                source_adv_sid: AdvertisingSetId(1),
803                broadcast_id: BroadcastId::try_from(0x11).unwrap(),
804                pa_sync_state: PaSyncState::Synced,
805                big_encryption: EncryptionStatus::BroadcastCodeRequired,
806                subgroups: vec![BigSubgroup::new(None)],
807            }),
808        );
809
810        // Default PA interval value and subgroups value read from the BRS
811        // characteristic are used.
812        #[rustfmt::skip]
813        fake_peer_service.expect_characteristic_value(
814            &AUDIO_SCAN_CONTROL_POINT_HANDLE,
815            vec![
816                0x03, 0x11, 0x00,                    // opcode, source id, pa sync
817                0xFF, 0xFF, 0x02,                    // pa sync, pa interval, num of subgroups
818                0x15, 0x00, 0x00, 0x00,              // bis sync (0th subgroup)
819                0x02, 0x01, 0x09,                    // metadata len, metadata
820                0xFF, 0xFF, 0xFF, 0xFF,              // bis sync (1th subgroup)
821                0x05, 0x04, 0x04, 0x65, 0x6E, 0x67,  // metadata len, metadata
822            ],
823        );
824
825        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
826        let op_fut = client.modify_broadcast_source(
827            BroadcastId::try_from(0x11).unwrap(),
828            PaSync::DoNotSync,
829            None,
830            Some(HashMap::from([(0, BisSync::sync(vec![1, 3, 5]).unwrap())])),
831            Some(HashMap::from([
832                (0, vec![Metadata::BroadcastAudioImmediateRenderingFlag]),
833                (1, vec![Metadata::Language("eng".to_string())]),
834                (5, vec![Metadata::ProgramInfoURI("this subgroup shouldn't be added".to_string())]),
835            ])),
836        );
837        pin_mut!(op_fut);
838        let polled = op_fut.poll_unpin(&mut noop_cx);
839        assert_matches!(polled, Poll::Ready(Ok(_)));
840    }
841
842    #[test]
843    fn modify_broadcast_source_fail() {
844        let (client, _fake_peer_service) = setup_client();
845
846        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
847        // Broadcast source wasn't previously added.
848        let op_fut = client.modify_broadcast_source(
849            BroadcastId::try_from(0x11).unwrap(),
850            PaSync::DoNotSync,
851            None,
852            None,
853            None,
854        );
855        pin_mut!(op_fut);
856        let polled = op_fut.poll_unpin(&mut noop_cx);
857        assert_matches!(polled, Poll::Ready(Err(_)));
858    }
859
860    #[test]
861    fn remove_broadcast_source() {
862        let (client, mut fake_peer_service) = setup_client();
863        let bid = BroadcastId::try_from(0x11).expect("should not fail");
864
865        // Manually update the broadcast source tracker for testing purposes.
866        // In practice, this would have been updated from BRS value change notification.
867        client.broadcast_sources.lock().update_state(
868            RECEIVE_STATE_1_HANDLE,
869            BroadcastReceiveState::NonEmpty(ReceiveState {
870                source_id: 0x11,
871                source_address_type: AddressType::Public,
872                source_address: [1, 2, 3, 4, 5, 6],
873                source_adv_sid: AdvertisingSetId(1),
874                broadcast_id: bid,
875                pa_sync_state: PaSyncState::Synced,
876                big_encryption: EncryptionStatus::BroadcastCodeRequired,
877                subgroups: vec![],
878            }),
879        );
880
881        fake_peer_service
882            .expect_characteristic_value(&AUDIO_SCAN_CONTROL_POINT_HANDLE, vec![0x05, 0x11]);
883
884        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
885        // Broadcast source wasn't previously added.
886        let op_fut = client.remove_broadcast_source(bid);
887        pin_mut!(op_fut);
888        let polled = op_fut.poll_unpin(&mut noop_cx);
889        assert_matches!(polled, Poll::Ready(Ok(_)));
890    }
891
892    #[test]
893    fn remove_broadcast_source_fail() {
894        let (client, _fake_peer_service) = setup_client();
895
896        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
897        // Broadcast source wasn't previously added.
898        let op_fut = client.remove_broadcast_source(BroadcastId::try_from(0x11).unwrap());
899        pin_mut!(op_fut);
900        let polled = op_fut.poll_unpin(&mut noop_cx);
901        assert_matches!(polled, Poll::Ready(Err(_)));
902    }
903
904    #[test]
905    fn set_broadcast_code() {
906        let (client, mut fake_peer_service) = setup_client();
907
908        // Manually update the broadcast source tracker for testing purposes.
909        // In practice, this would have been updated from BRS value change notification.
910        client.broadcast_sources.lock().update_state(
911            RECEIVE_STATE_1_HANDLE,
912            BroadcastReceiveState::NonEmpty(ReceiveState {
913                source_id: 0x01,
914                source_address_type: AddressType::Public,
915                source_address: [1, 2, 3, 4, 5, 6],
916                source_adv_sid: AdvertisingSetId(1),
917                broadcast_id: BroadcastId::try_from(0x030201).unwrap(),
918                pa_sync_state: PaSyncState::Synced,
919                big_encryption: EncryptionStatus::BroadcastCodeRequired,
920                subgroups: vec![],
921            }),
922        );
923
924        fake_peer_service.expect_characteristic_value(
925            &AUDIO_SCAN_CONTROL_POINT_HANDLE,
926            vec![0x04, 0x01, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
927        );
928
929        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
930        let set_code_fut =
931            client.set_broadcast_code(BroadcastId::try_from(0x030201).unwrap(), [1; 16]);
932        pin_mut!(set_code_fut);
933        let polled = set_code_fut.poll_unpin(&mut noop_cx);
934        assert_matches!(polled, Poll::Ready(Ok(_)));
935    }
936
937    #[test]
938    fn set_broadcast_code_fails() {
939        let (client, _) = setup_client();
940
941        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
942        let set_code_fut =
943            client.set_broadcast_code(BroadcastId::try_from(0x030201).unwrap(), [1; 16]);
944        pin_mut!(set_code_fut);
945        let polled = set_code_fut.poll_unpin(&mut noop_cx);
946
947        // Should fail because we cannot get source id for the broadcast id since BRS
948        // Characteristic value wasn't updated.
949        assert_matches!(polled, Poll::Ready(Err(_)));
950    }
951}