|  | // 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"); | 
|  | }; | 
|  | } | 
|  | } |