blob: d10e6da7116122a040321318aabe919388d304eb [file] [log] [blame]
// 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");
};
}
}