blob: eb44a1db86d50486f9cc4ca71bf0e750a8216cbc [file] [edit]
// Copyright 2026 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use bt_common::PeerId;
use bt_gatt::client::FromCharacteristic;
use bt_gatt::client::PeerService;
use bt_gatt::client::PeerServiceHandle;
use bt_gatt::Client;
use bt_gatt::GattTypes;
use crate::types::{Error, SupportedCapabilities};
/// Represents a connection to a single peer and manages its audio streams.
pub struct BapUnicastClient<T: GattTypes> {
peer_id: PeerId,
#[allow(unused)]
gatt_client: T::Client,
// TODO: Add ASCS and PACS clients here.
}
impl<T: GattTypes> BapUnicastClient<T> {
pub fn new(peer_id: PeerId, gatt_client: T::Client) -> Self {
Self { peer_id, gatt_client }
}
pub fn peer_id(&self) -> PeerId {
self.peer_id
}
/// Discovers PACS and ASCS and returns supported configurations.
pub async fn discover(&mut self) -> Result<SupportedCapabilities, Error>
where
<T as GattTypes>::NotificationStream: Send,
{
// 1. Discover PACS and read capabilities.
let pacs_handles = self.gatt_client.find_service(bt_pacs::PACS_UUID).await?;
if pacs_handles.len() != 1 {
return Err(Error::Configuration(format!(
"{} PACS service(s) - not unique",
pacs_handles.len()
)));
}
let handle = &pacs_handles[0];
let mut pac_records = vec![];
let service = handle.connect().await?;
let chrs = service.discover_characteristics(None).await?;
for chr in chrs {
if chr.uuid == bt_pacs::SourcePac::UUID {
let source_pac = bt_pacs::SourcePac::try_read::<T>(chr, &service).await?;
pac_records.extend(source_pac.capabilities);
} else if chr.uuid == bt_pacs::SinkPac::UUID {
let sink_pac = bt_pacs::SinkPac::try_read::<T>(chr, &service).await?;
pac_records.extend(sink_pac.capabilities);
}
}
// 2. Discover ASCS and count endpoints.
let ascs_handles = self.gatt_client.find_service(bt_ascs::server::ASCS_UUID).await?;
if ascs_handles.len() != 1 {
return Err(Error::Configuration(format!(
"{} ASCS service(s) - not unique",
ascs_handles.len()
)));
}
let handle = &ascs_handles[0];
let mut sink_ase_count = 0;
let mut source_ase_count = 0;
let service = handle.connect().await?;
let ascs_client =
bt_ascs::client::AudioStreamControlServiceClient::<T>::create(service).await?;
sink_ase_count += ascs_client.endpoints.sink_ases().len();
source_ase_count += ascs_client.endpoints.source_ases().len();
Ok(crate::mapping::map_capabilities(&pac_records, sink_ase_count, source_ase_count))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{setup_ascs_service, setup_pacs_service};
use crate::Configuration;
use bt_common::PeerId;
use bt_gatt::test_utils::{FakeClient, FakeTypes};
#[test]
fn test_discover_success() {
let mut client = FakeClient::new();
let _pacs_service = setup_pacs_service(&mut client);
let _ascs_service = setup_ascs_service(&mut client, 1, 1);
let mut unicast_client = BapUnicastClient::<FakeTypes>::new(PeerId(1), client);
let mapping = futures::executor::block_on(unicast_client.discover())
.expect("discovery should succeed");
assert!(mapping.contains_key(&Configuration::MonoAudioSink));
assert!(mapping.contains_key(&Configuration::Conversational));
}
#[test]
fn test_discover_no_services() {
let client = FakeClient::new();
let mut unicast_client = BapUnicastClient::<FakeTypes>::new(PeerId(1), client);
let result = futures::executor::block_on(unicast_client.discover());
assert!(matches!(result, Err(Error::Configuration(_))));
}
#[test]
fn test_discover_no_pacs_service() {
let mut client = FakeClient::new();
let _ascs_service = setup_ascs_service(&mut client, 1, 1);
let mut unicast_client = BapUnicastClient::<FakeTypes>::new(PeerId(1), client);
let result = futures::executor::block_on(unicast_client.discover());
assert!(matches!(result, Err(Error::Configuration(_))));
}
#[test]
fn test_discover_multiple_pacs_services() {
let mut client = FakeClient::new();
let _pacs_service1 = setup_pacs_service(&mut client);
let _pacs_service2 = setup_pacs_service(&mut client);
let _ascs_service = setup_ascs_service(&mut client, 1, 1);
let mut unicast_client = BapUnicastClient::<FakeTypes>::new(PeerId(1), client);
let result = futures::executor::block_on(unicast_client.discover());
assert!(matches!(result, Err(Error::Configuration(_))));
}
#[test]
fn test_discover_no_ascs_service() {
let mut client = FakeClient::new();
let _pacs_service = setup_pacs_service(&mut client);
let mut unicast_client = BapUnicastClient::<FakeTypes>::new(PeerId(1), client);
let result = futures::executor::block_on(unicast_client.discover());
assert!(matches!(result, Err(Error::Configuration(_))));
}
#[test]
fn test_discover_multiple_ascs_services() {
let mut client = FakeClient::new();
let _pacs_service = setup_pacs_service(&mut client);
let _ascs_service1 = setup_ascs_service(&mut client, 1, 1);
let _ascs_service2 = setup_ascs_service(&mut client, 1, 1);
let mut unicast_client = BapUnicastClient::<FakeTypes>::new(PeerId(1), client);
let result = futures::executor::block_on(unicast_client.discover());
assert!(matches!(result, Err(Error::Configuration(_))));
}
}