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; }