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