| // Copyright 2023 Google LLC | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | pub mod error; | 
 |  | 
 | use std::collections::HashMap; | 
 | use std::sync::{Arc, RwLock}; | 
 | use std::time::SystemTime; | 
 |  | 
 | use tracing::warn; | 
 |  | 
 | use bt_common::packet_encoding::Decodable; | 
 | use bt_gatt::client::{PeerService, ServiceCharacteristic}; | 
 | use bt_gatt::types::Handle; | 
 |  | 
 | use crate::client::error::{Error, ServiceError}; | 
 | use crate::types::*; | 
 |  | 
 | const READ_CHARACTERISTIC_BUFFER_SIZE: usize = 255; | 
 |  | 
 | #[allow(dead_code)] | 
 | type ReceiveStateReadValue = (BroadcastReceiveState, SystemTime); | 
 |  | 
 | /// Manages connection to the Broadcast Audio Scan Service at the | 
 | /// remote Scan Delegator and writes/reads characteristics to/from it. | 
 | #[allow(dead_code)] | 
 | pub struct BroadcastAudioScanServiceClient<T: bt_gatt::GattTypes> { | 
 |     gatt_client: T::PeerService, | 
 |     /// Broadcast Audio Scan Service only has one Broadcast Audio Scan Control | 
 |     /// Point characteristic according to BASS Section 3. There shall | 
 |     /// be one or more Broadcast Receive State characteristics. | 
 |     audio_scan_control_point: Handle, | 
 |     /// Broadcast Receive State characteristics can be used to determine the | 
 |     /// BASS status. | 
 |     receive_states: Arc<RwLock<HashMap<Handle, Option<ReceiveStateReadValue>>>>, | 
 |     // List of characteristic notification streams we are listening to. | 
 |     notification_streams: Vec<T::NotificationStream>, | 
 | } | 
 |  | 
 | #[allow(dead_code)] | 
 | impl<T: bt_gatt::GattTypes> BroadcastAudioScanServiceClient<T> { | 
 |     async fn create(gatt_client: T::PeerService) -> Result<Self, Error> { | 
 |         let characteristics = Self::discover_all_characteristics(&gatt_client).await?; | 
 |         let mut client = Self { | 
 |             gatt_client, | 
 |             audio_scan_control_point: characteristics.0, | 
 |             receive_states: Arc::new(RwLock::new(characteristics.1)), | 
 |             notification_streams: Vec::new(), | 
 |         }; | 
 |         client.register_notification().await?; | 
 |         Ok(client) | 
 |     } | 
 |  | 
 |     // Discover all the characteristics that are required. | 
 |     // On success, returns the tuple of (Broadcast Audio Scan Control Point | 
 |     // Characteristic, HashMap of all Broadcast Received State Characteristics). | 
 |     async fn discover_all_characteristics( | 
 |         gatt_client: &T::PeerService, | 
 |     ) -> Result<(Handle, HashMap<Handle, Option<ReceiveStateReadValue>>), Error> { | 
 |         let bascp = | 
 |             ServiceCharacteristic::<T>::find(gatt_client, BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID) | 
 |                 .await | 
 |                 .map_err(|e| Error::Gatt(e))?; | 
 |         let brs = ServiceCharacteristic::<T>::find(gatt_client, BROADCAST_RECEIVE_STATE_UUID) | 
 |             .await | 
 |             .map_err(|e| Error::Gatt(e))?; | 
 |         if bascp.len() == 0 || brs.len() == 0 { | 
 |             return Err(Error::Service(ServiceError::MissingCharacteristic)); | 
 |         } | 
 |         if bascp.len() > 1 { | 
 |             return Err(Error::Service(ServiceError::ExtraScanControlPointCharacteristic)); | 
 |         } | 
 |         // Discover all characteristics at the BASS at the remote peer over the GATT | 
 |         // connection. Service should have a single Broadcast Audio Scan Control Point | 
 |         // Characteristic | 
 |         let mut brs_map = HashMap::new(); | 
 |         for c in brs { | 
 |             // Read the value of the Broadcast Recieve State at the time of discovery for | 
 |             // record. | 
 |             let mut buf = vec![0; READ_CHARACTERISTIC_BUFFER_SIZE]; | 
 |             match c.read(&mut buf[..]).await { | 
 |                 Ok(read_bytes) => match BroadcastReceiveState::decode(&buf[0..read_bytes]) { | 
 |                     Ok(decoded) => { | 
 |                         brs_map.insert(*c.handle(), Some((decoded.0, SystemTime::now()))); | 
 |                         continue; | 
 |                     } | 
 |                     Err(e) => warn!( | 
 |                         "Failed to decode characteristic ({:?}) to Broadcast Receive State value: {:?}", | 
 |                         *c.handle(), | 
 |                         e | 
 |                     ), | 
 |                 }, | 
 |                 Err(e) => warn!("Failed to read characteristic ({:?}) value: {:?}", *c.handle(), e), | 
 |             } | 
 |             brs_map.insert(*c.handle(), None); | 
 |         } | 
 |         Ok((*bascp[0].handle(), brs_map)) | 
 |     } | 
 |  | 
 |     /// Registers for notifications for Broadcast Receive State characteristics. | 
 |     async fn register_notification(&mut self) -> Result<(), Error> { | 
 |         // Notification is mandatory for Receive State as per BASS v1.0 Section 3.2.1. | 
 |         let lock = self.receive_states.write().unwrap(); | 
 |         for handle in lock.keys() { | 
 |             let stream = self.gatt_client.subscribe(handle); | 
 |             self.notification_streams.push(stream); | 
 |         } | 
 |         Ok(()) | 
 |     } | 
 | } | 
 |  | 
 | #[cfg(test)] | 
 | mod tests { | 
 |     use super::*; | 
 |  | 
 |     use std::task::Poll; | 
 |  | 
 |     use futures::{pin_mut, FutureExt}; | 
 |  | 
 |     use bt_common::Uuid; | 
 |     use bt_gatt::test_utils::*; | 
 |     use bt_gatt::types::{ | 
 |         AttributePermissions, CharacteristicProperties, CharacteristicProperty, Handle, | 
 |     }; | 
 |     use bt_gatt::Characteristic; | 
 |  | 
 |     #[test] | 
 |     fn create_client() { | 
 |         let mut fake_peer_service = FakePeerService::new(); | 
 |         // Add 3 Broadcast Receive State Characteristics, 1 Broadcast Audio Scan Control | 
 |         // Point Characteristic, and 1 random one. | 
 |         fake_peer_service.add_characteristic( | 
 |             Characteristic { | 
 |                 handle: Handle(1), | 
 |                 uuid: BROADCAST_RECEIVE_STATE_UUID, | 
 |                 properties: CharacteristicProperties(vec![ | 
 |                     CharacteristicProperty::Broadcast, | 
 |                     CharacteristicProperty::Notify, | 
 |                 ]), | 
 |                 permissions: AttributePermissions::default(), | 
 |                 descriptors: vec![], | 
 |             }, | 
 |             vec![], | 
 |         ); | 
 |         fake_peer_service.add_characteristic( | 
 |             Characteristic { | 
 |                 handle: Handle(2), | 
 |                 uuid: BROADCAST_RECEIVE_STATE_UUID, | 
 |                 properties: CharacteristicProperties(vec![ | 
 |                     CharacteristicProperty::Broadcast, | 
 |                     CharacteristicProperty::Notify, | 
 |                 ]), | 
 |                 permissions: AttributePermissions::default(), | 
 |                 descriptors: vec![], | 
 |             }, | 
 |             vec![], | 
 |         ); | 
 |         fake_peer_service.add_characteristic( | 
 |             Characteristic { | 
 |                 handle: Handle(3), | 
 |                 uuid: BROADCAST_RECEIVE_STATE_UUID, | 
 |                 properties: CharacteristicProperties(vec![ | 
 |                     CharacteristicProperty::Broadcast, | 
 |                     CharacteristicProperty::Notify, | 
 |                 ]), | 
 |                 permissions: AttributePermissions::default(), | 
 |                 descriptors: vec![], | 
 |             }, | 
 |             vec![], | 
 |         ); | 
 |         fake_peer_service.add_characteristic( | 
 |             Characteristic { | 
 |                 handle: Handle(4), | 
 |                 uuid: Uuid::from_u16(0x1234), | 
 |                 properties: CharacteristicProperties(vec![CharacteristicProperty::Notify]), | 
 |                 permissions: AttributePermissions::default(), | 
 |                 descriptors: vec![], | 
 |             }, | 
 |             vec![], | 
 |         ); | 
 |         fake_peer_service.add_characteristic( | 
 |             Characteristic { | 
 |                 handle: Handle(5), | 
 |                 uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID, | 
 |                 properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]), | 
 |                 permissions: AttributePermissions::default(), | 
 |                 descriptors: vec![], | 
 |             }, | 
 |             vec![], | 
 |         ); | 
 |  | 
 |         let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref()); | 
 |         let create_result = | 
 |             BroadcastAudioScanServiceClient::<FakeTypes>::create(fake_peer_service.clone()); | 
 |         pin_mut!(create_result); | 
 |         let polled = create_result.poll_unpin(&mut noop_cx); | 
 |         let Poll::Ready(Ok(client)) = polled else { | 
 |             panic!("Expected BASSClient to be succesfully created"); | 
 |         }; | 
 |  | 
 |         // Check that all the characteristics have been discovered. | 
 |         assert_eq!(client.audio_scan_control_point, Handle(5)); | 
 |         let broadcast_receive_states = client.receive_states.read().unwrap(); | 
 |         assert!(broadcast_receive_states.contains_key(&Handle(1))); | 
 |         assert!(broadcast_receive_states.contains_key(&Handle(2))); | 
 |         assert!(broadcast_receive_states.contains_key(&Handle(3))); | 
 |  | 
 |         // Three notification one for each of broadcast receive state characteristic | 
 |         // should have been set up. | 
 |         assert_eq!(client.notification_streams.len(), 3); | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn create_client_fails_missing_characteristics() { | 
 |         // Missing scan control point characteristic. | 
 |         let mut fake_peer_service = FakePeerService::new(); | 
 |         fake_peer_service.add_characteristic( | 
 |             Characteristic { | 
 |                 handle: Handle(1), | 
 |                 uuid: BROADCAST_RECEIVE_STATE_UUID, | 
 |                 properties: CharacteristicProperties(vec![ | 
 |                     CharacteristicProperty::Broadcast, | 
 |                     CharacteristicProperty::Notify, | 
 |                 ]), | 
 |                 permissions: AttributePermissions::default(), | 
 |                 descriptors: vec![], | 
 |             }, | 
 |             vec![], | 
 |         ); | 
 |  | 
 |         let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref()); | 
 |         let create_result = | 
 |             BroadcastAudioScanServiceClient::<FakeTypes>::create(fake_peer_service.clone()); | 
 |         pin_mut!(create_result); | 
 |         let polled = create_result.poll_unpin(&mut noop_cx); | 
 |         let Poll::Ready(Err(_)) = polled else { | 
 |             panic!("Expected BASSClient to have failed"); | 
 |         }; | 
 |  | 
 |         // Missing receive state characteristic. | 
 |         let mut fake_peer_service: FakePeerService = FakePeerService::new(); | 
 |         fake_peer_service.add_characteristic( | 
 |             Characteristic { | 
 |                 handle: Handle(1), | 
 |                 uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID, | 
 |                 properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]), | 
 |                 permissions: AttributePermissions::default(), | 
 |                 descriptors: vec![], | 
 |             }, | 
 |             vec![], | 
 |         ); | 
 |  | 
 |         let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref()); | 
 |         let create_result = | 
 |             BroadcastAudioScanServiceClient::<FakeTypes>::create(fake_peer_service.clone()); | 
 |         pin_mut!(create_result); | 
 |         let polled = create_result.poll_unpin(&mut noop_cx); | 
 |         let Poll::Ready(Err(_)) = polled else { | 
 |             panic!("Expected BASSClient to have failed"); | 
 |         }; | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn create_client_fails_duplicate_characteristics() { | 
 |         // More than one scan control point characteristics. | 
 |         let mut fake_peer_service = FakePeerService::new(); | 
 |         fake_peer_service.add_characteristic( | 
 |             Characteristic { | 
 |                 handle: Handle(1), | 
 |                 uuid: BROADCAST_RECEIVE_STATE_UUID, | 
 |                 properties: CharacteristicProperties(vec![ | 
 |                     CharacteristicProperty::Broadcast, | 
 |                     CharacteristicProperty::Notify, | 
 |                 ]), | 
 |                 permissions: AttributePermissions::default(), | 
 |                 descriptors: vec![], | 
 |             }, | 
 |             vec![], | 
 |         ); | 
 |         fake_peer_service.add_characteristic( | 
 |             Characteristic { | 
 |                 handle: Handle(2), | 
 |                 uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID, | 
 |                 properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]), | 
 |                 permissions: AttributePermissions::default(), | 
 |                 descriptors: vec![], | 
 |             }, | 
 |             vec![], | 
 |         ); | 
 |         fake_peer_service.add_characteristic( | 
 |             Characteristic { | 
 |                 handle: Handle(3), | 
 |                 uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID, | 
 |                 properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]), | 
 |                 permissions: AttributePermissions::default(), | 
 |                 descriptors: vec![], | 
 |             }, | 
 |             vec![], | 
 |         ); | 
 |  | 
 |         let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref()); | 
 |         let create_result = | 
 |             BroadcastAudioScanServiceClient::<FakeTypes>::create(fake_peer_service.clone()); | 
 |         pin_mut!(create_result); | 
 |         let polled = create_result.poll_unpin(&mut noop_cx); | 
 |         let Poll::Ready(Err(_)) = polled else { | 
 |             panic!("Expected BASSClient to have failed"); | 
 |         }; | 
 |     } | 
 | } |