blob: f9ba7fd22d13199a43d55683f2d06a7c2dafcdc1 [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::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");
};
}
}