Skip to main content

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, 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/// 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: 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    /// Requests the Scan Delegator to modify a Broadcast Source's
289    /// synchronization state and/or metadata.
290    ///
291    /// This method writes a **Modify Source** operation to the Broadcast Audio
292    /// Scan Control Point. It reads the current state of the Broadcast
293    /// Source from the local cache (the BRS characteristic value), applies
294    /// the requested modifications, and sends the complete updated subgroup
295    /// list to the server.
296    ///
297    /// # Arguments
298    ///
299    /// * `broadcast_id` - id of the broadcast source to modify
300    /// * `pa_sync` - pa sync mode the scan delegator peer should attempt to be
301    ///   in
302    /// * `pa_interval` - updated PA interval value. If none, unknown value is
303    ///   used
304    /// * `bis_map` - desired BIG to BIS synchronization update information. If
305    ///   a BIG does not exist as a key, sync for that BIG is not updated.
306    /// * `metadata_map` - map of updated metadata for BIGs. If a mapping does
307    ///   not exist for a BIG, that BIG's metadata is not updated
308    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        // Update BIS_Sync param for BIGs if applicable.
321        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        // Update metadata for BIGs if applicable.
328        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            // Left over metadata values are new subgroups that are to be added. New
336            // subgroups can only be added if the subgroup index is
337            // contiguous to existing subgroups.
338            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    /// Sets the broadcast code for a particular broadcast stream.
367    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        // Save the broadcast code we sent.
378        self.broadcast_codes.lock().insert(source_id, broadcast_code);
379        Ok(())
380    }
381
382    /// Returns a list of currently known broadcast sources at the time
383    /// this method was called.
384    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        // Add 3 Broadcast Receive State Characteristics, 1 Broadcast Audio Scan Control
426        // Point Characteristic, and 1 random one.
427        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        // Check that all the characteristics have been discovered.
504        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        // Missing scan control point characteristic.
515        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        // Missing receive state characteristic.
541        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        // More than one scan control point characteristics.
566        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        // Send notification for updating BRS characteristic to indicate it's synced and
617        // requires broadcast code.
618        #[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,                      // source id and address type
632                0x02, 0x03, 0x04, 0x05, 0x06, 0x07,                   // address
633                0x01, 0x02, 0x03, 0x04,                               // ad set id and broadcast id
634                PaSyncState::Synced as u8,
635                EncryptionStatus::BroadcastCodeRequired.raw_value(),
636                0x00,                                                 // no subgroups
637            ],
638        );
639
640        // Check that synced and broadcast code required events were sent out.
641        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        // Stream should be pending since no more notifications.
655        assert!(event_stream.poll_next_unpin(&mut noop_cx).is_pending());
656
657        // Send notification for updating BRS characteristic to indicate it requires
658        // sync info. Notification for updating the BRS characteristic value for
659        // characteristic with handle 3.
660        #[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,             // source id and address type
674                0x03, 0x04, 0x05, 0x06, 0x07, 0x08,          // address
675                0x01, 0x03, 0x04, 0x05,                      // ad set id and broadcast id
676                PaSyncState::SyncInfoRequest as u8,
677                EncryptionStatus::NotEncrypted.raw_value(),
678                0x00,                                        // no subgroups
679            ],
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        // Stream should be pending since no more notifications.
694        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        // Manually update the broadcast source tracker for testing purposes.
755        // In practice, this would have been updated from BRS value change notification.
756        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,  // opcode, source id, pa sync
775                0xAA, 0xAA, 0x00,  // pa sync, pa interval, num of subgroups
776            ],
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        // Manually update the broadcast source tracker for testing purposes.
797        // In practice, this would have been updated from BRS value change notification.
798        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        // Default PA interval value and subgroups value read from the BRS
813        // characteristic are used.
814        #[rustfmt::skip]
815        fake_peer_service.expect_characteristic_value(
816            &AUDIO_SCAN_CONTROL_POINT_HANDLE,
817            vec![
818                0x03, 0x11, 0x00,                    // opcode, source id, pa sync
819                0xFF, 0xFF, 0x02,                    // pa sync, pa interval, num of subgroups
820                0x15, 0x00, 0x00, 0x00,              // bis sync (0th subgroup)
821                0x02, 0x01, 0x09,                    // metadata len, metadata
822                0xFF, 0xFF, 0xFF, 0xFF,              // bis sync (1th subgroup)
823                0x05, 0x04, 0x04, 0x65, 0x6E, 0x67,  // metadata len, metadata
824            ],
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        // Broadcast source wasn't previously added.
850        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        // Manually update the broadcast source tracker for testing purposes.
868        // In practice, this would have been updated from BRS value change notification.
869        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        // Broadcast source wasn't previously added.
888        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        // Broadcast source wasn't previously added.
900        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        // Manually update the broadcast source tracker for testing purposes.
911        // In practice, this would have been updated from BRS value change notification.
912        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        // Should fail because we cannot get source id for the broadcast id since BRS
950        // Characteristic value wasn't updated.
951        assert_matches!(polled, Poll::Ready(Err(_)));
952    }
953}