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 {