rust/bt-bap-unicast: Initialize crate and implement foundation

- Create Cargo.toml with dependencies.
- Define core types (Configuration, CodecCapabilitySetting, Error).
- Implement BapUnicastManager and BapUnicastClient skeletons.
- Add unit tests for types and manager.

Test: unit tests included and passed
Bug: 433288675
Change-Id: If06724488b828098af037964a91068246a6a6964
Reviewed-on: https://bluetooth-review.googlesource.com/c/bluetooth/+/3060
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 076e0b5..a9f1a7d 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -11,6 +11,7 @@
 [workspace.dependencies]
 
 ## Local path dependencies (keep sorted)
+bt-ascs = { path = "bt-ascs" }
 bt-bap = { path = "bt-bap" }
 bt-bass = { path = "bt-bass" }
 bt-battery = { path = "bt-battery" }
diff --git a/rust/bt-bap-unicast/Cargo.toml b/rust/bt-bap-unicast/Cargo.toml
new file mode 100644
index 0000000..a89c152
--- /dev/null
+++ b/rust/bt-bap-unicast/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "bt-bap-unicast"
+version = "0.1.0"
+license.workspace = true
+edition.workspace = true
+
+[dependencies]
+bt-ascs.workspace = true
+bt-common.workspace = true
+bt-gatt.workspace = true
+bt-pacs.workspace = true
+futures.workspace = true
+thiserror.workspace = true
diff --git a/rust/bt-bap-unicast/DESIGN.md b/rust/bt-bap-unicast/DESIGN.md
new file mode 100644
index 0000000..0f7837a
--- /dev/null
+++ b/rust/bt-bap-unicast/DESIGN.md
@@ -0,0 +1,293 @@
+# Basic Audio Profile Unicast library
+
+An implementation of the [Basic Audio Profile 1.0.2](spec).
+
+It is assumed that a developer implementing this feature will have access to the BAP specification to reference specific tables and values not fully detailed in this document.
+
+Note: For automated tools and AI agents, specialized skills are available in the repository (under `.agents/skills/`) to read and reference the specifications.
+
+## Problem Statement
+We are designing a mid-level library to simplify the establishment and management of Bluetooth LE Audio Unicast streams (BAP 1.0.2), which currently requires complex and repetitive interactions with PACS and ASCS state machines. The "Simple Control" feature aims to automate the mapping of varied peer capabilities to standard audio configurations and handle the stream setup state machine, providing a clean API that yields data stream handles to the client while retaining flexibility for testing unsupported configurations.
+
+In our design, we strive to make this library easy to use for clients to connect and perform audio stream control operations.
+
+This library uses the bt-ascs and bt-pacs client libraries, with the goal that
+the client of this library should not need to use these libraries directly.  In
+some advanced situations, we may provide a way to use the ASCS and PACS clients
+directly.
+
+This library is focused on the Unicast roles defined in the spec: Unicast
+Client and Unicast Server.
+
+The current implementation status can be found in TODO.md.
+
+## Basic design
+
+The Specification defines a number of specific capabilities which must or may
+be supported by the Unicast Server:
+   * Audio Capabilities (Table 7 & Table 13)
+   * QoS Configurations (Table 56)
+
+It also defines a set of audio configurations for multiple channel audio in
+section 4.4.
+
+This version of the library provides simple control for audio streams. Fine-grained control is deferred to a later version; clients requiring fine-grained control should use the underlying libraries directly for now.
+
+In either control method, once audio is started, the set of CIS along with
+the codec configurations can be retrieved and provided to the client, so it
+can be passed to an audio system which may need to send/receive audio.
+
+
+### Simple Control
+
+The simple control method is managed by a `BapUnicastManager` which can track multiple peers (identified by `PeerId`). It provides a high-level interface for clients to establish and control audio streams based on spec-defined configurations.
+
+#### Initialization and Discovery
+The manager is initialized without side effects. Calling the asynchronous `add_peer(PeerId)` triggers the connection and discovery of PACS and ASCS on the remote peer. This method returns the initial mapping of supported spec-defined **Configurations** to the set of valid **Codec Capability Settings** (e.g., `LC3_48_2`) that the peer supports, along with a stream of events for dynamic updates. If discovery fails, the method returns an error immediately.
+
+#### Stream Control API
+The manager provides the following asynchronous operations:
+*   **`start_streaming(PeerId, Configuration, CodecSetting)`**: Initiates the ASCS state machine to configure and enable the stream. It returns the established CIS objects (from `bt_gatt::connected_isochronous`) to the client, handing over ownership of the data plane.
+*   **`pause_streaming(PeerId, Option<ConnectedIsochronousStream>)`**: Moves the stream to a paused state (typically mapping to `QoS Configured` in ASCS). The client can indicate it expects to close the CIS to save power by returning the CIS object to the library. If omitted, the library attempts to keep the CIS connected to minimize resume latency.
+*   **`resume_streaming(PeerId)`**: Re-enables a paused stream. If the CIS was closed during pause, the library will re-establish it.
+*   **`stop_streaming(PeerId, ConnectedIsochronousStream)`**: Disables and releases the streams, returning the ASEs to the `Idle` state. The client must return the CIS object to the library so it can handle the teardown.
+
+#### Peer Events
+The manager provides a stream of events for each peer to notify the client of dynamic changes:
+*   **`CapabilitiesChanged`**: Emitted when the peer's available audio contexts or capabilities change (discovered via PACS notifications). It provides the updated mapping of supported configurations.
+*   **`StreamStateChanged`**: Emitted when a stream's state changes (e.g., due to peer-initiated transitions or autonomous release).
+
+#### Design Principles
+*   **Prescriptive Execution**: The manager acts as a direct executor of the client's request. If a state transition fails (e.g., QoS rejected by the peer), the manager fails immediately and returns the error to the client rather than attempting to negotiate a fallback.
+*   **Trusting Validation**: While the manager returns a list of supported configurations, it does not strictly forbid the client from attempting to start a configuration not in that list. If the client provides a valid spec configuration, the manager will attempt to execute it.
+*   **State Tracking**: The manager tracks the active state of streams (which ASEs are in which state) to facilitate control operations, but does not update its stored capabilities list for a peer based on forced successful configurations.
+
+```mermaid
+sequenceDiagram
+    participant Client
+    participant Manager
+    participant Peer (ASCS/PACS)
+    Client->>Manager: add_peer(PeerId)
+    Manager->>Peer: Discover PACS & ASCS
+    Peer-->>Manager: Capabilities & ASEs
+    Manager-->>Client: Map<Configuration, Set<CodecSetting>>
+    Client->>Manager: start_streaming(PeerId, Config, Setting)
+    Manager->>Peer: Config Codec LTVs
+    Manager->>Peer: Config QoS
+    Manager->>Peer: Enable
+    Peer-->>Manager: CIS Established
+    Manager-->>Client: CIS Handles
+```
+
+### Fine grained control
+
+Fine-grained control is deferred to a later version of this library. Clients requiring fine-grained control over endpoints and specific state transitions should use the `bt-ascs` and `bt-pacs` libraries directly.
+
+## Error Handling
+
+The library will define a centralized `Error` enum to represent failures in stream management and control operations.
+
+### `src/error.rs` or `src/types.rs`
+
+```rust
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    /// An error occurred in the underlying GATT interaction.
+    #[error("GATT error: {0}")]
+    Gatt(bt_gatt::Error),
+
+    /// An error occurred in the ASCS client.
+    #[error("ASCS error: {0}")]
+    Ascs(bt_ascs::Error),
+
+    /// The requested peer was not found in the manager.
+    #[error("Peer {0} not found")]
+    PeerNotFound(PeerId),
+
+    /// The operation requested is invalid in the current state.
+    #[error("Invalid state: {0}")]
+    InvalidState(String),
+
+    /// The configuration requested is not valid according to the spec.
+    #[error("Invalid configuration: {0}")]
+    InvalidConfiguration(String),
+
+    /// The peer rejected the operation (e.g. QoS or Codec configuration rejected).
+    #[error("Peer rejected operation: {0}")]
+    PeerRejected(String),
+
+    /// An internal error occurred.
+    #[error("Internal error: {0}")]
+    Internal(String),
+}
+```
+
+### Usage Scenarios
+
+*   **`Gatt`**: Used when reading/writing characteristics fails during discovery or control point operations.
+*   **`Ascs`**: Used when the underlying `bt-ascs` library returns an error, e.g., failure to parse ASCS notifications.
+*   **`PeerNotFound`**: Returned by `start_streaming`, `pause_streaming`, etc., if the provided `PeerId` has not been added to the manager via `add_peer`.
+*   **`InvalidState`**: Returned if the client attempts to `resume_streaming` on a stream that is not paused, or `start_streaming` on a peer that is already streaming.
+*   **`InvalidConfiguration`**: Returned if the client requests a spec configuration that is known to be invalid or unsupported by the library's current implementation level.
+*   **`PeerRejected`**: Returned when the peer explicitly rejects a state transition in the ASCS control point (e.g., returning a failure response code for Config Codec or Config QoS).
+
+## Files to be Created
+
+To implement the Simple Control feature, we will create the following files in the `src/` directory:
+
+#### `src/lib.rs`
+*   **Implementation**: The library root. It will declare the modules (`manager`, `client`, `types`, `mapping`) and re-export the public-facing API (like the `BapUnicastManager` and the configuration enums).
+*   **Rationale**: Standard Rust convention for a library crate. It defines the public surface area.
+
+#### `src/manager.rs`
+*   **Implementation**: Contains the `BapUnicastManager` struct. It will handle the collection of peers, mapped by their `PeerId`. It will contain the `add_peer` function and delegate the actual stream control operations to the specific client instance for that peer.
+*   **Rationale**: Fulfills the requirement to have a manager that can track multiple peers to support future multi-device configurations, while keeping the top-level API clean.
+
+#### `src/client.rs`
+*   **Implementation**: Contains a `BapUnicastClient` struct representing a connection to a *single* peer. This is where the core logic lives:
+    *   Driving the ASCS state machine (Prescriptive execution).
+    *   Interacting with `bt-pacs` and `bt-ascs`.
+    *   Handling the "Trusting" validation where it attempts any valid configuration requested.
+    *   Tracking the **Active State** of the streams on that specific peer.
+*   **Rationale**: Separates the "collection of peers" from the "single peer interaction", keeping the code modular and easier to maintain and test.
+
+#### `src/types.rs`
+*   **Implementation**: Defines the strongly-typed enums and structs:
+    *   An enum for spec-defined **Configurations** (e.g., `MonoAudioSource`, `Conversational`).
+    *   An enum for **Codec Capability Settings** (e.g., `Lc3_48_2`).
+    *   Structs to represent the valid pairs returned to the user.
+*   **Rationale**: Centralizes the definitions derived from the Bluetooth specification, ensuring type safety across the library.
+
+#### `src/mapping.rs`
+*   **Implementation**: Contains the logic that looks at the raw capabilities returned by `bt-pacs` (frequencies, frame durations) and the endpoints available in `bt-ascs`, and calculates which of our strongly-typed spec configurations are actually possible.
+*   **Rationale**: This is the "main value" processing engine of the library. Keeping it in its own file prevents other files from becoming massive and hard to read.
+
+#### `tests/tests.rs`
+*   **Implementation**: This will use the public API of the library to simulate full use cases. It will likely use mock implementations of the underlying Bluetooth services to simulate remote peers.
+*   **Rationale**: Ensures that all components work together correctly and that the public API behaves as expected without requiring real hardware.
+
+## Integration with Dependencies
+
+The library relies on `bt-pacs` and `bt-ascs` to interact with the remote peer's GATT services, and `bt-common` for shared types.
+
+### `bt-common`
+
+`bt-common` provides shared types and structures, including the LTV (Length-Type-Value) structures used for codec capabilities and configurations.
+
+
+### `bt-pacs`
+
+`bt-pacs` provides types for parsing Published Audio Capabilities (PAC) records and Audio Locations. The library reads these from the remote peer's PACS service.
+
+Key types used from `bt-pacs`:
+*   **`PacRecord`**: Represents a capability record.
+    ```rust
+    pub struct PacRecord {
+        pub codec_id: CodecId,
+        pub codec_specific_capabilities: Vec<CodecCapability>,
+        pub metadata: Vec<Metadata>,
+    }
+    ```
+    `CodecCapability` (from `bt-common`) contains the supported parameters like sampling frequencies and frame durations.
+
+### `bt-ascs`
+
+The library uses `AudioStreamControlServiceClient` from `bt-ascs::client` to drive the ASCS state machine.
+
+**API Contract for `AudioStreamControlServiceClient`:**
+
+```rust
+impl<T: bt_gatt::GattTypes> AudioStreamControlServiceClient<T> {
+    /// Discover the control point and all endpoints on the server.
+    pub async fn create(gatt_client: T::PeerService) -> Result<Self, Error>;
+
+    /// Configure the codec for a set of ASEs.
+    pub async fn configure_codec(
+        &mut self,
+        codec_configurations: Vec<CodecConfiguration>,
+    ) -> Result<OperationResult<(AseId, QosParameters)>, Error>;
+
+    /// Configure QoS for a set of ASEs.
+    pub async fn configure_qos(
+        &mut self,
+        qos_configurations: Vec<QosConfiguration>,
+    ) -> Result<OperationResult<AseId>, Error>;
+
+    /// Enable a set of ASEs.
+    pub async fn enable(
+        &mut self,
+        ases_with_metadata: Vec<AseIdWithMetadata>,
+    ) -> Result<OperationResult<AseId>, Error>;
+}
+```
+
+**Key Parameter Types:**
+
+*   **`CodecConfiguration`**:
+    ```rust
+    pub struct CodecConfiguration {
+        pub ase_id: AseId,
+        pub target_latency: TargetLatency,
+        pub target_phy: TargetPhy,
+        pub codec_id: CodecId,
+        pub codec_specific_configuration: Vec<u8>, // LTV encoded
+    }
+    ```
+
+    > [!NOTE]
+    > The LTV structures used for `codec_specific_configuration` are provided by the `bt-common` crate.
+
+*   **`QosConfiguration`**:
+    ```rust
+    pub struct QosConfiguration {
+        pub ase_id: AseId,
+        pub cig_id: CigId,
+        pub cis_id: CisId,
+        pub sdu_interval: SduInterval,
+        pub framing: Framing,
+        pub phy: Vec<Phy>,
+        pub max_sdu: MaxSdu,
+        pub retransmission_number: u8,
+        pub max_transport_latency: MaxTransportLatency,
+        pub presentation_delay: PresentationDelay,
+    }
+    ```
+*   **`AseIdWithMetadata`**:
+    ```rust
+    pub struct AseIdWithMetadata {
+        pub ase_id: AseId,
+        pub metadata: Vec<Metadata>,
+    }
+    ```
+
+## Mapping Details
+
+The library automates the mapping of raw peer capabilities and endpoints to standard BAP configurations. This logic resides in `src/mapping.rs`.
+
+### Inputs
+*   **PACS Capabilities**: Discovered via `bt-pacs`, including supported sampling frequencies, frame durations, channel counts, and max supported codec frames per SDU.
+*   **ASCS Endpoints**: Discovered via `bt-ascs`, specifically the number of available Sink and Source ASEs (Audio Stream Endpoints).
+
+### Outputs
+*   A mapping of strongly-typed **Configurations** (e.g., `Audio Configuration 1`) to a set of compatible **Codec Capability Settings** (e.g., `LC3_48_2`).
+
+### Mapping Algorithm
+For each standard BAP configuration defined in the specification (e.g., Section 4.4; see also Tables 7, 13, and 56 for specific capability and QoS parameters):
+1.  **Direction & ASE Check**: Verify the peer supports the required direction (Sink or Source) and has a sufficient number of ASEs available for that configuration.
+    *   *Example*: `Audio Configuration 1` requires 1 Sink ASE.
+2.  **Codec Capability Intersection**: Intersect the peer's supported PAC parameters with the requirements of each `CodecSetting`.
+    *   Check if the peer supports the required sampling frequency (e.g., 48 kHz).
+    *   Check if the peer supports the required frame duration (e.g., 10 ms).
+    *   Check if the peer supports the required channel count.
+3.  **Result**: If a configuration has at least one matching `CodecSetting` and sufficient ASEs, it is included in the returned map for the client to choose from.
+
+## Alternatives / Future Plans
+
+*   **High-Level Intent API**: We considered exposing semantic intents (e.g., "Play Music") instead of spec configurations, but ruled it out to give mid-level library users precise control.
+*   **Multi-Device Coordination**: We ruled out multi-device synchronization (e.g., true wireless stereo) for the MVP to reduce complexity, but kept the `BapUnicastManager` structure to support it in the future.
+*   **Automatic Negotiation**: We opted for a prescriptive approach rather than automatic fallback negotiation when a peer rejects a configuration, keeping the implementation predictable.
+
+## Testing
+
+*   **Unit Tests**: Will be included in each file (e.g., testing mapping logic in `src/mapping.rs` with fake data).
+*   **Integration Tests**: A new file `tests/tests.rs` will be created to test the public API of the `BapUnicastManager` with mock peer connections.
diff --git a/rust/bt-bap-unicast/LICENSE b/rust/bt-bap-unicast/LICENSE
new file mode 120000
index 0000000..30cff74
--- /dev/null
+++ b/rust/bt-bap-unicast/LICENSE
@@ -0,0 +1 @@
+../../LICENSE
\ No newline at end of file
diff --git a/rust/bt-bap-unicast/TODO.md b/rust/bt-bap-unicast/TODO.md
new file mode 100644
index 0000000..cfd2b2e
--- /dev/null
+++ b/rust/bt-bap-unicast/TODO.md
@@ -0,0 +1,27 @@
+# TODO: BAP Unicast Simple Control Implementation
+
+## Phase 1: Foundation and Discovery
+ - [x] Define core types for Configurations and Codec Capability Settings (enums/structs).
+ - [x] Implement `BapUnicastManager` skeleton with `PeerId` mapping.
+ - [ ] Implement `add_peer` function:
+    - [ ] Connect to PACS and read capabilities.
+    - [ ] Connect to ASCS and discover ASEs.
+    - [ ] Implement mapping logic from raw PACS/ASCS data to spec-defined Configurations.
+
+## Phase 2: Stream Control (Prescriptive)
+ - [ ] Implement `start_streaming`:
+    - [ ] Generate Codec Specific Configuration LTVs for requested setting.
+    - [ ] Drive ASCS state machine (Config Codec, Config QoS, Enable).
+    - [ ] Extract and return CIS handles upon success.
+ - [ ] Implement `stop_streaming` / `release`:
+     - [ ] Drive ASCS state machine back to Idle.
+     - [ ] Ensure CIS is released.
+
+## Phase 3: Pause and Resume
+ - [ ] Implement `pause_streaming`:
+     - [ ] Move ASEs to a state that retains CIS (e.g., QoS Configured or custom handling).
+ - [ ] Implement `resume_streaming`:
+     - [ ] Move ASEs back to Streaming state.
+
+## Phase 4: Testing Support
+ - [ ] Ensure `start_streaming` skips capability validation check against discovered PACS data to allow testing unsupported configurations.
diff --git a/rust/bt-bap-unicast/src/client.rs b/rust/bt-bap-unicast/src/client.rs
new file mode 100644
index 0000000..b4ace67
--- /dev/null
+++ b/rust/bt-bap-unicast/src/client.rs
@@ -0,0 +1,22 @@
+// 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;
+
+/// Represents a connection to a single peer and manages its audio streams.
+#[derive(Debug)]
+pub struct BapUnicastClient {
+    peer_id: PeerId,
+    // TODO: Add ASCS and PACS clients here.
+}
+
+impl BapUnicastClient {
+    pub fn new(peer_id: PeerId) -> Self {
+        Self { peer_id }
+    }
+
+    pub fn peer_id(&self) -> PeerId {
+        self.peer_id
+    }
+}
diff --git a/rust/bt-bap-unicast/src/lib.rs b/rust/bt-bap-unicast/src/lib.rs
new file mode 100644
index 0000000..9b54d52
--- /dev/null
+++ b/rust/bt-bap-unicast/src/lib.rs
@@ -0,0 +1,11 @@
+// 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.
+
+pub mod client;
+pub mod manager;
+pub mod types;
+
+pub use client::BapUnicastClient;
+pub use manager::BapUnicastManager;
+pub use types::{CodecCapabilitySetting, Configuration, Error, SupportedConfiguration};
diff --git a/rust/bt-bap-unicast/src/manager.rs b/rust/bt-bap-unicast/src/manager.rs
new file mode 100644
index 0000000..b33b427
--- /dev/null
+++ b/rust/bt-bap-unicast/src/manager.rs
@@ -0,0 +1,58 @@
+// 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::client::BapUnicastClient;
+use crate::types::Error;
+use bt_common::PeerId;
+use std::collections::HashMap;
+
+/// Manages multiple BAP Unicast peers.
+#[derive(Debug, Default)]
+pub struct BapUnicastManager {
+    peers: HashMap<PeerId, BapUnicastClient>,
+}
+
+impl BapUnicastManager {
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Add a peer to the manager.
+    /// In the future, this will trigger discovery.
+    pub async fn add_peer(&mut self, peer_id: PeerId) -> Result<(), Error> {
+        if self.peers.contains_key(&peer_id) {
+            return Err(Error::PeerAlreadyExists(peer_id));
+        }
+
+        let client = BapUnicastClient::new(peer_id);
+        self.peers.insert(peer_id, client);
+        Ok(())
+    }
+
+    /// Remove a peer from the manager.
+    pub fn remove_peer(&mut self, peer_id: PeerId) -> Result<(), Error> {
+        if self.peers.remove(&peer_id).is_none() {
+            return Err(Error::PeerNotFound(peer_id));
+        }
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use bt_common::PeerId;
+
+    #[test]
+    fn test_add_remove_peer() {
+        let mut manager = BapUnicastManager::new();
+        let peer_id = PeerId(1);
+
+        assert!(futures::executor::block_on(manager.add_peer(peer_id)).is_ok());
+        assert!(futures::executor::block_on(manager.add_peer(peer_id)).is_err()); // Duplicate
+
+        assert!(manager.remove_peer(peer_id).is_ok());
+        assert!(manager.remove_peer(peer_id).is_err()); // Not found
+    }
+}
diff --git a/rust/bt-bap-unicast/src/types.rs b/rust/bt-bap-unicast/src/types.rs
new file mode 100644
index 0000000..41e922c
--- /dev/null
+++ b/rust/bt-bap-unicast/src/types.rs
@@ -0,0 +1,86 @@
+// 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 thiserror::Error;
+
+/// 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)]
+pub enum Configuration {
+    /// Configuration 1: Mono Audio Sink (typically 1 Sink ASE)
+    MonoAudioSink,
+    /// Configuration 3: Conversational (typically 1 Sink and 1 Source ASE)
+    Conversational,
+    // TODO: Add more configurations from spec.
+}
+
+/// Codec capability settings.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum CodecCapabilitySetting {
+    /// LC3 48 kHz, 10 ms
+    Lc3_48_2,
+    /// LC3 16 kHz, 10 ms
+    Lc3_16_2,
+    // TODO: Add more settings from spec.
+}
+
+/// A valid pair of configuration and codec setting supported by a peer.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct SupportedConfiguration {
+    pub configuration: Configuration,
+    pub codec_setting: CodecCapabilitySetting,
+}
+
+/// Centralized Error enum for the library.
+#[derive(Debug, Error)]
+pub enum Error {
+    /// An error occurred in the underlying GATT interaction.
+    #[error("GATT error: {0}")]
+    Gatt(#[from] bt_gatt::types::Error),
+
+    /// An error occurred in the ASCS client.
+    #[error("ASCS error: {0}")]
+    Ascs(#[from] bt_ascs::Error),
+
+    /// The requested peer was not found in the manager.
+    #[error("Peer {0:?} not found")]
+    PeerNotFound(PeerId),
+
+    /// The peer already exists in the manager, and can't be added.
+    #[error("Peer {0:?} already exists")]
+    PeerAlreadyExists(PeerId),
+
+    /// The operation requested is invalid in the current state.
+    #[error("Invalid state: {0}")]
+    InvalidState(String),
+
+    /// The configuration requested is not valid according to the spec.
+    #[error("Invalid configuration: {0}")]
+    InvalidConfiguration(String),
+
+    /// The peer rejected the operation (e.g. QoS or Codec configuration
+    /// rejected).
+    #[error("Peer rejected operation: {0}")]
+    PeerRejected(String),
+
+    /// An internal error occurred.
+    #[error("Internal error: {0}")]
+    Internal(String),
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_supported_configuration() {
+        let config = Configuration::MonoAudioSink;
+        let setting = CodecCapabilitySetting::Lc3_48_2;
+        let supported = SupportedConfiguration { configuration: config, codec_setting: setting };
+        assert_eq!(supported.configuration, Configuration::MonoAudioSink);
+        assert_eq!(supported.codec_setting, CodecCapabilitySetting::Lc3_48_2);
+    }
+}