| // 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::time::SystemTime; | 
 | use std::sync::{Arc, RwLock}; | 
 |  | 
 | use futures::Stream; | 
 | use tracing::warn; | 
 |  | 
 | use bt_common::packet_encoding::Decodable; | 
 | use bt_gatt::types::{Handle, Result as GattResult}; | 
 | use bt_gatt::client::{CharacteristicNotification, PeerService, ServiceCharacteristic}; | 
 |  | 
 | 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<PeerServiceT, GattNotificationStream> { | 
 |     gatt_client: Box<PeerServiceT>, | 
 |     /// 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<GattNotificationStream>, | 
 | } | 
 |  | 
 | #[allow(dead_code)] | 
 | impl<GattNotificationStream: Stream<Item = GattResult<CharacteristicNotification>>, PeerServiceT: PeerService<NotificationStream = GattNotificationStream>> BroadcastAudioScanServiceClient<PeerServiceT, GattNotificationStream> { | 
 |     async fn create(gatt_client: Box<PeerServiceT>) -> 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: &PeerServiceT) -> Result<(Handle, HashMap<Handle, Option<ReceiveStateReadValue>>), Error> { | 
 |         let bascp = ServiceCharacteristic::find(gatt_client, BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID).await.map_err(|e| Error::Gatt(e))?; | 
 |         let brs = ServiceCharacteristic::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::{FutureExt, pin_mut}; | 
 |  | 
 |     use bt_common::Uuid; | 
 |     use bt_gatt::Characteristic; | 
 |     use bt_gatt::types::{AttributePermissions, CharacteristicProperty, CharacteristicProperties, Handle}; | 
 |     use bt_gatt::test_utils::*; | 
 |  | 
 |     #[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::create(Box::new(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::create(Box::new(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::create(Box::new(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::create(Box::new(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"); | 
 |         }; | 
 |     } | 
 | } |