rust/bt-ascs: Implement remaining operations

Add operation handling for:
 - QoS Configure
 - Enable
 - Disable
 - Receiver Start Ready
 - Release
 - Receiver Stop Ready
 - Released

Bug: b/309015034
Change-Id: I747b6081bc4c0508dde2603bf70874c1ed73b2d1
Reviewed-on: https://bluetooth-review.googlesource.com/c/bluetooth/+/1760
diff --git a/rust/bt-ascs/src/server.rs b/rust/bt-ascs/src/server.rs
index 49b1225..982968f 100644
--- a/rust/bt-ascs/src/server.rs
+++ b/rust/bt-ascs/src/server.rs
@@ -2,7 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+use bt_common::core::ltv::LtValue;
 use bt_common::core::CodecId;
+use bt_common::generic_audio::metadata_ltv::Metadata;
 use bt_common::packet_encoding::Encodable;
 use bt_common::{PeerId, Uuid};
 use bt_gatt::server::{LocalService, Server, ServiceDefinition, ServiceId};
@@ -143,15 +145,18 @@
             return None;
         }
         let mut notification = Vec::with_capacity(2 + self.response_codes.len() * 3);
-        // Opcode and Number_of_ASEs
+        // Opcode
         notification.push(self.opcode.unwrap().into());
-        if self.response_codes[0].ase_id_value() == 0x00 {
+        if let ResponseCode::InvalidLength { .. } | ResponseCode::UnsupportedOpcode { .. } =
+            self.response_codes[0]
+        {
             // UnsupportedOpcode or InvalidLength. Number_of_ASEs shall be set to 0xFF
             // See ASCS v1.0.1 Table 4.7.  We only include the first response_code.
             notification.push(0xFF);
             notification.extend(self.response_codes[0].notify_value());
             return Some(notification);
         }
+        // Number_of_ASEs
         notification.push(self.response_codes.len() as u8);
         for response in &self.response_codes {
             notification.extend(response.notify_value());
@@ -364,7 +369,6 @@
 enum AseAdditionalParameters {
     /// When in states with no additional parameters: Idle, Releasing
     None,
-    /// When CodecConfigured
     CodecConfigured {
         framing: Framing,
         preferred_phys: Vec<Phy>,
@@ -374,6 +378,16 @@
         codec_id: CodecId,
         codec_config: Vec<u8>,
     },
+    QosConfigured {
+        configuration: QosConfiguration,
+    },
+    /// When Enabling, Streaming, or Disabling
+    Streaming {
+        cig_id: CigId,
+        cis_id: CisId,
+        metadata: Vec<Metadata>,
+        qos_configured: QosConfiguration,
+    },
 }
 
 impl AseAdditionalParameters {
@@ -383,6 +397,10 @@
             AseAdditionalParameters::CodecConfigured { codec_config, .. } => {
                 23 + codec_config.len()
             }
+            AseAdditionalParameters::QosConfigured { .. } => 15,
+            AseAdditionalParameters::Streaming { metadata, .. } => {
+                metadata.iter().fold(3, |total, m| total + m.encoded_len() as usize)
+            }
         }
     }
     fn into_char_value(&self) -> Vec<u8> {
@@ -409,10 +427,53 @@
                 value.extend(codec_config.clone());
                 value
             }
+            AseAdditionalParameters::QosConfigured {
+                configuration:
+                    QosConfiguration {
+                        cig_id,
+                        cis_id,
+                        sdu_interval,
+                        framing,
+                        phy,
+                        max_sdu,
+                        retransmission_number,
+                        max_transport_latency,
+                        presentation_delay,
+                        ..
+                    },
+            } => {
+                let mut value = Vec::with_capacity(self.char_size());
+                value.resize(self.char_size(), 0);
+                cig_id.encode(&mut value[0..]).unwrap();
+                cis_id.encode(&mut value[1..]).unwrap();
+                sdu_interval.encode(&mut value[2..]).unwrap();
+                framing.encode(&mut value[5..]).unwrap();
+                value[6] = Phy::to_bits(phy.iter());
+                max_sdu.encode(&mut value[7..]).unwrap();
+                value[9] = *retransmission_number;
+                max_transport_latency.encode(&mut value[10..]).unwrap();
+                presentation_delay.encode(&mut value[12..]).unwrap();
+                value
+            }
+            AseAdditionalParameters::Streaming { cig_id, cis_id, metadata, .. } => {
+                let mut value = Vec::with_capacity(self.char_size());
+                value.resize(self.char_size(), 0);
+                cig_id.encode(&mut value[0..]).unwrap();
+                cis_id.encode(&mut value[1..]).unwrap();
+                value[2] = metadata.iter().fold(0usize, |acc, i| acc + i.encoded_len()) as u8;
+                LtValue::encode_all(metadata.clone().into_iter(), &mut value[3..]).unwrap();
+                value
+            }
         }
     }
 }
 
+impl From<QosConfiguration> for AseAdditionalParameters {
+    fn from(value: QosConfiguration) -> Self {
+        Self::QosConfigured { configuration: value }
+    }
+}
+
 #[derive(Debug, Clone)]
 struct AudioStreamEndpoint {
     handle: Handle,
@@ -430,6 +491,16 @@
         value.extend(self.additional.into_char_value());
         value
     }
+
+    fn get_cis(&self) -> Option<(CigId, CisId)> {
+        match &self.additional {
+            AseAdditionalParameters::QosConfigured { configuration } => {
+                Some((configuration.cig_id, configuration.cis_id))
+            }
+            AseAdditionalParameters::Streaming { cig_id, cis_id, .. } => Some((*cig_id, *cis_id)),
+            _ => None,
+        }
+    }
 }
 
 impl From<&AudioStreamEndpoint> for Characteristic {
@@ -529,8 +600,164 @@
 }
 
 #[derive(Debug)]
+pub struct QosConfigureResponder {
+    endpoint: AudioStreamEndpoint,
+    configuration: QosConfiguration,
+    sender: futures::channel::oneshot::Sender<Result<AudioStreamEndpoint, ResponseCode>>,
+}
+
+impl QosConfigureResponder {
+    pub fn reject(self, err: ResponseCode) {
+        let _ = self.sender.send(Err(err));
+    }
+
+    pub fn accept(mut self) {
+        self.endpoint.state = AseState::QosConfigured;
+        self.endpoint.additional = self.configuration.into();
+        let _ = self.sender.send(Ok(self.endpoint));
+    }
+}
+
+#[derive(Debug)]
+pub struct EnableResponder {
+    endpoint: AudioStreamEndpoint,
+    metadata: Vec<Metadata>,
+    sender: futures::channel::oneshot::Sender<Result<AudioStreamEndpoint, ResponseCode>>,
+}
+
+impl EnableResponder {
+    pub fn reject(self, err: ResponseCode) {
+        let _ = self.sender.send(Err(err));
+    }
+
+    pub fn accept(mut self) {
+        let AseAdditionalParameters::QosConfigured { configuration, .. } = self.endpoint.additional
+        else {
+            // Shouldn't happen
+            let _ = self
+                .sender
+                .send(Err(ResponseCode::UnspecifiedError { ase_id: self.endpoint.ase_id }));
+            return;
+        };
+        self.endpoint.state = AseState::Enabling;
+        self.endpoint.additional = AseAdditionalParameters::Streaming {
+            cig_id: configuration.cig_id,
+            cis_id: configuration.cis_id,
+            metadata: self.metadata,
+            qos_configured: configuration,
+        };
+        let _ = self.sender.send(Ok(self.endpoint));
+    }
+}
+
+#[derive(Debug)]
+pub struct DisableResponder {
+    endpoint: AudioStreamEndpoint,
+    sender: futures::channel::oneshot::Sender<Result<AudioStreamEndpoint, ResponseCode>>,
+}
+
+impl DisableResponder {
+    pub fn reject(self, err: ResponseCode) {
+        let _ = self.sender.send(Err(err));
+    }
+
+    pub fn accept(mut self) {
+        let AseAdditionalParameters::Streaming { qos_configured, .. } = self.endpoint.additional
+        else {
+            unreachable!();
+        };
+        self.endpoint.state = AseState::QosConfigured;
+        self.endpoint.additional = qos_configured.into();
+        let _ = self.sender.send(Ok(self.endpoint));
+    }
+}
+
+#[derive(Debug)]
+pub struct UpdateMetadataResponder {
+    endpoint: AudioStreamEndpoint,
+    metadata: Vec<Metadata>,
+    sender: futures::channel::oneshot::Sender<Result<AudioStreamEndpoint, ResponseCode>>,
+}
+
+impl UpdateMetadataResponder {
+    pub fn reject(self, err: ResponseCode) {
+        let _ = self.sender.send(Err(err));
+    }
+
+    pub fn accept(mut self) {
+        let AseAdditionalParameters::Streaming { metadata, .. } = &mut self.endpoint.additional
+        else {
+            unreachable!();
+        };
+        *metadata = self.metadata;
+        let _ = self.sender.send(Ok(self.endpoint));
+    }
+}
+
+#[derive(Debug)]
 pub enum ServiceEvent {
-    CodecConfigure { configuration: CodecConfiguration, responder: CodecConfigureResponder },
+    CodecConfigure {
+        configuration: CodecConfiguration,
+        responder: CodecConfigureResponder,
+    },
+    QosConfigure {
+        /// Peer configuring this stream
+        peer_id: PeerId,
+        /// Stream ID of the stream being configured
+        /// This ID is unique to the peer
+        // TODO: Consider replacing with source or sink, AseId should map to (Peer, Cig, Cis) at
+        // this point
+        target_configuration: QosConfiguration,
+        responder: QosConfigureResponder,
+    },
+    Enable {
+        /// Peer enabling this stream
+        peer_id: PeerId,
+        /// Stream ID of the stream being configured
+        ase_id: AseId,
+        /// Stream that this is being tied to.
+        /// This CIS may already be established after QosConfigure and will
+        /// match the QosConfigured value.
+        cis: (CigId, CisId),
+        /// Additional Metadata provided
+        /// Also available using
+        /// AudioStreamControlServiceServer::get_metadata(peer_id, ase_id)
+        metadata: Vec<Metadata>,
+        /// Responder.  Responding positively to this will start streaming if
+        /// the StreamEndpoint is a sink endpoint.  For a source
+        /// endpoint, an additional Start event will be generated to
+        /// indicate when the client is ready to receive data.
+        responder: EnableResponder,
+    },
+    /// Ok to start streaming.
+    /// Sent only for Source Endpoints.
+    /// No responder, this event has already been accepted.
+    Start {
+        peer_id: PeerId,
+        ase_id: AseId,
+        cis: (CigId, CisId),
+    },
+    /// Disable this stream.
+    /// Stop sending data on the CisId and CigId listed, or expect the client to
+    /// stop sending audio data.
+    Disable {
+        /// Peer disabling this stream
+        peer_id: PeerId,
+        /// Stream ID of this stream
+        ase_id: AseId,
+        /// Isochronous Stream that was previously in use.
+        cis: (CigId, CisId),
+        /// Responder.  Responding positively will indicate to the client that
+        /// data has ceased being sent.
+        responder: DisableResponder,
+    },
+    /// Update Metadata
+    UpdateMetadata {
+        peer_id: PeerId,
+        ase_id: AseId,
+        metadata: Vec<Metadata>,
+        responder: UpdateMetadataResponder,
+    },
 }
 
 impl CodecConfiguration {
@@ -551,6 +778,25 @@
     }
 }
 
+impl QosConfiguration {
+    fn into_event(
+        self,
+        peer_id: PeerId,
+        endpoint: AudioStreamEndpoint,
+        sender: oneshot::Sender<Result<AudioStreamEndpoint, ResponseCode>>,
+    ) -> crate::server::ServiceEvent {
+        ServiceEvent::QosConfigure {
+            peer_id,
+            target_configuration: self.clone(),
+            responder: crate::server::QosConfigureResponder {
+                endpoint,
+                configuration: self,
+                sender,
+            },
+        }
+    }
+}
+
 impl AseControlOperation {
     fn apply(
         self,
@@ -560,6 +806,7 @@
         let mut current_response_codes = Vec::new();
         let mut waiting = Vec::new();
         let mut events: Vec<crate::server::ServiceEvent> = Vec::new();
+        let mut endpoints = Vec::new();
         let opcode: Option<AseControlPointOpcode> = (&self).try_into().ok();
         match self {
             Self::ConfigCodec { codec_configurations, mut responses } => {
@@ -581,7 +828,205 @@
                     events.push(codec_configuration.into_event(endpoint.clone(), send));
                 }
             }
-            _ => todo!(),
+            Self::ConfigQos { qos_configurations, mut responses } => {
+                current_response_codes.append(&mut responses);
+                for configuration in qos_configurations {
+                    let ase_id = configuration.ase_id;
+                    let Some(endpoint) = endpoint_map.get(&ase_id) else {
+                        current_response_codes
+                            .push(ResponseCode::InvalidAseId { value: ase_id.into() });
+                        continue;
+                    };
+                    if !opcode.unwrap().allowed_in_state(&endpoint.state) {
+                        current_response_codes
+                            .push(ResponseCode::InvalidAseStateMachineTransition { ase_id });
+                        continue;
+                    }
+                    let (send, recv) = oneshot::channel();
+                    waiting.push(recv);
+                    events.push(configuration.into_event(peer_id, endpoint.clone(), send));
+                }
+            }
+            Self::Enable { ases_with_metadata, mut responses } => {
+                current_response_codes.append(&mut responses);
+                for AseIdWithMetadata { ase_id, metadata } in ases_with_metadata {
+                    let Some(endpoint) = endpoint_map.get(&ase_id) else {
+                        current_response_codes
+                            .push(ResponseCode::InvalidAseId { value: ase_id.into() });
+                        continue;
+                    };
+                    if !opcode.unwrap().allowed_in_state(&endpoint.state) {
+                        current_response_codes
+                            .push(ResponseCode::InvalidAseStateMachineTransition { ase_id });
+                        continue;
+                    }
+                    let (sender, recv) = oneshot::channel();
+                    waiting.push(recv);
+                    let cis = endpoint.get_cis().unwrap();
+                    let event = ServiceEvent::Enable {
+                        ase_id,
+                        cis,
+                        peer_id,
+                        metadata: metadata.clone(),
+                        responder: EnableResponder { endpoint: endpoint.clone(), metadata, sender },
+                    };
+                    events.push(event);
+                }
+            }
+            Self::ReceiverStartReady { ases } => {
+                for ase_id in ases {
+                    let Some(endpoint) = endpoint_map.get(&ase_id) else {
+                        current_response_codes
+                            .push(ResponseCode::InvalidAseId { value: ase_id.into() });
+                        continue;
+                    };
+                    if !opcode.unwrap().allowed_in_state(&endpoint.state) {
+                        current_response_codes
+                            .push(ResponseCode::InvalidAseStateMachineTransition { ase_id });
+                        continue;
+                    }
+                    if endpoint.direction != AudioDirection::Source {
+                        current_response_codes.push(ResponseCode::InvalidAseDirection { ase_id });
+                        continue;
+                    }
+                    let cis = endpoint.get_cis().unwrap();
+                    // Automatically accept.
+                    let mut endpoint = endpoint.clone();
+                    endpoint.state = AseState::Streaming;
+                    endpoints.push(endpoint);
+                    current_response_codes.push(ResponseCode::Success { ase_id });
+                    events.push(ServiceEvent::Start { peer_id, ase_id, cis });
+                }
+            }
+            Self::Disable { ases } => {
+                for ase_id in ases {
+                    let Some(endpoint) = endpoint_map.get(&ase_id) else {
+                        current_response_codes
+                            .push(ResponseCode::InvalidAseId { value: ase_id.into() });
+                        continue;
+                    };
+                    if !opcode.unwrap().allowed_in_state(&endpoint.state) {
+                        current_response_codes
+                            .push(ResponseCode::InvalidAseStateMachineTransition { ase_id });
+                        continue;
+                    }
+                    let AseAdditionalParameters::Streaming { cig_id, cis_id, .. } =
+                        endpoint.additional
+                    else {
+                        unreachable!();
+                    };
+                    if endpoint.direction == AudioDirection::Source {
+                        // Accept automatically, and wait for the ReceiverStopReady to send Disable
+                        // event.
+                        let mut endpoint = endpoint.clone();
+                        endpoint.state = AseState::Disabling;
+                        endpoints.push(endpoint);
+                        current_response_codes.push(ResponseCode::Success { ase_id });
+                        continue;
+                    }
+                    let (sender, recv) = oneshot::channel();
+                    waiting.push(recv);
+                    events.push(ServiceEvent::Disable {
+                        peer_id,
+                        ase_id,
+                        cis: (cig_id, cis_id),
+                        responder: DisableResponder { endpoint: endpoint.clone(), sender },
+                    });
+                }
+            }
+            Self::ReceiverStopReady { ases } => {
+                for ase_id in ases {
+                    let Some(endpoint) = endpoint_map.get(&ase_id) else {
+                        current_response_codes
+                            .push(ResponseCode::InvalidAseId { value: ase_id.into() });
+                        continue;
+                    };
+                    if !opcode.unwrap().allowed_in_state(&endpoint.state) {
+                        current_response_codes
+                            .push(ResponseCode::InvalidAseStateMachineTransition { ase_id });
+                        continue;
+                    }
+                    if endpoint.direction != AudioDirection::Source {
+                        current_response_codes.push(ResponseCode::InvalidAseDirection { ase_id });
+                        continue;
+                    }
+                    let AseAdditionalParameters::Streaming { cig_id, cis_id, .. } =
+                        endpoint.additional
+                    else {
+                        unreachable!();
+                    };
+                    let (sender, recv) = oneshot::channel();
+                    waiting.push(recv);
+                    events.push(ServiceEvent::Disable {
+                        peer_id,
+                        ase_id,
+                        cis: (cig_id, cis_id),
+                        responder: DisableResponder { endpoint: endpoint.clone(), sender },
+                    });
+                }
+            }
+            Self::Release { ases } => {
+                for ase_id in ases {
+                    let Some(endpoint) = endpoint_map.get(&ase_id) else {
+                        current_response_codes
+                            .push(ResponseCode::InvalidAseId { value: ase_id.into() });
+                        continue;
+                    };
+                    if !opcode.unwrap().allowed_in_state(&endpoint.state) {
+                        current_response_codes
+                            .push(ResponseCode::InvalidAseStateMachineTransition { ase_id });
+                        continue;
+                    }
+                    // Automatically accept.  We will automatically perform the Released operation
+                    // on the next poll.
+                    let mut endpoint = endpoint.clone();
+                    endpoint.state = AseState::Releasing;
+                    endpoint.additional = AseAdditionalParameters::None;
+                    current_response_codes.push(ResponseCode::Success { ase_id });
+                    endpoints.push(endpoint);
+                }
+            }
+            Self::Released { ase_id } => {
+                // Should only happen when we detect a link loss and are told so by the client.
+                // Therefore we can transition immediately.
+                // If there is no endpoint by that ase_id, we do nothing.
+                if let Some(mut endpoint) = endpoint_map.get(&ase_id).cloned() {
+                    // TODO(b/433287917): implement caching with either preferred or cached
+                    // configurations
+                    endpoint.state = AseState::Idle;
+                    endpoint.additional = AseAdditionalParameters::None;
+                    endpoints.push(endpoint);
+                } else {
+                    log::warn!("Received Released for unknown endpoint: {ase_id:?}");
+                }
+            }
+            Self::UpdateMetadata { ases_with_metadata, mut responses } => {
+                current_response_codes.append(&mut responses);
+                for AseIdWithMetadata { ase_id, metadata } in ases_with_metadata {
+                    let Some(endpoint) = endpoint_map.get(&ase_id) else {
+                        current_response_codes
+                            .push(ResponseCode::InvalidAseId { value: ase_id.into() });
+                        continue;
+                    };
+                    if ![AseState::Enabling, AseState::Streaming].contains(&endpoint.state) {
+                        current_response_codes
+                            .push(ResponseCode::InvalidAseStateMachineTransition { ase_id });
+                        continue;
+                    }
+                    let (sender, recv) = oneshot::channel();
+                    waiting.push(recv);
+                    events.push(ServiceEvent::UpdateMetadata {
+                        peer_id,
+                        ase_id,
+                        metadata: metadata.clone(),
+                        responder: UpdateMetadataResponder {
+                            endpoint: endpoint.clone(),
+                            metadata,
+                            sender,
+                        },
+                    });
+                }
+            }
         }
         (
             events,
@@ -589,7 +1034,7 @@
                 peer_id,
                 opcode,
                 current_response_codes,
-                endpoints: Vec::new(),
+                endpoints,
                 waiting: waiting.into_iter().collect(),
             },
         )
diff --git a/rust/bt-ascs/src/tests.rs b/rust/bt-ascs/src/tests.rs
index 7dd7a30..60ca82c 100644
--- a/rust/bt-ascs/src/tests.rs
+++ b/rust/bt-ascs/src/tests.rs
@@ -14,6 +14,7 @@
 use crate::server::AudioStreamControlServiceServer;
 use crate::server::ServiceEvent;
 use crate::server::{ASCS_SERVICE_ID, ASCS_UUID};
+use crate::types::CodecConfiguration;
 use crate::*;
 
 #[track_caller]
@@ -195,16 +196,41 @@
     assert!(peer_one_value[1] != peer_two_value[1]);
 }
 
-#[test]
-fn invalid_operation() {
-    let (mut ascs_server, fake_server, mut server_events) = published_server();
+#[track_caller]
+fn expect_control_point_notified(
+    ascs_server: &mut Pin<Box<AudioStreamControlServiceServer<FakeTypes>>>,
+    server_events: &mut UnboundedReceiver<FakeServerEvent>,
+) -> Vec<u8> {
+    // Expect the write to be responded to / acknowledged and a notification from
+    // the CP handle and the Source ASE
+    match expect_service_event(server_events) {
+        FakeServerEvent::WriteResponded { value, .. } => assert!(value.is_ok()),
+        x => panic!("Expected acknowledge of write, got {x:?}"),
+    };
+    assert!(poll_server(ascs_server).is_pending());
+    match expect_service_event(server_events) {
+        FakeServerEvent::Notified { handle, value, peers, .. } => {
+            assert!(peers.contains(&PeerId(1)));
+            assert_eq!(Handle(1), handle);
+            value
+        }
+        x => panic!("Expected acknowledge of write, got {x:?}"),
+    }
+}
 
+#[track_caller]
+fn find_ase_id(
+    ascs_server: &mut Pin<Box<AudioStreamControlServiceServer<FakeTypes>>>,
+    fake_server: &FakeServer,
+    server_events: &mut UnboundedReceiver<FakeServerEvent>,
+    handle: Handle,
+) -> u8 {
     // Read the sink uuid
-    fake_server.incoming_read(PeerId(1), ASCS_SERVICE_ID, Handle(3), 0);
+    fake_server.incoming_read(PeerId(1), ASCS_SERVICE_ID, handle, 0);
     // Poll the ascs server, should not result in an ASCS event
-    assert!(poll_server(&mut ascs_server).is_pending());
+    assert!(poll_server(ascs_server).is_pending());
 
-    // Should have the response
+    // Find the AseId
     let sink_value;
     match server_events.poll_next_unpin(&mut futures_test::task::noop_context()) {
         Poll::Ready(Some(FakeServerEvent::ReadResponded { service_id, handle: _, value })) => {
@@ -215,6 +241,27 @@
     };
 
     let ase_id = sink_value[0];
+    // Should start in idle.
+    assert_eq!(sink_value[1], 0x00);
+    assert_eq!(sink_value.len(), 2);
+
+    ase_id
+}
+
+#[track_caller]
+fn find_first_ase_id(
+    ascs_server: &mut Pin<Box<AudioStreamControlServiceServer<FakeTypes>>>,
+    fake_server: &FakeServer,
+    server_events: &mut UnboundedReceiver<FakeServerEvent>,
+) -> u8 {
+    find_ase_id(ascs_server, fake_server, server_events, Handle(3))
+}
+
+#[test]
+fn invalid_operation() {
+    let (mut ascs_server, fake_server, mut server_events) = published_server();
+
+    let ase_id = find_first_ase_id(&mut ascs_server, &fake_server, &mut server_events);
 
     // Write an operation that is unknown.
     fake_server.incoming_write(
@@ -231,46 +278,18 @@
         x => panic!("Expected to still be pending, got {x:?}"),
     };
 
-    // Expect the write to be responded to / acknowledged and a notification from
-    // the CP handle and the Source ASE
-    match expect_service_event(&mut server_events) {
-        FakeServerEvent::WriteResponded { value, .. } => assert!(value.is_ok()),
-        x => panic!("Expected acknowledge of write, got {x:?}"),
-    };
-    assert!(poll_server(&mut ascs_server).is_pending());
-    match expect_service_event(&mut server_events) {
-        FakeServerEvent::Notified { handle, value, peers, .. } => {
-            assert!(peers.contains(&PeerId(1)));
-            assert_eq!(Handle(1), handle);
-            // Opcode should match
-            // Number_of_ASEs should be 0xFF (Table 4.7)
-            // ASE_ID is 0x00, and Reason should be 0x00
-            assert_eq!(value, &[0x1f, 0xFF, 0x00, 0x01, 0x00]);
-        }
-        x => panic!("Expected acknowledge of write, got {x:?}"),
-    };
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 0xFF (Table 4.7)
+    // ASE_ID is 0x00, and Reason should be 0x00
+    assert_eq!(cp_value, &[0x1f, 0xFF, 0x00, 0x01, 0x00]);
 }
 
 #[test]
 fn invalid_length() {
     let (mut ascs_server, fake_server, mut server_events) = published_server();
 
-    // Read the sink uuid
-    fake_server.incoming_read(PeerId(1), ASCS_SERVICE_ID, Handle(3), 0);
-    // Poll the ascs server, should not result in an ASCS event
-    assert!(poll_server(&mut ascs_server).is_pending());
-
-    // Should have the response
-    let sink_value;
-    match server_events.poll_next_unpin(&mut futures_test::task::noop_context()) {
-        Poll::Ready(Some(FakeServerEvent::ReadResponded { service_id, handle: _, value })) => {
-            assert_eq!(service_id, ASCS_SERVICE_ID);
-            sink_value = value.unwrap();
-        }
-        x => panic!("Expected the read to be responded to got {x:?}"),
-    };
-
-    let ase_id = sink_value[0];
+    let ase_id = find_first_ase_id(&mut ascs_server, &fake_server, &mut server_events);
 
     // Write an operation that is the wrong length (too short)
     fake_server.incoming_write(
@@ -287,22 +306,736 @@
         x => panic!("Expected to still be pending, got {x:?}"),
     };
 
-    // Expect the write to be responded to / acknowledged and a notification from
-    // the CP handle and the Source ASE
-    match expect_service_event(&mut server_events) {
-        FakeServerEvent::WriteResponded { value, .. } => assert!(value.is_ok()),
-        x => panic!("Expected acknowledge of write, got {x:?}"),
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 0xFF (Table 4.7)
+    // ASE_ID is 0x00, and Reason should be 0x00
+    assert_eq!(cp_value, &[0x01, 0xFF, 0x00, 0x02, 0x00]);
+}
+
+#[track_caller]
+fn assert_handle_value_eq(
+    ascs_server: &mut Pin<Box<AudioStreamControlServiceServer<FakeTypes>>>,
+    fake_server: &FakeServer,
+    events: &mut UnboundedReceiver<FakeServerEvent>,
+    handle: Handle,
+    expected: &[u8],
+) {
+    // Read the sink uuid
+    fake_server.incoming_read(PeerId(1), ASCS_SERVICE_ID, handle, 0);
+    // Poll the ascs server, should not result in an ASCS event
+    assert!(poll_server(ascs_server).is_pending());
+    let sink_value;
+    match events.poll_next_unpin(&mut futures_test::task::noop_context()) {
+        Poll::Ready(Some(FakeServerEvent::ReadResponded { service_id, handle: _, value })) => {
+            assert_eq!(service_id, ASCS_SERVICE_ID);
+            sink_value = value.unwrap();
+        }
+        x => panic!("Expected the read to be responded to got {x:?}"),
     };
-    assert!(poll_server(&mut ascs_server).is_pending());
+
+    assert_eq!(sink_value, expected);
+}
+
+#[rustfmt::skip]
+fn build_codec_configure(ase_id: u8) -> Vec<u8> {
+    // Balanced Latency, 1m phy, LC3 codec (no company / company codecid): ltv (xx
+    // bytes), 48khz, frame_duration 10ms, stereo audio channels,
+    vec![
+        0x01, 0x01, ase_id, 0x02, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00,
+        0x10, // 16 bytes
+        0x02, 0x01, 0x08, // 48khz
+        0x02, 0x02, 0x01, // 10ms duration
+        0x05, 0x03, 0x00, 0x00, 0x00, 0x03, // FrontLeft + FrontRight
+        0x03, 0x04, 0x64, 0x00, // 100 bytes per codec frame
+    ]
+}
+
+#[test]
+fn invalid_ase_ids() {
+    let (mut ascs_server, fake_server, mut server_events) = published_server();
+
+    let ase_id = find_first_ase_id(&mut ascs_server, &fake_server, &mut server_events);
+
+    // Try with an invalid ase_id
+    let codec_configure_bad_ase_id = build_codec_configure(0x00);
+
+    fake_server.incoming_write(
+        PeerId(1),
+        ASCS_SERVICE_ID,
+        Handle(1),
+        0,
+        codec_configure_bad_ase_id.clone(),
+    );
+
+    // Should have nothing to respond to
+    match poll_server(&mut ascs_server) {
+        Poll::Pending => {}
+        x => panic!("Expected to still be pending, got {x:?}"),
+    };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match (0x01)
+    // Number_of_ASEs should be 1
+    // ASE_ID is 0x00, and Reason should be 0x03 (invalid ASE ID)
+    assert_eq!(cp_value, &[0x01, 0x01, 0x00, 0x03, 0x00]);
+
+    // Try with one valid and one invalid
+    let mut codec_configure_two_ase_ids_one_bad = build_codec_configure(ase_id);
+    // Adjust the number of ASEs
+    codec_configure_two_ase_ids_one_bad[1] = 0x02;
+    // This one has a bad ase_id in it
+    // TODO(b/518022833): adjust to find all ASE_IDs and randomly pick a non-used
+    // one)
+    let bad_ase_id_configure = build_codec_configure(ase_id | 0xF0);
+    // Skip the opcode and num of ase_ids in this one
+    codec_configure_two_ase_ids_one_bad.extend(&bad_ase_id_configure[2..]);
+
+    fake_server.incoming_write(
+        PeerId(1),
+        ASCS_SERVICE_ID,
+        Handle(1),
+        0,
+        codec_configure_two_ase_ids_one_bad.clone(),
+    );
+
+    // Should have the one valid CodecConfigure to repond to
+    match poll_server(&mut ascs_server) {
+        Poll::Ready(Some(Ok(ServiceEvent::CodecConfigure {
+            configuration:
+                CodecConfiguration {
+                    target_latency,
+                    target_phy,
+                    codec_id,
+                    codec_specific_configuration,
+                    ase_id: configured_ase_id,
+                },
+            responder,
+        }))) => {
+            use crate::types::*;
+            use bt_common::core::{CodecId, CodingFormat};
+            assert_eq!(AseId(ase_id), configured_ase_id);
+            assert_eq!(target_latency, TargetLatency::TargetBalanced);
+            assert_eq!(target_phy, TargetPhy::Le1MPhy);
+            assert_eq!(codec_id, CodecId::Assigned(CodingFormat::Lc3));
+            assert_eq!(codec_specific_configuration.len(), 16);
+            responder.accept(
+                Framing::Unframed,
+                vec![Phy::Le1MPhy],
+                0x10,
+                MaxTransportLatency::try_from(std::time::Duration::from_millis(40)).unwrap(),
+                PresentationDelayRange::build(20000, 100000).unwrap(),
+            );
+        }
+        x => panic!("Expected CodecConfigure, got {x:?}"),
+    }
+    // Then we should be pending.
+    match poll_server(&mut ascs_server) {
+        Poll::Pending => {}
+        x => panic!("Expected to still be pending, got {x:?}"),
+    };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 2
+    // ASE_ID should match and succeed, and the other ase_id should have failed.
+    assert_eq!(cp_value, &[0x01, 0x02, ase_id | 0xF0, 0x03, 0x00, ase_id, 0x00, 0x00]);
+}
+
+#[test]
+fn application_rejection() {
+    let (mut ascs_server, fake_server, mut server_events) = published_server();
+
+    let ase_id = find_first_ase_id(&mut ascs_server, &fake_server, &mut server_events);
+
+    // Codec configure
+    let codec_configure = build_codec_configure(ase_id);
+    fake_server.incoming_write(PeerId(1), ASCS_SERVICE_ID, Handle(1), 0, codec_configure.clone());
+
+    // Should have a CodecConfigure to respond to. Let's reject.
+    match poll_server(&mut ascs_server) {
+        Poll::Ready(Some(Ok(ServiceEvent::CodecConfigure {
+            configuration:
+                CodecConfiguration {
+                    target_latency,
+                    target_phy,
+                    codec_id,
+                    codec_specific_configuration,
+                    ase_id,
+                },
+            responder,
+        }))) => {
+            use crate::types::*;
+            use bt_common::core::{CodecId, CodingFormat};
+            assert_eq!(target_latency, TargetLatency::TargetBalanced);
+            assert_eq!(target_phy, TargetPhy::Le1MPhy);
+            assert_eq!(codec_id, CodecId::Assigned(CodingFormat::Lc3));
+            assert_eq!(codec_specific_configuration.len(), 16);
+            responder.reject(ResponseCode::InsufficientResources { ase_id });
+        }
+
+        x => panic!("Expected CodecConfigure, got {x:?}"),
+    };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 1 should match ASE_ID and response should match.
+    assert_eq!(cp_value, &[0x01, 0x01, ase_id, 0x0D, 0x00]);
+}
+
+#[test]
+fn endpoint_lifecycle_source() {
+    let (mut ascs_server, fake_server, mut server_events) = published_server();
+
+    let ase_id = find_first_ase_id(&mut ascs_server, &fake_server, &mut server_events);
+
+    // QosConfigure the ase_id, with CIG 0x01, CIS 0x01, SDU Interval 0x1FF,
+    // Unframed, 1m PHY, Max SDU 0x0FFF, RetransmissionNumber 0xFF,
+    // Max_Transport_latency max (0x0FA0), and 30ms (0x007530)
+    let qos_configure = vec![
+        0x02, 0x01, ase_id, 0x01, 0x01, 0x00, 0xFF, 0x01, 0x00, 0x01, 0xFF, 0x0F, 0xFF, 0xA0, 0x0F,
+        0x30, 0x75, 0x00,
+    ];
+    // Can't transition to QoS without CodecConfigure first
+    fake_server.incoming_write(PeerId(1), ASCS_SERVICE_ID, Handle(1), 0, qos_configure.clone());
+
+    // Should have nothing to respond to
+    match poll_server(&mut ascs_server) {
+        Poll::Pending => {}
+        x => panic!("Expected to still be pending, got {x:?}"),
+    };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 1
+    // ASE_ID is 0x00, and Reason should be 0x04 (invalid state machine transition)
+    assert_eq!(cp_value, &[0x02, 0x01, ase_id, 0x04, 0x00]);
+
+    // And the ASE should still be in idle.
+    assert_handle_value_eq(
+        &mut ascs_server,
+        &fake_server,
+        &mut server_events,
+        Handle(3),
+        &[ase_id, 0x00],
+    );
+
+    // Go to CodecConfigure
+    let codec_configure = build_codec_configure(ase_id);
+
+    fake_server.incoming_write(PeerId(1), ASCS_SERVICE_ID, Handle(1), 0, codec_configure.clone());
+
+    // Should have a CodecConfigure to respond to.
+    match poll_server(&mut ascs_server) {
+        Poll::Ready(Some(Ok(ServiceEvent::CodecConfigure {
+            configuration:
+                CodecConfiguration {
+                    target_latency,
+                    target_phy,
+                    codec_id,
+                    codec_specific_configuration,
+                    ..
+                },
+            responder,
+        }))) => {
+            use crate::types::*;
+            use bt_common::core::{CodecId, CodingFormat};
+            assert_eq!(target_latency, TargetLatency::TargetBalanced);
+            assert_eq!(target_phy, TargetPhy::Le1MPhy);
+            assert_eq!(codec_id, CodecId::Assigned(CodingFormat::Lc3));
+            assert_eq!(codec_specific_configuration.len(), 16);
+            responder.accept(
+                Framing::Unframed,
+                vec![Phy::Le1MPhy],
+                0x10,
+                MaxTransportLatency::try_from(std::time::Duration::from_millis(40)).unwrap(),
+                PresentationDelayRange::build(20000, 100000).unwrap(),
+            );
+        }
+
+        x => panic!("Expected CodecConfigure, got {x:?}"),
+    };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 1 should match ASE_ID and success 0x00
+    assert_eq!(cp_value, &[0x01, 0x01, ase_id, 0x00, 0x00]);
+
+    // And the ASE should be configured and notified
     match expect_service_event(&mut server_events) {
         FakeServerEvent::Notified { handle, value, peers, .. } => {
             assert!(peers.contains(&PeerId(1)));
-            assert_eq!(Handle(1), handle);
-            // Opcode should match
-            // Number_of_ASEs should be 0xFF (Table 4.7)
-            // ASE_ID is 0x00, and Reason should be 0x00
-            assert_eq!(value, &[0x01, 0xFF, 0x00, 0x02, 0x00]);
+            assert_eq!(Handle(3), handle);
+            assert_eq!(value[0], ase_id);
+            assert_eq!(value[1], 0x01); // Configured state
+            assert_eq!(value.len(), 2 + 23 + usize::from(value[24]))
+            // state length should be 2 (static) + 24
+            // (configured state) + len of codec
+            // configuration
         }
-        x => panic!("Expected acknowledge of write, got {x:?}"),
+        x => panic!("Expected Endpoint Notification, got {x:?}"),
+    }
+
+    // QoSConfigure
+    fake_server.incoming_write(PeerId(1), ASCS_SERVICE_ID, Handle(1), 0, qos_configure.clone());
+
+    // Should have a QosConfigure to respond to.
+    match poll_server(&mut ascs_server) {
+        Poll::Ready(Some(Ok(ServiceEvent::QosConfigure {
+            peer_id,
+            target_configuration: _,
+            responder,
+        }))) => {
+            assert_eq!(peer_id, PeerId(1));
+            responder.accept();
+        }
+
+        x => panic!("Expected QoSConfigure, got {x:?}"),
     };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 1 should match ASE_ID and success 0x00
+    assert_eq!(cp_value, &[0x02, 0x01, ase_id, 0x00, 0x00]);
+
+    // And the ASE should be configured and notified
+    match expect_service_event(&mut server_events) {
+        FakeServerEvent::Notified { handle, value, peers, .. } => {
+            assert!(peers.contains(&PeerId(1)));
+            assert_eq!(Handle(3), handle);
+            assert_eq!(value[0], ase_id);
+            assert_eq!(value[1], 0x02); // QoS Configured
+        }
+        x => panic!("Expected Endpoint Notification, got {x:?}"),
+    }
+
+    // Enable
+    let enable = vec![0x03, 0x01, ase_id, 0x00];
+    fake_server.incoming_write(PeerId(1), ASCS_SERVICE_ID, Handle(1), 0, enable.clone());
+
+    // Should have an event o respond to.
+    match poll_server(&mut ascs_server) {
+        Poll::Ready(Some(Ok(ServiceEvent::Enable {
+            ase_id: enabled_ase_id, responder, ..
+        }))) => {
+            assert_eq!(ase_id, enabled_ase_id.0);
+            responder.accept();
+        }
+
+        x => panic!("Expected Enable, got {x:?}"),
+    };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 1 should match ASE_ID and success 0x00
+    assert_eq!(cp_value, &[0x03, 0x01, ase_id, 0x00, 0x00]);
+
+    // And the ASE should be configured and notified
+    match expect_service_event(&mut server_events) {
+        FakeServerEvent::Notified { handle, value, peers, .. } => {
+            assert!(peers.contains(&PeerId(1)));
+            assert_eq!(Handle(3), handle);
+            assert_eq!(value[0], ase_id);
+            assert_eq!(value[1], 0x03); // Enabling
+        }
+        x => panic!("Expected Endpoint Notification, got {x:?}"),
+    }
+
+    // Receiver Start Ready -> Streaming
+    let receiver_start_ready = vec![0x04, 0x01, ase_id];
+    fake_server.incoming_write(
+        PeerId(1),
+        ASCS_SERVICE_ID,
+        Handle(1),
+        0,
+        receiver_start_ready.clone(),
+    );
+
+    // We auto-accept this, so we should not have a response, but do generate an
+    // event.
+    match poll_server(&mut ascs_server) {
+        Poll::Ready(Some(Ok(ServiceEvent::Start {
+            peer_id: _,
+            ase_id: started_ase_id,
+            cis: _,
+        }))) => {
+            assert_eq!(ase_id, started_ase_id.0);
+        }
+        x => panic!("Expected Start, got {x:?}"),
+    };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 1 should match ASE_ID and success 0x00
+    assert_eq!(cp_value, &[0x04, 0x01, ase_id, 0x00, 0x00]);
+
+    // And the ASE should be configured and notified
+    match expect_service_event(&mut server_events) {
+        FakeServerEvent::Notified { handle, value, peers, .. } => {
+            assert!(peers.contains(&PeerId(1)));
+            assert_eq!(Handle(3), handle);
+            assert_eq!(value[0], ase_id);
+            assert_eq!(value[1], 0x04); // Streaming
+        }
+        x => panic!("Expected Endpoint Notification, got {x:?}"),
+    }
+
+    // Disable -> Disabling
+    let disable = vec![0x05, 0x01, ase_id];
+    fake_server.incoming_write(PeerId(1), ASCS_SERVICE_ID, Handle(1), 0, disable.clone());
+
+    // We auto-accept this, so we should not have a event.
+    // We also don't generate an event until Receiver Stop Ready
+    match poll_server(&mut ascs_server) {
+        Poll::Pending => {}
+        x => panic!("Expected no event on Disable, got {x:?}"),
+    };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 1 should match ASE_ID and success 0x00
+    assert_eq!(cp_value, &[0x05, 0x01, ase_id, 0x00, 0x00]);
+
+    // And the ASE should be configured and notified
+    match expect_service_event(&mut server_events) {
+        FakeServerEvent::Notified { handle, value, peers, .. } => {
+            assert!(peers.contains(&PeerId(1)));
+            assert_eq!(Handle(3), handle);
+            assert_eq!(value[0], ase_id);
+            assert_eq!(value[1], 0x05); // Disabling
+        }
+        x => panic!("Expected Endpoint Notification, got {x:?}"),
+    }
+
+    // Receiver Stop Ready -> QoSConfigured
+    let receiver_stop_ready = vec![0x06, 0x01, ase_id];
+    fake_server.incoming_write(
+        PeerId(1),
+        ASCS_SERVICE_ID,
+        Handle(1),
+        0,
+        receiver_stop_ready.clone(),
+    );
+
+    match poll_server(&mut ascs_server) {
+        Poll::Ready(Some(Ok(ServiceEvent::Disable {
+            peer_id: _,
+            ase_id: disabled_ase_id,
+            cis: _,
+            responder,
+        }))) => {
+            assert_eq!(ase_id, disabled_ase_id.0);
+            responder.accept();
+        }
+        x => panic!("Expected Disable, got {x:?}"),
+    };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 1 should match ASE_ID and success 0x00
+    assert_eq!(cp_value, &[0x06, 0x01, ase_id, 0x00, 0x00]);
+
+    // And the ASE should be configured and notified
+    match expect_service_event(&mut server_events) {
+        FakeServerEvent::Notified { handle, value, peers, .. } => {
+            assert!(peers.contains(&PeerId(1)));
+            assert_eq!(Handle(3), handle);
+            assert_eq!(value[0], ase_id);
+            assert_eq!(value[1], 0x02); // QosConfigured
+        }
+        x => panic!("Expected Endpoint Notification, got {x:?}"),
+    }
+
+    // Release -> Releasing
+    let release = vec![0x08, 0x01, ase_id];
+    fake_server.incoming_write(PeerId(1), ASCS_SERVICE_ID, Handle(1), 0, release.clone());
+
+    // We don't expect an event for this (disable has already happened
+    match poll_server(&mut ascs_server) {
+        Poll::Pending => {}
+        x => panic!("Expected no event from release, got {x:?}"),
+    };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 1 should match ASE_ID and success 0x00
+    assert_eq!(cp_value, &[0x08, 0x01, ase_id, 0x00, 0x00]);
+
+    // And the ASE should be configured and notified
+    match expect_service_event(&mut server_events) {
+        FakeServerEvent::Notified { handle, value, peers, .. } => {
+            assert!(peers.contains(&PeerId(1)));
+            assert_eq!(Handle(3), handle);
+            assert_eq!(value[0], ase_id);
+            assert_eq!(value[1], 0x06); // Releasing
+        }
+        x => panic!("Expected Endpoint Notification, got {x:?}"),
+    }
+
+    // TODO(b/518022833): Automated behaviors are not done yet
+    // TODO(b/518022833): Test Update Metadata
+}
+
+#[test]
+fn endpoint_lifecycle_sink() {
+    let (mut ascs_server, fake_server, mut server_events) = published_server();
+
+    // Handle 3 is the Sink ASE
+    let ase_id = find_ase_id(&mut ascs_server, &fake_server, &mut server_events, Handle(4));
+
+    // QosConfigure the ase_id, with CIG 0x01, CIS 0x01, SDU Interval 0x1FF,
+    // Unframed, 1m PHY, Max SDU 0x0FFF, RetransmissionNumber 0xFF,
+    // Max_Transport_latency max (0x0FA0), and 30ms (0x007530)
+    let qos_configure = vec![
+        0x02, 0x01, ase_id, 0x01, 0x01, 0x00, 0xFF, 0x01, 0x00, 0x01, 0xFF, 0x0F, 0xFF, 0xA0, 0x0F,
+        0x30, 0x75, 0x00,
+    ];
+    // Can't transition to QoS without CodecConfigure first
+    fake_server.incoming_write(PeerId(1), ASCS_SERVICE_ID, Handle(1), 0, qos_configure.clone());
+
+    // Should have nothing to respond to
+    match poll_server(&mut ascs_server) {
+        Poll::Pending => {}
+        x => panic!("Expected to still be pending, got {x:?}"),
+    };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 1
+    // ASE_ID is 0x00, and Reason should be 0x04 (invalid state machine transition)
+    assert_eq!(cp_value, &[0x02, 0x01, ase_id, 0x04, 0x00]);
+
+    // And the ASE should still be in idle.
+    assert_handle_value_eq(
+        &mut ascs_server,
+        &fake_server,
+        &mut server_events,
+        Handle(4),
+        &[ase_id, 0x00],
+    );
+
+    // Go to CodecConfigure
+    let codec_configure = build_codec_configure(ase_id);
+
+    fake_server.incoming_write(PeerId(1), ASCS_SERVICE_ID, Handle(1), 0, codec_configure.clone());
+
+    // Should have a CodecConfigure to respond to.
+    match poll_server(&mut ascs_server) {
+        Poll::Ready(Some(Ok(ServiceEvent::CodecConfigure {
+            configuration:
+                CodecConfiguration {
+                    target_latency,
+                    target_phy,
+                    codec_id,
+                    codec_specific_configuration,
+                    ..
+                },
+            responder,
+        }))) => {
+            use crate::types::*;
+            use bt_common::core::{CodecId, CodingFormat};
+            assert_eq!(target_latency, TargetLatency::TargetBalanced);
+            assert_eq!(target_phy, TargetPhy::Le1MPhy);
+            assert_eq!(codec_id, CodecId::Assigned(CodingFormat::Lc3));
+            assert_eq!(codec_specific_configuration.len(), 16);
+            responder.accept(
+                Framing::Unframed,
+                vec![Phy::Le1MPhy],
+                0x10,
+                MaxTransportLatency::try_from(std::time::Duration::from_millis(40)).unwrap(),
+                PresentationDelayRange::build(20000, 100000).unwrap(),
+            );
+        }
+
+        x => panic!("Expected CodecConfigure, got {x:?}"),
+    };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 1 should match ASE_ID and success 0x00
+    assert_eq!(cp_value, &[0x01, 0x01, ase_id, 0x00, 0x00]);
+
+    // And the ASE should be configured and notified
+    match expect_service_event(&mut server_events) {
+        FakeServerEvent::Notified { handle, value, peers, .. } => {
+            assert!(peers.contains(&PeerId(1)));
+            assert_eq!(Handle(4), handle);
+            assert_eq!(value[0], ase_id);
+            assert_eq!(value[1], 0x01); // Configured state
+            assert_eq!(value.len(), 2 + 23 + usize::from(value[24]))
+            // state length should be 2 (static) + 24
+            // (configured state) + len of codec
+            // configuration
+        }
+        x => panic!("Expected Endpoint Notification, got {x:?}"),
+    }
+
+    // QoSConfigure
+    fake_server.incoming_write(PeerId(1), ASCS_SERVICE_ID, Handle(1), 0, qos_configure.clone());
+
+    // Should have a QosConfigure to respond to.
+    match poll_server(&mut ascs_server) {
+        Poll::Ready(Some(Ok(ServiceEvent::QosConfigure {
+            peer_id,
+            target_configuration: _,
+            responder,
+        }))) => {
+            assert_eq!(peer_id, PeerId(1));
+            responder.accept();
+        }
+
+        x => panic!("Expected QoSConfigure, got {x:?}"),
+    };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 1 should match ASE_ID and success 0x00
+    assert_eq!(cp_value, &[0x02, 0x01, ase_id, 0x00, 0x00]);
+
+    // And the ASE should be configured and notified
+    match expect_service_event(&mut server_events) {
+        FakeServerEvent::Notified { handle, value, peers, .. } => {
+            assert!(peers.contains(&PeerId(1)));
+            assert_eq!(Handle(4), handle);
+            assert_eq!(value[0], ase_id);
+            assert_eq!(value[1], 0x02); // QoS Configured
+        }
+        x => panic!("Expected Endpoint Notification, got {x:?}"),
+    }
+
+    // Enable
+    let enable = vec![0x03, 0x01, ase_id, 0x00];
+    fake_server.incoming_write(PeerId(1), ASCS_SERVICE_ID, Handle(1), 0, enable.clone());
+
+    // Should have an event to respond to.
+    match poll_server(&mut ascs_server) {
+        Poll::Ready(Some(Ok(ServiceEvent::Enable {
+            ase_id: enabled_ase_id, responder, ..
+        }))) => {
+            assert_eq!(ase_id, enabled_ase_id.0);
+            responder.accept();
+        }
+
+        x => panic!("Expected Enable, got {x:?}"),
+    };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 1 should match ASE_ID and success 0x00
+    assert_eq!(cp_value, &[0x03, 0x01, ase_id, 0x00, 0x00]);
+
+    // And the ASE should be configured and notified
+    match expect_service_event(&mut server_events) {
+        FakeServerEvent::Notified { handle, value, peers, .. } => {
+            assert!(peers.contains(&PeerId(1)));
+            assert_eq!(Handle(4), handle);
+            assert_eq!(value[0], ase_id);
+            assert_eq!(value[1], 0x03); // Enabling
+        }
+        x => panic!("Expected Endpoint Notification, got {x:?}"),
+    }
+
+    // Receiver Start Ready should not be allowed from the client for a Sink ASE
+    let receiver_start_ready = vec![0x04, 0x01, ase_id];
+    fake_server.incoming_write(
+        PeerId(1),
+        ASCS_SERVICE_ID,
+        Handle(1),
+        0,
+        receiver_start_ready.clone(),
+    );
+
+    // Should not have an event for the error.
+    match poll_server(&mut ascs_server) {
+        Poll::Pending => {}
+        x => panic!("Expected no event for invalid ReceiverStartReady, got {x:?}"),
+    };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 1 should match ASE_ID and success 0x00
+    assert_eq!(cp_value, &[0x04, 0x01, ase_id, 0x05, 0x00]);
+
+    // TODO: We don't do server-initiated ReceiverStart yet, so we can't proceed to
+    // streaming.
+
+    // Disable -> QosConfigured
+    let disable = vec![0x05, 0x01, ase_id];
+    fake_server.incoming_write(PeerId(1), ASCS_SERVICE_ID, Handle(1), 0, disable.clone());
+
+    // On Sink ASE, we generate the Disable event when Disabling.
+    match poll_server(&mut ascs_server) {
+        Poll::Ready(Some(Ok(ServiceEvent::Disable {
+            peer_id: _,
+            ase_id: disabled_ase_id,
+            cis: _,
+            responder,
+        }))) => {
+            assert_eq!(ase_id, disabled_ase_id.0);
+            responder.accept();
+        }
+        x => panic!("Expected Disable, got {x:?}"),
+    };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 1 should match ASE_ID and success 0x00
+    assert_eq!(cp_value, &[0x05, 0x01, ase_id, 0x00, 0x00]);
+
+    // And the ASE should be configured and notified
+    match expect_service_event(&mut server_events) {
+        FakeServerEvent::Notified { handle, value, peers, .. } => {
+            assert!(peers.contains(&PeerId(1)));
+            assert_eq!(Handle(4), handle);
+            assert_eq!(value[0], ase_id);
+            assert_eq!(value[1], 0x02); // Automatically went to QoSConfigure
+        }
+        x => panic!("Expected Endpoint Notification, got {x:?}"),
+    }
+
+    // Receiver Stop Ready is not allowed for a Sink ASE
+    let receiver_stop_ready = vec![0x06, 0x01, ase_id];
+    fake_server.incoming_write(
+        PeerId(1),
+        ASCS_SERVICE_ID,
+        Handle(1),
+        0,
+        receiver_stop_ready.clone(),
+    );
+
+    assert!(poll_server(&mut ascs_server).is_pending());
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 1 should match ASE_ID and Failure for Invalid ASE
+    // State Transition (since we are already in QosCondfiguredj
+    assert_eq!(cp_value, &[0x06, 0x01, ase_id, 0x04, 0x00]);
+
+    // Release -> Releasing
+    let release = vec![0x08, 0x01, ase_id];
+    fake_server.incoming_write(PeerId(1), ASCS_SERVICE_ID, Handle(1), 0, release.clone());
+
+    // We don't expect an event for this (disable has already happened
+    match poll_server(&mut ascs_server) {
+        Poll::Pending => {}
+        x => panic!("Expected no event from release, got {x:?}"),
+    };
+
+    let cp_value = expect_control_point_notified(&mut ascs_server, &mut server_events);
+    // Opcode should match
+    // Number_of_ASEs should be 1 should match ASE_ID and success 0x00
+    assert_eq!(cp_value, &[0x08, 0x01, ase_id, 0x00, 0x00]);
+
+    // And the ASE should be configured and notified
+    match expect_service_event(&mut server_events) {
+        FakeServerEvent::Notified { handle, value, peers, .. } => {
+            assert!(peers.contains(&PeerId(1)));
+            assert_eq!(Handle(4), handle);
+            assert_eq!(value[0], ase_id);
+            assert_eq!(value[1], 0x06); // Releasing
+        }
+        x => panic!("Expected Endpoint Notification, got {x:?}"),
+    }
+
+    // TODO: Automated behaviors are not done yet
+    // TODO: Test Update Metadata
 }
diff --git a/rust/bt-ascs/src/types.rs b/rust/bt-ascs/src/types.rs
index 0681283..6eb27c9 100644
--- a/rust/bt-ascs/src/types.rs
+++ b/rust/bt-ascs/src/types.rs
@@ -220,7 +220,7 @@
             Self::Enable { .. } => &[AseState::QosConfigured],
             Self::ReceiverStartReady { .. } => &[AseState::Enabling],
             Self::ReceiverStopReady { .. } => &[AseState::Disabling],
-            Self::Disable { .. } => &[AseState::Streaming],
+            Self::Disable { .. } => &[AseState::Streaming, AseState::Enabling],
             Self::UpdateMetadata { .. } => &[AseState::Enabling, AseState::Streaming],
             Self::Release { .. } => &[
                 AseState::Enabling,
@@ -255,7 +255,7 @@
     // The only possible error here is InvalidLength
     Release { ases: Vec<AseId> },
     // This is only initiated by the server
-    Released,
+    Released { ase_id: AseId },
 }
 
 impl AseControlOperation {
@@ -287,7 +287,7 @@
             AseControlOperation::ReceiverStopReady { .. } => Ok(0x06),
             AseControlOperation::UpdateMetadata { .. } => Ok(0x07),
             AseControlOperation::Release { .. } => Ok(0x08),
-            AseControlOperation::Released => Err(Error::ServerOnlyOperation),
+            AseControlOperation::Released { .. } => Err(Error::ServerOnlyOperation),
         }
     }
 }
@@ -642,6 +642,20 @@
     }
 }
 
+impl Encodable for CigId {
+    type Error = bt_common::packet_encoding::Error;
+    fn encoded_len(&self) -> core::primitive::usize {
+        Self::BYTE_SIZE
+    }
+    fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+        if buf.len() < Self::BYTE_SIZE {
+            return Err(Self::Error::BufferTooSmall);
+        }
+        buf[0] = self.0;
+        Ok(())
+    }
+}
+
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct CisId(u8);
 
@@ -667,6 +681,20 @@
     }
 }
 
+impl Encodable for CisId {
+    type Error = bt_common::packet_encoding::Error;
+    fn encoded_len(&self) -> core::primitive::usize {
+        Self::BYTE_SIZE
+    }
+    fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+        if buf.len() < Self::BYTE_SIZE {
+            return Err(Self::Error::BufferTooSmall);
+        }
+        buf[0] = self.0;
+        Ok(())
+    }
+}
+
 /// SDU Inteval parameter
 /// This value is 24 bits long and little-endian on the wire.
 /// It is stored native-endian here.
@@ -693,6 +721,20 @@
     }
 }
 
+impl Encodable for SduInterval {
+    type Error = bt_common::packet_encoding::Error;
+    fn encoded_len(&self) -> core::primitive::usize {
+        Self::BYTE_SIZE
+    }
+    fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+        if buf.len() < Self::BYTE_SIZE {
+            return Err(Self::Error::BufferTooSmall);
+        }
+        [buf[0], buf[1], buf[2], _] = self.0.to_le_bytes();
+        Ok(())
+    }
+}
+
 decodable_enum! {
 pub enum Framing<u8, bt_common::packet_encoding::Error, OutOfRange> {
     Unframed = 0x00,
@@ -704,6 +746,20 @@
     const BYTE_SIZE: usize = 1;
 }
 
+impl Encodable for Framing {
+    type Error = bt_common::packet_encoding::Error;
+    fn encoded_len(&self) -> core::primitive::usize {
+        Self::BYTE_SIZE
+    }
+    fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+        if buf.len() < Self::BYTE_SIZE {
+            return Err(Self::Error::BufferTooSmall);
+        }
+        buf[0] = (*self).into();
+        Ok(())
+    }
+}
+
 /// Max SDU parameter value.
 /// Valid range is 0x0000-0x0FFF
 /// Transmitted in little-endian. Stored here in native-endian.
@@ -722,6 +778,20 @@
     }
 }
 
+impl Encodable for MaxSdu {
+    type Error = bt_common::packet_encoding::Error;
+    fn encoded_len(&self) -> core::primitive::usize {
+        Self::BYTE_SIZE
+    }
+    fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+        if buf.len() < Self::BYTE_SIZE {
+            return Err(Self::Error::BufferTooSmall);
+        }
+        [buf[0], buf[1]] = self.0.to_le_bytes();
+        Ok(())
+    }
+}
+
 impl MaxSdu {
     const BYTE_SIZE: usize = 2;
 }