rust/bt-bap-unicast: Implement discovery skeleton and mapping - Create src/mapping.rs with skeleton mapping logic and tests. - Update BapUnicastClient to include discover method returning dummy mapping. - Update BapUnicastManager::add_peer to return the mapping. - Derive Hash for Configuration and CodecCapabilitySetting to allow them as HashMap keys. - Declare mapping module in lib.rs. Test: unit tests passed Bug: 433288675 Change-Id: I7f281de4cf050f8c9630fa453274c2796a6a6964 Reviewed-on: https://bluetooth-review.googlesource.com/c/bluetooth/+/3062
diff --git a/rust/bt-bap-unicast/src/client.rs b/rust/bt-bap-unicast/src/client.rs index 14bd217..a89532d 100644 --- a/rust/bt-bap-unicast/src/client.rs +++ b/rust/bt-bap-unicast/src/client.rs
@@ -5,6 +5,8 @@ use bt_common::PeerId; 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, @@ -21,4 +23,19 @@ 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> { + // TODO: Implement actual discovery. + // Using dummy data for now to trigger skeleton mapping logic. + let pac_records = vec![bt_pacs::PacRecord { + codec_id: bt_common::core::CodecId::Assigned(bt_common::core::CodingFormat::Lc3), + codec_specific_capabilities: vec![], + metadata: vec![], + }]; + let sink_ase_count = 1; + let source_ase_count = 1; + + Ok(crate::mapping::map_capabilities(&pac_records, sink_ase_count, source_ase_count)) + } }
diff --git a/rust/bt-bap-unicast/src/lib.rs b/rust/bt-bap-unicast/src/lib.rs index 9b54d52..12dc7b0 100644 --- a/rust/bt-bap-unicast/src/lib.rs +++ b/rust/bt-bap-unicast/src/lib.rs
@@ -4,6 +4,7 @@ pub mod client; pub mod manager; +pub mod mapping; pub mod types; pub use client::BapUnicastClient;
diff --git a/rust/bt-bap-unicast/src/manager.rs b/rust/bt-bap-unicast/src/manager.rs index 6912b1e..58e770b 100644 --- a/rust/bt-bap-unicast/src/manager.rs +++ b/rust/bt-bap-unicast/src/manager.rs
@@ -2,12 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use crate::client::BapUnicastClient; -use crate::types::Error; use bt_common::PeerId; use bt_gatt::GattTypes; use std::collections::HashMap; +use crate::client::BapUnicastClient; +use crate::types::{Error, SupportedCapabilities}; + /// Manages multiple BAP Unicast peers. pub struct BapUnicastManager<T: GattTypes> { peers: HashMap<PeerId, BapUnicastClient<T>>, @@ -21,14 +22,19 @@ /// Add a peer to the manager. /// Triggers the connection and discovery of PACS and ASCS on the remote /// peer. - pub async fn add_peer(&mut self, peer_id: PeerId, gatt_client: T::Client) -> Result<(), Error> { + pub async fn add_peer( + &mut self, + peer_id: PeerId, + gatt_client: T::Client, + ) -> Result<SupportedCapabilities, Error> { if self.peers.contains_key(&peer_id) { return Err(Error::PeerAlreadyExists(peer_id)); } - let client = BapUnicastClient::new(peer_id, gatt_client); + let mut client = BapUnicastClient::new(peer_id, gatt_client); + let mapping = client.discover().await?; self.peers.insert(peer_id, client); - Ok(()) + Ok(mapping) } /// Remove a peer from the manager. @@ -43,6 +49,7 @@ #[cfg(test)] mod tests { use super::*; + use crate::Configuration; use bt_common::PeerId; use bt_gatt::test_utils::{FakeClient, FakeTypes}; @@ -52,7 +59,11 @@ let peer_id = PeerId(1); let client = FakeClient::new(); - assert!(futures::executor::block_on(manager.add_peer(peer_id, client.clone())).is_ok()); + let mapping = + futures::executor::block_on(manager.add_peer(peer_id, client.clone())).unwrap(); + assert!(mapping.contains_key(&Configuration::MonoAudioSink)); + assert!(mapping.contains_key(&Configuration::Conversational)); + assert!(futures::executor::block_on(manager.add_peer(peer_id, client.clone())).is_err()); // Duplicate assert!(manager.remove_peer(peer_id).is_ok());
diff --git a/rust/bt-bap-unicast/src/mapping.rs b/rust/bt-bap-unicast/src/mapping.rs new file mode 100644 index 0000000..745ed63 --- /dev/null +++ b/rust/bt-bap-unicast/src/mapping.rs
@@ -0,0 +1,68 @@ +// 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 crate::types::{CodecCapabilitySetting, Configuration}; +use bt_pacs::PacRecord; +use std::collections::HashMap; + +/// Maps discovered capabilities and endpoints to supported configurations. +pub fn map_capabilities( + pac_records: &[PacRecord], + sink_ase_count: usize, + source_ase_count: usize, +) -> HashMap<Configuration, Vec<CodecCapabilitySetting>> { + let mut mapping = HashMap::new(); + + // TODO: Implement actual mapping logic based on PAC records. + // This is a skeleton implementation that returns hardcoded values if resources + // are available. + + if pac_records.is_empty() { + return mapping; + } + + // Configuration 1: Mono Audio Sink (Client to Server) + // Requires 1 Sink ASE. + if sink_ase_count >= 1 { + mapping.insert( + Configuration::MonoAudioSink, + vec![CodecCapabilitySetting::Lc3_48_2, CodecCapabilitySetting::Lc3_16_2], + ); + } + + // Configuration 3: Conversational (Bidirectional) + // Requires 1 Sink ASE and 1 Source ASE. + if sink_ase_count >= 1 && source_ase_count >= 1 { + mapping.insert(Configuration::Conversational, vec![CodecCapabilitySetting::Lc3_16_2]); + } + + mapping +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_map_capabilities_empty() { + let mapping = map_capabilities(&[], 0, 0); + assert!(mapping.is_empty()); + } + + #[test] + fn test_map_capabilities_with_resources() { + // Create a dummy PacRecord + // We need to mock it or use a real one if easy. + // For now, we just need a non-empty slice to trigger the skeleton logic. + let pac_records = vec![PacRecord { + codec_id: bt_common::core::CodecId::Assigned(bt_common::core::CodingFormat::Lc3), + codec_specific_capabilities: vec![], + metadata: vec![], + }]; + + let mapping = map_capabilities(&pac_records, 1, 1); + assert!(mapping.contains_key(&Configuration::MonoAudioSink)); + assert!(mapping.contains_key(&Configuration::Conversational)); + } +}
diff --git a/rust/bt-bap-unicast/src/types.rs b/rust/bt-bap-unicast/src/types.rs index 41e922c..eac9df9 100644 --- a/rust/bt-bap-unicast/src/types.rs +++ b/rust/bt-bap-unicast/src/types.rs
@@ -8,7 +8,7 @@ /// Spec-defined configurations. /// Defined in Section 4.4 of Basic Audio Profile 1.0.1 /// TODO: Add the remaining configurations -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Configuration { /// Configuration 1: Mono Audio Sink (typically 1 Sink ASE) MonoAudioSink, @@ -18,7 +18,7 @@ } /// Codec capability settings. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum CodecCapabilitySetting { /// LC3 48 kHz, 10 ms Lc3_48_2, @@ -27,6 +27,11 @@ // TODO: Add more settings from spec. } +/// Convenience alias for mapping configurations to capabilities available on a +/// peer +pub type SupportedCapabilities = + std::collections::HashMap<Configuration, Vec<CodecCapabilitySetting>>; + /// A valid pair of configuration and codec setting supported by a peer. #[derive(Debug, Clone, PartialEq, Eq)] pub struct SupportedConfiguration {