diff --git a/crates/ironrdp-rdpeusb/src/client.rs b/crates/ironrdp-rdpeusb/src/client.rs index e04efe479..c1394d79b 100644 --- a/crates/ironrdp-rdpeusb/src/client.rs +++ b/crates/ironrdp-rdpeusb/src/client.rs @@ -215,7 +215,7 @@ pub trait UrbdrcDeviceBackend: Send { /// the message MUST match the RequestId in the QUERY_DEVICE_TEXT message. /// /// [3.3.5.3.5]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/834f56cc-cfed-4649-8952-0b6486638c28 - fn query_device_text(&mut self, channel_id: u32, text_type: u32, locale_id: u32) -> PduResult>; + fn query_device_text(&mut self, channel_id: u32, text_type: u32, locale_id: u32) -> PduResult; /// Process an `IoControl` request. /// @@ -550,19 +550,15 @@ impl DvcProcessor for UrbdrcDeviceClient { if !self.ready_for_io || dev_text_pdu.udev_iface != self.udev_iface { return Ok(Vec::new()); } - if let Some(device_text) = + let device_text = self.backend - .query_device_text(channel_id, dev_text_pdu.text_type, dev_text_pdu.locale_id)? - { - Ok(vec![Box::new(QueryDeviceTextRsp { - msg_id: dev_text_pdu.msg_id, - udev_iface: dev_text_pdu.udev_iface, - hresult: device_text.hresult, - device_description: device_text.description.into(), - })]) - } else { - Ok(Vec::new()) - } + .query_device_text(channel_id, dev_text_pdu.text_type, dev_text_pdu.locale_id)?; + Ok(vec![Box::new(QueryDeviceTextRsp { + msg_id: dev_text_pdu.msg_id, + udev_iface: dev_text_pdu.udev_iface, + hresult: device_text.hresult, + device_description: device_text.description.into(), + })]) } IoCtl(io_ctl_pdu) => { if !self.ready_for_io || io_ctl_pdu.udev_iface != self.udev_iface { diff --git a/crates/ironrdp-testsuite-core/tests/rdpeusb/client.rs b/crates/ironrdp-testsuite-core/tests/rdpeusb/client.rs index 8e4ac1618..40294643b 100644 --- a/crates/ironrdp-testsuite-core/tests/rdpeusb/client.rs +++ b/crates/ironrdp-testsuite-core/tests/rdpeusb/client.rs @@ -18,17 +18,7 @@ use ironrdp_rdpeusb::pdu::{ UrbdrcClientControlPdu, UrbdrcClientDevicePdu, UrbdrcServerControlPdu, UrbdrcServerDevicePdu, }; -use super::simple_device_info; - -const STREAM_ID_PROXY: u32 = 1; - -fn proxy_iface_id(iface: InterfaceId) -> u32 { - u32::from(iface) | (STREAM_ID_PROXY << 30) -} - -fn encode_pdu(pdu: &T) -> Vec { - encode_vec(pdu).expect("encode should succeed") -} +use super::{encode_pdu, proxy_iface_id, simple_device_info}; fn decode_control_msg(message: &DvcMessage) -> UrbdrcClientControlPdu { let encoded = encode_vec(message.as_ref()).expect("encode should succeed"); @@ -81,30 +71,28 @@ impl DeviceManagerBackend for TestDeviceManager { } } -struct TestDeviceBackend { +struct NoopDeviceClientBackend { device_info: DeviceInfo, } -impl TestDeviceBackend { +impl NoopDeviceClientBackend { fn new(device_info: DeviceInfo) -> Self { Self { device_info } } } -impl UrbdrcDeviceBackend for TestDeviceBackend { +impl UrbdrcDeviceBackend for NoopDeviceClientBackend { fn device_info(&mut self, _channel_id: u32) -> PduResult { Ok(self.device_info.clone()) } fn cancel_request(&mut self, _request_id: RequestId, _channel_id: u32) {} - fn query_device_text( - &mut self, - _channel_id: u32, - _text_type: u32, - _locale_id: u32, - ) -> PduResult> { - Ok(None) + fn query_device_text(&mut self, _channel_id: u32, _text_type: u32, _locale_id: u32) -> PduResult { + Ok(DeviceText { + hresult: 0, + description: String::new(), + }) } fn io_control( @@ -169,10 +157,10 @@ fn channel_setup_sequence() { .expect("device manager state lock should not be poisoned"); state .pending_devices - .push_back(Box::new(TestDeviceBackend::new(simple_device_info()))); + .push_back(Box::new(NoopDeviceClientBackend::new(simple_device_info()))); state .pending_devices - .push_back(Box::new(TestDeviceBackend::new(simple_device_info()))); + .push_back(Box::new(NoopDeviceClientBackend::new(simple_device_info()))); } let callback_manager_state = Arc::clone(&manager_state); @@ -295,7 +283,7 @@ fn channel_setup_sequence() { #[test] fn new_device_sequence() { let udev_iface = InterfaceId::try_from(4).expect("valid device interface id"); - let backend = Box::new(TestDeviceBackend::new(simple_device_info())); + let backend = Box::new(NoopDeviceClientBackend::new(simple_device_info())); let mut client = UrbdrcDeviceClient::new(udev_iface, backend).expect("device client should be created"); assert!(!client.ready_for_io()); diff --git a/crates/ironrdp-testsuite-core/tests/rdpeusb/io/mod.rs b/crates/ironrdp-testsuite-core/tests/rdpeusb/io/mod.rs new file mode 100644 index 000000000..7453d84d6 --- /dev/null +++ b/crates/ironrdp-testsuite-core/tests/rdpeusb/io/mod.rs @@ -0,0 +1,355 @@ +use std::sync::mpsc::{self, Receiver, Sender}; + +use ironrdp_core::encode_vec; +use ironrdp_dvc::{DvcMessage, DvcProcessor as _}; +use ironrdp_pdu::PduResult; +use ironrdp_rdpeusb::client::{UrbdrcDeviceBackend, UrbdrcDeviceClient}; +use ironrdp_rdpeusb::io::{ + DeviceAnnounce, DeviceText, InternalIoControlPacket, IoControlCompletionResult, IoControlPacket, RequestId, + TransferInCompletionResult, TransferInPacket, TransferOutCompletionResult, TransferOutPacket, +}; +use ironrdp_rdpeusb::pdu::header::InterfaceId; +use ironrdp_rdpeusb::server::{UrbdrcDeviceServer, UrbdrcDeviceServerBackend}; + +use super::simple_device_info; + +const CHANNEL_ID: u32 = 11; +const DEVICE_TEXT_DESCRIPTION: &str = "Test USB device"; +const DEVICE_TEXT_HRESULT: u32 = 0; + +#[derive(Debug)] +enum ClientEvent { + QueryDeviceText { + channel_id: u32, + text_type: u32, + locale_id: u32, + }, + IoControl { + channel_id: u32, + request_id: RequestId, + request: IoControlPacket, + }, + InternalIoControl { + channel_id: u32, + request_id: RequestId, + request: InternalIoControlPacket, + }, + TransferIn { + channel_id: u32, + request_id: RequestId, + request: TransferInPacket, + }, + TransferOut { + channel_id: u32, + request_id: RequestId, + request: TransferOutPacket, + }, + TransferOutNoAck { + channel_id: u32, + request_id: RequestId, + request: TransferOutPacket, + }, + Cancel { + channel_id: u32, + request_id: RequestId, + }, +} + +#[derive(Debug)] +enum ServerEvent { + DeviceText(DeviceText), + IoControlCompleted { + channel_id: u32, + request_id: RequestId, + completion: IoControlCompletionResult, + }, + InternalIoControlCompleted { + channel_id: u32, + request_id: RequestId, + completion: IoControlCompletionResult, + }, + TransferInCompleted { + channel_id: u32, + request_id: RequestId, + completion: TransferInCompletionResult, + }, + TransferOutCompleted { + channel_id: u32, + request_id: RequestId, + completion: TransferOutCompletionResult, + }, +} + +struct ChannelClientBackend { + events: Sender, +} + +impl ChannelClientBackend { + fn send(&self, event: ClientEvent) { + self.events + .send(event) + .expect("client event receiver should remain connected"); + } +} + +impl UrbdrcDeviceBackend for ChannelClientBackend { + fn device_info(&mut self, _channel_id: u32) -> PduResult { + Ok(simple_device_info()) + } + + fn cancel_request(&mut self, request_id: RequestId, channel_id: u32) { + self.send(ClientEvent::Cancel { channel_id, request_id }); + } + + fn query_device_text(&mut self, channel_id: u32, text_type: u32, locale_id: u32) -> PduResult { + self.send(ClientEvent::QueryDeviceText { + channel_id, + text_type, + locale_id, + }); + Ok(DeviceText { + hresult: DEVICE_TEXT_HRESULT, + description: DEVICE_TEXT_DESCRIPTION.to_owned(), + }) + } + + fn io_control( + &mut self, + channel_id: u32, + request_id: RequestId, + request: IoControlPacket, + ) -> PduResult> { + self.send(ClientEvent::IoControl { + channel_id, + request_id, + request, + }); + Ok(None) + } + + fn internal_io_control( + &mut self, + channel_id: u32, + request_id: RequestId, + request: InternalIoControlPacket, + ) -> PduResult> { + self.send(ClientEvent::InternalIoControl { + channel_id, + request_id, + request, + }); + Ok(None) + } + + fn transfer_in( + &mut self, + channel_id: u32, + request_id: RequestId, + request: TransferInPacket, + ) -> PduResult> { + self.send(ClientEvent::TransferIn { + channel_id, + request_id, + request, + }); + Ok(None) + } + + fn transfer_out( + &mut self, + channel_id: u32, + request_id: RequestId, + request: TransferOutPacket, + ) -> PduResult> { + self.send(ClientEvent::TransferOut { + channel_id, + request_id, + request, + }); + Ok(None) + } + + fn transfer_out_no_ack( + &mut self, + channel_id: u32, + request_id: RequestId, + request: TransferOutPacket, + ) -> PduResult<()> { + self.send(ClientEvent::TransferOutNoAck { + channel_id, + request_id, + request, + }); + Ok(()) + } + + fn retract(&mut self, _channel_id: u32) -> PduResult<()> { + Ok(()) + } +} + +struct ChannelDeviceServerBackend { + events: Sender, +} + +impl ChannelDeviceServerBackend { + fn send(&self, event: ServerEvent) { + self.events + .send(event) + .expect("server event receiver should remain connected"); + } +} + +impl UrbdrcDeviceServerBackend for ChannelDeviceServerBackend { + fn add_device(&mut self, _device: DeviceAnnounce) -> PduResult<()> { + Ok(()) + } + + fn device_text(&mut self, device_text: DeviceText) { + self.send(ServerEvent::DeviceText(device_text)); + } + + fn io_control_completed( + &mut self, + channel_id: u32, + request_id: RequestId, + completion: IoControlCompletionResult, + ) -> PduResult<()> { + self.send(ServerEvent::IoControlCompleted { + channel_id, + request_id, + completion, + }); + Ok(()) + } + + fn internal_io_control_completed( + &mut self, + channel_id: u32, + request_id: RequestId, + completion: IoControlCompletionResult, + ) -> PduResult<()> { + self.send(ServerEvent::InternalIoControlCompleted { + channel_id, + request_id, + completion, + }); + Ok(()) + } + + fn transfer_in_completed( + &mut self, + channel_id: u32, + request_id: RequestId, + completion: TransferInCompletionResult, + ) -> PduResult<()> { + self.send(ServerEvent::TransferInCompleted { + channel_id, + request_id, + completion, + }); + Ok(()) + } + + fn transfer_out_completed( + &mut self, + channel_id: u32, + request_id: RequestId, + completion: TransferOutCompletionResult, + ) -> PduResult<()> { + self.send(ServerEvent::TransferOutCompleted { + channel_id, + request_id, + completion, + }); + Ok(()) + } +} + +struct ConnectedDevice { + client: UrbdrcDeviceClient, + server: UrbdrcDeviceServer, + client_events: Receiver, + server_events: Receiver, +} + +impl ConnectedDevice { + fn new() -> Self { + let udev_iface = InterfaceId::try_from(4).expect("valid device interface id"); + let completion_iface = InterfaceId::try_from(5).expect("valid completion interface id"); + let (client_events_tx, client_events) = mpsc::channel(); + let (server_events_tx, server_events) = mpsc::channel(); + + let client_backend = Box::new(ChannelClientBackend { + events: client_events_tx, + }); + let server_backend = Box::new(ChannelDeviceServerBackend { + events: server_events_tx, + }); + let mut client = UrbdrcDeviceClient::new(udev_iface, client_backend).expect("device client should be created"); + let mut server = + UrbdrcDeviceServer::new(server_backend, completion_iface).expect("device server should be created"); + + let mut to_client = server.start(CHANNEL_ID).expect("server start should succeed"); + let mut settled = false; + for _ in 0..16 { + let mut to_server = Vec::new(); + for message in to_client { + to_server.extend(process_message(&mut client, message)); + } + if to_server.is_empty() { + settled = true; + break; + } + + to_client = Vec::new(); + for message in to_server { + to_client.extend(process_message(&mut server, message)); + } + if to_client.is_empty() { + settled = true; + break; + } + } + assert!(settled, "device DVC setup should settle"); + assert!(client.ready_for_io()); + + Self { + client, + server, + client_events, + server_events, + } + } + + fn send_to_client(&mut self, message: DvcMessage) -> Vec { + process_message(&mut self.client, message) + } + + fn send_to_server(&mut self, message: DvcMessage) -> Vec { + process_message(&mut self.server, message) + } + + fn next_client_event(&self) -> ClientEvent { + self.client_events.try_recv().expect("client backend should be called") + } + + fn next_server_event(&self) -> ServerEvent { + self.server_events.try_recv().expect("server backend should be called") + } +} + +fn process_message(processor: &mut dyn ironrdp_dvc::DvcProcessor, message: DvcMessage) -> Vec { + let payload = encode_vec(message.as_ref()).expect("DVC message should encode"); + processor + .process(CHANNEL_ID, &payload) + .expect("DVC message should process") +} + +fn only_message(mut messages: Vec) -> DvcMessage { + assert_eq!(messages.len(), 1); + messages.pop().expect("one message should be present") +} + +mod requests; +mod transfers; diff --git a/crates/ironrdp-testsuite-core/tests/rdpeusb/io/requests.rs b/crates/ironrdp-testsuite-core/tests/rdpeusb/io/requests.rs new file mode 100644 index 000000000..d3cbdd881 --- /dev/null +++ b/crates/ironrdp-testsuite-core/tests/rdpeusb/io/requests.rs @@ -0,0 +1,217 @@ +use ironrdp_rdpeusb::io::{InternalIoControlPacket, IoControlCompletionResult, IoControlPacket, IoctlInternalUsb}; +use rstest::rstest; + +use super::{ + CHANNEL_ID, ClientEvent, ConnectedDevice, DEVICE_TEXT_DESCRIPTION, DEVICE_TEXT_HRESULT, ServerEvent, only_message, +}; + +// Refs: [Query Device Text][2.2.6.5] and [Query Device Text Response][2.2.6.6]. +// [2.2.6.5]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/d03a7696-2d56-4f20-b7a9-a5e72a045956 +// [2.2.6.6]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/acffdcfa-c792-40a4-a8ee-c545ea5b0a38 +#[test] +fn query_device_text_round_trip() { + let mut device = ConnectedDevice::new(); + + let request = device + .server + .query_device_text(1, 0x0409) + .expect("query device text should succeed"); + let response = only_message(device.send_to_client(request)); + + let ClientEvent::QueryDeviceText { + channel_id, + text_type, + locale_id, + } = device.next_client_event() + else { + panic!("expected query device text event"); + }; + assert_eq!(channel_id, CHANNEL_ID); + assert_eq!(text_type, 1); + assert_eq!(locale_id, 0x0409); + + assert!(device.send_to_server(response).is_empty()); + let ServerEvent::DeviceText(device_text) = device.next_server_event() else { + panic!("expected device text event"); + }; + assert_eq!(device_text.hresult, DEVICE_TEXT_HRESULT); + assert_eq!(device_text.description, DEVICE_TEXT_DESCRIPTION); +} + +// Ref: [IO Control Completion][2.2.7.1]. +// [2.2.7.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/b1722374-0658-47ba-8368-87bf9d3db4d4 +#[rstest] +#[case::reset_port( + IoControlPacket { + ioctl_code: IoctlInternalUsb::ResetPort, + input_buffer: Vec::new(), + output_buffer_size: 0, + }, + IoControlCompletionResult { + hresult: 0, + information: 0, + output_buffer: Vec::new(), + }, +)] +#[case::get_port_status( + IoControlPacket { + ioctl_code: IoctlInternalUsb::GetPortStatus, + input_buffer: Vec::new(), + output_buffer_size: 4, + }, + IoControlCompletionResult { + hresult: 0, + information: 4, + output_buffer: vec![1, 0, 0, 0], + }, +)] +#[case::get_hub_name( + IoControlPacket { + ioctl_code: IoctlInternalUsb::GetHubName, + input_buffer: Vec::new(), + output_buffer_size: 8, + }, + IoControlCompletionResult { + hresult: 0, + information: 4, + output_buffer: vec![b'H', 0, b'1', 0], + }, +)] +fn io_control_pending_completion_round_trip( + #[case] packet: IoControlPacket, + #[case] completion: IoControlCompletionResult, +) { + let mut device = ConnectedDevice::new(); + let expected_ioctl_code = packet.ioctl_code; + let expected_input_buffer = packet.input_buffer.clone(); + let expected_output_buffer_size = packet.output_buffer_size; + let expected_hresult = completion.hresult; + let expected_information = completion.information; + let expected_completion_output = completion.output_buffer.clone(); + + let request = device.server.io_control(packet).expect("IO control should succeed"); + assert!(request.expects_completion); + let request_id = request.request_id; + assert!(device.send_to_client(request.message).is_empty()); + + let ClientEvent::IoControl { + channel_id, + request_id: backend_request_id, + request, + } = device.next_client_event() + else { + panic!("expected IO control event"); + }; + assert_eq!(channel_id, CHANNEL_ID); + assert_eq!(backend_request_id, request_id); + assert_eq!(request.ioctl_code, expected_ioctl_code); + assert_eq!(request.input_buffer, expected_input_buffer); + assert_eq!(request.output_buffer_size, expected_output_buffer_size); + + let response = device + .client + .io_ctl_completion(request_id, completion) + .expect("IO control completion should succeed"); + assert!(device.send_to_server(response).is_empty()); + + let ServerEvent::IoControlCompleted { + channel_id, + request_id: backend_request_id, + completion, + } = device.next_server_event() + else { + panic!("expected IO control completion event"); + }; + assert_eq!(channel_id, CHANNEL_ID); + assert_eq!(backend_request_id, request_id); + assert_eq!(completion.hresult, expected_hresult); + assert_eq!(completion.information, expected_information); + assert_eq!(completion.output_buffer, expected_completion_output); +} + +// Ref: [Internal IO Control Message][2.2.6.4]. +// [2.2.6.4]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/c3f3e320-336d-4d1b-84c9-51e0ed330ffe +#[test] +fn internal_io_control_pending_completion_round_trip() { + let mut device = ConnectedDevice::new(); + let request = InternalIoControlPacket::QueryBusTime; + + let request = device + .server + .internal_io_control(request) + .expect("internal IO control should succeed"); + assert!(request.expects_completion); + let request_id = request.request_id; + assert!(device.send_to_client(request.message).is_empty()); + + let ClientEvent::InternalIoControl { + channel_id, + request_id: backend_request_id, + request, + } = device.next_client_event() + else { + panic!("expected internal IO control event"); + }; + assert_eq!(channel_id, CHANNEL_ID); + assert_eq!(backend_request_id, request_id); + assert!(matches!(request, InternalIoControlPacket::QueryBusTime)); + + let completion = IoControlCompletionResult { + hresult: 0, + information: 4, + output_buffer: vec![42, 0, 0, 0], + }; + let response = device + .client + .internal_io_ctl_completion(request_id, completion) + .expect("internal IO control completion should succeed"); + assert!(device.send_to_server(response).is_empty()); + + let ServerEvent::InternalIoControlCompleted { + channel_id, + request_id: backend_request_id, + completion, + } = device.next_server_event() + else { + panic!("expected internal IO control completion event"); + }; + assert_eq!(channel_id, CHANNEL_ID); + assert_eq!(backend_request_id, request_id); + assert_eq!(completion.hresult, 0); + assert_eq!(completion.information, 4); + assert_eq!(completion.output_buffer, [42, 0, 0, 0]); +} + +// Ref: [Processing a Cancel Request Message][3.3.5.3.1]. +// [3.3.5.3.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/d5315234-d9ba-42dc-bc1b-b421c57a21ae +#[test] +fn cancel_pending_request() { + let mut device = ConnectedDevice::new(); + let request = device + .server + .io_control(IoControlPacket { + ioctl_code: IoctlInternalUsb::GetPortStatus, + input_buffer: Vec::new(), + output_buffer_size: 4, + }) + .expect("IO control should succeed"); + let request_id = request.request_id; + assert!(device.send_to_client(request.message).is_empty()); + assert!(matches!(device.next_client_event(), ClientEvent::IoControl { .. })); + + let cancel = device + .server + .cancel_request(request_id) + .expect("cancel request should succeed"); + assert!(device.send_to_client(cancel).is_empty()); + + let ClientEvent::Cancel { + channel_id, + request_id: backend_request_id, + } = device.next_client_event() + else { + panic!("expected cancel event"); + }; + assert_eq!(channel_id, CHANNEL_ID); + assert_eq!(backend_request_id, request_id); +} diff --git a/crates/ironrdp-testsuite-core/tests/rdpeusb/io/transfers.rs b/crates/ironrdp-testsuite-core/tests/rdpeusb/io/transfers.rs new file mode 100644 index 000000000..84e4f95c8 --- /dev/null +++ b/crates/ironrdp-testsuite-core/tests/rdpeusb/io/transfers.rs @@ -0,0 +1,289 @@ +use ironrdp_rdpeusb::io::{ + TransferInCompletionResult, TransferInPacket, TransferOutCompletionResult, TransferOutPacket, TsUrbInKind, + TsUrbInPacket, TsUrbOutKind, TsUrbOutPacket, UrbFunction, +}; +use ironrdp_rdpeusb::pdu::completion::ts_urb_result::{TsUrbResult, TsUrbResultHeader, TsUrbResultPayload}; +use ironrdp_rdpeusb::pdu::usb_dev::ts_urb::utils::SetupPacket; +use ironrdp_rdpeusb::pdu::usb_dev::ts_urb::{ + TsUrbBulkOrInterruptTransfer, TsUrbControlGetConfigRequest, TsUrbControlGetInterfaceRequest, + TsUrbControlGetStatusRequest, TsUrbControlTransfer, TsUrbControlVendorClassRequest, TsUrbIsochTransfer, +}; +use ironrdp_rdpeusb::pdu::utils::UsbdIsoPacketDesc; +use rstest::rstest; + +use super::{CHANNEL_ID, ClientEvent, ConnectedDevice, ServerEvent}; + +fn successful_urb_result() -> TsUrbResult { + TsUrbResult { + header: TsUrbResultHeader { usbd_status: 0 }, + payload: TsUrbResultPayload::Raw(Vec::new()), + } +} + +// Refs: [URB Completion][2.2.7.2] and [URB Completion No Data][2.2.7.3]. +// [2.2.7.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/5bfa9c84-a74b-4942-9d09-e770b21081eb +// [2.2.7.3]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/994fac8f-d258-47a6-aa35-48783abe49ec +#[rstest] +#[case::get_configuration( + TransferInPacket { + ts_urb: TsUrbInPacket { + kind: TsUrbInKind::CtlGetConfig(TsUrbControlGetConfigRequest), + func: UrbFunction::URB_FUNCTION_GET_CONFIGURATION, + }, + output_buffer_size: 1, + }, + vec![0x01], +)] +#[case::get_configuration_without_data( + TransferInPacket { + ts_urb: TsUrbInPacket { + kind: TsUrbInKind::CtlGetConfig(TsUrbControlGetConfigRequest), + func: UrbFunction::URB_FUNCTION_GET_CONFIGURATION, + }, + output_buffer_size: 1, + }, + Vec::new(), +)] +#[case::get_interface( + TransferInPacket { + ts_urb: TsUrbInPacket { + kind: TsUrbInKind::CtlGetIface(TsUrbControlGetInterfaceRequest { interface: 2 }), + func: UrbFunction::URB_FUNCTION_GET_INTERFACE, + }, + output_buffer_size: 1, + }, + vec![0x02], +)] +#[case::get_status( + TransferInPacket { + ts_urb: TsUrbInPacket { + kind: TsUrbInKind::CtlGetStatus(TsUrbControlGetStatusRequest { index: 0x81 }), + func: UrbFunction::URB_FUNCTION_GET_STATUS_FROM_ENDPOINT, + }, + output_buffer_size: 2, + }, + vec![0x01, 0x00], +)] +fn transfer_in_completion_round_trip(#[case] packet: TransferInPacket, #[case] output_buffer: Vec) { + let mut device = ConnectedDevice::new(); + let expected_kind = packet.ts_urb.kind.clone(); + let expected_func = packet.ts_urb.func; + let expected_output_buffer_size = packet.output_buffer_size; + let request = device.server.transfer_in(packet).expect("transfer in should succeed"); + assert!(request.expects_completion); + let request_id = request.request_id; + assert!(device.send_to_client(request.message).is_empty()); + + let ClientEvent::TransferIn { + channel_id, + request_id: backend_request_id, + request, + } = device.next_client_event() + else { + panic!("expected transfer in event"); + }; + assert_eq!(channel_id, CHANNEL_ID); + assert_eq!(backend_request_id, request_id); + assert_eq!(request.ts_urb.kind, expected_kind); + assert_eq!(request.ts_urb.func, expected_func); + assert_eq!(request.output_buffer_size, expected_output_buffer_size); + + let response = device + .client + .transfer_in_completion( + request_id, + TransferInCompletionResult { + ts_urb_result: successful_urb_result(), + hresult: 0, + output_buffer: output_buffer.clone(), + }, + ) + .expect("transfer in completion should succeed"); + assert!(device.send_to_server(response).is_empty()); + + let ServerEvent::TransferInCompleted { + channel_id, + request_id: backend_request_id, + completion, + } = device.next_server_event() + else { + panic!("expected transfer in completion event"); + }; + assert_eq!(channel_id, CHANNEL_ID); + assert_eq!(backend_request_id, request_id); + assert_eq!(completion.ts_urb_result, successful_urb_result()); + assert_eq!(completion.hresult, 0); + assert_eq!(completion.output_buffer, output_buffer); +} + +// Ref: [Transfer Out Request][2.2.6.8]. +// [2.2.6.8]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/6d6c85b2-47bb-4674-975a-dc7d8ed684cd +#[rstest] +#[case::bulk_or_interrupt( + TransferOutPacket { + ts_urb: TsUrbOutPacket { + kind: TsUrbOutKind::BulkInterruptTransfer(TsUrbBulkOrInterruptTransfer { + pipe_handle: 7, + transfer_flags: 0, + }), + no_ack: false, + func: UrbFunction::URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER, + }, + output_buffer: vec![1, 2, 3], + }, + TransferOutCompletionResult { + ts_urb_result: successful_urb_result(), + hresult: 0, + output_buffer_size: 3, + }, +)] +#[case::control( + TransferOutPacket { + ts_urb: TsUrbOutPacket { + kind: TsUrbOutKind::CtlTransfer(TsUrbControlTransfer { + pipe: 0, + transfer_flags: 0, + setup_packet: SetupPacket { + request_type: 0, + request: 9, + value: 1, + index: 0, + length: 0, + }, + }), + no_ack: false, + func: UrbFunction::URB_FUNCTION_CONTROL_TRANSFER, + }, + output_buffer: Vec::new(), + }, + TransferOutCompletionResult { + ts_urb_result: successful_urb_result(), + hresult: 0, + output_buffer_size: 0, + }, +)] +#[case::vendor( + TransferOutPacket { + ts_urb: TsUrbOutPacket { + kind: TsUrbOutKind::VendorClassReq(TsUrbControlVendorClassRequest { + transfer_flags: 0, + request: 1, + value: 2, + index: 3, + }), + no_ack: false, + func: UrbFunction::URB_FUNCTION_VENDOR_DEVICE, + }, + output_buffer: vec![4, 5], + }, + TransferOutCompletionResult { + ts_urb_result: successful_urb_result(), + hresult: 0, + output_buffer_size: 2, + }, +)] +fn transfer_out_completion_round_trip( + #[case] packet: TransferOutPacket, + #[case] completion: TransferOutCompletionResult, +) { + let mut device = ConnectedDevice::new(); + let expected_kind = packet.ts_urb.kind.clone(); + let expected_func = packet.ts_urb.func; + let expected_output_buffer = packet.output_buffer.clone(); + let expected_ts_urb_result = completion.ts_urb_result.clone(); + let expected_hresult = completion.hresult; + let expected_output_buffer_size = completion.output_buffer_size; + let request = device.server.transfer_out(packet).expect("transfer out should succeed"); + assert!(request.expects_completion); + let request_id = request.request_id; + assert!(device.send_to_client(request.message).is_empty()); + + let ClientEvent::TransferOut { + channel_id, + request_id: backend_request_id, + request, + } = device.next_client_event() + else { + panic!("expected transfer out event"); + }; + assert_eq!(channel_id, CHANNEL_ID); + assert_eq!(backend_request_id, request_id); + assert_eq!(request.ts_urb.kind, expected_kind); + assert!(!request.ts_urb.no_ack); + assert_eq!(request.ts_urb.func, expected_func); + assert_eq!(request.output_buffer, expected_output_buffer); + + let response = device + .client + .transfer_out_completion(request_id, completion) + .expect("transfer out completion should succeed"); + assert!(device.send_to_server(response).is_empty()); + + let ServerEvent::TransferOutCompleted { + channel_id, + request_id: backend_request_id, + completion, + } = device.next_server_event() + else { + panic!("expected transfer out completion event"); + }; + assert_eq!(channel_id, CHANNEL_ID); + assert_eq!(backend_request_id, request_id); + assert_eq!(completion.ts_urb_result, expected_ts_urb_result); + assert_eq!(completion.hresult, expected_hresult); + assert_eq!(completion.output_buffer_size, expected_output_buffer_size); +} + +#[test] +fn transfer_out_no_ack() { + let mut device = ConnectedDevice::new(); + let request = device + .server + .transfer_out(TransferOutPacket { + ts_urb: TsUrbOutPacket { + kind: TsUrbOutKind::IsochTransfer(TsUrbIsochTransfer { + pipe_handle: 7, + transfer_flags: 0, + start_frame: 100, + error_count: 0, + iso_packet: vec![UsbdIsoPacketDesc { + offset: 0, + length: 3, + status: 0, + }], + }), + no_ack: true, + func: UrbFunction::URB_FUNCTION_ISOCH_TRANSFER, + }, + output_buffer: vec![1, 2, 3], + }) + .expect("no-ack transfer out should succeed"); + assert!(!request.expects_completion); + let request_id = request.request_id; + assert!(device.send_to_client(request.message).is_empty()); + + let ClientEvent::TransferOutNoAck { + channel_id, + request_id: backend_request_id, + request, + } = device.next_client_event() + else { + panic!("expected no-ack transfer out event"); + }; + assert_eq!(channel_id, CHANNEL_ID); + assert_eq!(backend_request_id, request_id); + let TsUrbOutKind::IsochTransfer(urb) = request.ts_urb.kind else { + panic!("expected isochronous transfer"); + }; + assert_eq!(urb.pipe_handle, 7); + assert_eq!(urb.transfer_flags, 0); + assert_eq!(urb.start_frame, 100); + assert_eq!(urb.error_count, 0); + assert_eq!(urb.iso_packet.len(), 1); + assert_eq!(urb.iso_packet[0].offset, 0); + assert_eq!(urb.iso_packet[0].length, 3); + assert_eq!(urb.iso_packet[0].status, 0); + assert!(request.ts_urb.no_ack); + assert_eq!(request.ts_urb.func, UrbFunction::URB_FUNCTION_ISOCH_TRANSFER); + assert_eq!(request.output_buffer, [1, 2, 3]); +} diff --git a/crates/ironrdp-testsuite-core/tests/rdpeusb/mod.rs b/crates/ironrdp-testsuite-core/tests/rdpeusb/mod.rs index 9741b9b5c..d92fe641b 100644 --- a/crates/ironrdp-testsuite-core/tests/rdpeusb/mod.rs +++ b/crates/ironrdp-testsuite-core/tests/rdpeusb/mod.rs @@ -1,6 +1,10 @@ -use ironrdp_rdpeusb::io::device::{ - DeviceInfo, UsbBcdVersion, UsbClassCodes, UsbConfigInfo, UsbConnectionSpeed, UsbDeviceDescriptorInfo, - UsbDeviceLocation, UsbInterfaceInfo, +use ironrdp_core::encode_vec; +use ironrdp_rdpeusb::{ + io::device::{ + DeviceInfo, UsbBcdVersion, UsbClassCodes, UsbConfigInfo, UsbConnectionSpeed, UsbDeviceDescriptorInfo, + UsbDeviceLocation, UsbInterfaceInfo, + }, + pdu::header::InterfaceId, }; fn simple_device_info() -> DeviceInfo { @@ -31,5 +35,17 @@ fn simple_device_info() -> DeviceInfo { } } +const STREAM_ID_PROXY: u32 = 1; + +fn proxy_iface_id(iface: InterfaceId) -> u32 { + u32::from(iface) | (STREAM_ID_PROXY << 30) +} + +fn encode_pdu(pdu: &T) -> Vec { + encode_vec(pdu).expect("encode should succeed") +} + mod client; mod device; +mod io; +mod server; diff --git a/crates/ironrdp-testsuite-core/tests/rdpeusb/server.rs b/crates/ironrdp-testsuite-core/tests/rdpeusb/server.rs new file mode 100644 index 000000000..234d65a41 --- /dev/null +++ b/crates/ironrdp-testsuite-core/tests/rdpeusb/server.rs @@ -0,0 +1,226 @@ +use std::sync::mpsc::{self, Sender, TryRecvError}; + +use ironrdp_core::{decode, encode_vec}; +use ironrdp_dvc::{DvcMessage, DvcProcessor as _}; +use ironrdp_pdu::PduResult; +use ironrdp_rdpeusb::CHANNEL_NAME; +use ironrdp_rdpeusb::io::device::add_device_from_info; +use ironrdp_rdpeusb::io::{ + DeviceAnnounce, DeviceText, IoControlCompletionResult, RequestId, TransferInCompletionResult, + TransferOutCompletionResult, +}; +use ironrdp_rdpeusb::pdu::caps::{Capability, RimExchangeCapabilityResponse}; +use ironrdp_rdpeusb::pdu::header::InterfaceId; +use ironrdp_rdpeusb::pdu::notify::{ChannelCreated, Direction}; +use ironrdp_rdpeusb::pdu::sink::AddVirtualChannel; +use ironrdp_rdpeusb::pdu::{ + UrbdrcClientControlPdu, UrbdrcClientDevicePdu, UrbdrcServerControlPdu, UrbdrcServerDevicePdu, +}; +use ironrdp_rdpeusb::server::{ + UrbdrcControlServer, UrbdrcControlServerBackend, UrbdrcDeviceServer, UrbdrcDeviceServerBackend, +}; + +use super::{encode_pdu, proxy_iface_id, simple_device_info}; + +fn decode_control_msg(message: &DvcMessage) -> UrbdrcServerControlPdu { + let encoded = encode_vec(message.as_ref()).expect("encode should succeed"); + decode(&encoded).expect("decode should succeed") +} + +fn decode_device_msg(message: &DvcMessage) -> UrbdrcServerDevicePdu { + let encoded = encode_vec(message.as_ref()).expect("encode should succeed"); + decode(&encoded).expect("decode should succeed") +} + +struct TestControlBackend { + device_channel_created: Sender<()>, +} + +impl UrbdrcControlServerBackend for TestControlBackend { + fn create_device_chan(&mut self) -> PduResult<()> { + self.device_channel_created + .send(()) + .expect("device channel receiver should remain connected"); + Ok(()) + } +} + +struct TestDeviceBackend { + device_announced: Sender, +} + +impl UrbdrcDeviceServerBackend for TestDeviceBackend { + fn add_device(&mut self, device: DeviceAnnounce) -> PduResult<()> { + self.device_announced + .send(device) + .expect("device announcement receiver should remain connected"); + Ok(()) + } + + fn device_text(&mut self, _device_text: DeviceText) {} + + fn io_control_completed( + &mut self, + _channel_id: u32, + _request_id: RequestId, + _completion: IoControlCompletionResult, + ) -> PduResult<()> { + Ok(()) + } + + fn internal_io_control_completed( + &mut self, + _channel_id: u32, + _request_id: RequestId, + _completion: IoControlCompletionResult, + ) -> PduResult<()> { + Ok(()) + } + + fn transfer_in_completed( + &mut self, + _channel_id: u32, + _request_id: RequestId, + _completion: TransferInCompletionResult, + ) -> PduResult<()> { + Ok(()) + } + + fn transfer_out_completed( + &mut self, + _channel_id: u32, + _request_id: RequestId, + _completion: TransferOutCompletionResult, + ) -> PduResult<()> { + Ok(()) + } +} + +// Ref: [Channel Setup Sequence][1.3.1.1] +// [1.3.1.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/55bb34fc-7fd0-4aca-8739-5fb6759b66fc +#[test] +fn capability_exchange_sequence() { + let (device_channel_created, channel_created_rx) = mpsc::channel(); + let backend = Box::new(TestControlBackend { device_channel_created }); + let mut server = UrbdrcControlServer::new(backend); + + assert_eq!(server.channel_name(), CHANNEL_NAME); + + let resp = server.start(10).expect("start should succeed"); + assert_eq!(resp.len(), 1); + let UrbdrcServerControlPdu::Caps(request) = decode_control_msg(&resp[0]) else { + panic!("expected capability request"); + }; + assert_eq!(request.capability, Capability::RimCapabilityVersion01); + + let resp = server + .process( + 10, + &encode_pdu(&UrbdrcClientControlPdu::Caps(RimExchangeCapabilityResponse { + msg_id: request.msg_id, + capability: Capability::RimCapabilityVersion01, + result: 0, + })), + ) + .expect("capability response should succeed"); + assert_eq!(resp.len(), 2); + + let UrbdrcServerControlPdu::IfaceRelease(release) = decode_control_msg(&resp[0]) else { + panic!("expected capabilities interface release"); + }; + assert_eq!(release.iface_id, u32::from(InterfaceId::CAPABILITIES)); + + let UrbdrcServerControlPdu::ChanCreated(channel_created_request) = decode_control_msg(&resp[1]) else { + panic!("expected channel-created request"); + }; + assert_eq!(channel_created_request.direction, Direction::ToClient); + + let resp = server + .process( + 10, + &encode_pdu(&UrbdrcClientControlPdu::ChanCreated(ChannelCreated { + msg_id: channel_created_request.msg_id, + direction: Direction::ToServer, + })), + ) + .expect("channel-created response should succeed"); + assert_eq!(resp.len(), 1); + + let UrbdrcServerControlPdu::IfaceRelease(release) = decode_control_msg(&resp[0]) else { + panic!("expected notification interface release"); + }; + assert_eq!(release.iface_id, proxy_iface_id(InterfaceId::NOTIFY_CLIENT)); + + let resp = server + .process( + 10, + &encode_pdu(&UrbdrcClientControlPdu::AddChan(AddVirtualChannel { msg_id: 0 })), + ) + .expect("add virtual channel should succeed"); + assert!(resp.is_empty()); + channel_created_rx.try_recv().expect("backend should be notified"); + assert!( + matches!(channel_created_rx.try_recv(), Err(TryRecvError::Empty)), + "backend should be notified exactly once" + ); +} + +// Ref: [New Device Sequence][1.3.1.2] +// [1.3.1.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/7e3da218-9cdc-4ebd-bb76-e70202c7f264 +#[test] +fn new_device_sequence() { + let udev_iface = InterfaceId::try_from(4).expect("valid device interface id"); + let completion_iface = InterfaceId::try_from(5).expect("valid completion interface id"); + let (device_announced, announcement_rx) = mpsc::channel(); + let backend = Box::new(TestDeviceBackend { device_announced }); + let mut server = UrbdrcDeviceServer::new(backend, completion_iface).expect("device server should be created"); + + assert_eq!(server.channel_name(), CHANNEL_NAME); + + let resp = server.start(11).expect("start should succeed"); + assert_eq!(resp.len(), 1); + let UrbdrcServerDevicePdu::ChanCreated(channel_created_request) = decode_device_msg(&resp[0]) else { + panic!("expected channel-created request"); + }; + assert_eq!(channel_created_request.direction, Direction::ToClient); + + let resp = server + .process( + 11, + &encode_pdu(&UrbdrcClientDevicePdu::ChanCreated(ChannelCreated { + msg_id: channel_created_request.msg_id, + direction: Direction::ToServer, + })), + ) + .expect("channel-created response should succeed"); + assert_eq!(resp.len(), 1); + let UrbdrcServerDevicePdu::IfaceRelease(release) = decode_device_msg(&resp[0]) else { + panic!("expected notification interface release"); + }; + assert_eq!(release.iface_id, proxy_iface_id(InterfaceId::NOTIFY_CLIENT)); + + let add_device = add_device_from_info(udev_iface, &simple_device_info()).expect("ADD_DEVICE should be generated"); + let resp = server + .process(11, &encode_pdu(&UrbdrcClientDevicePdu::AddDev(add_device))) + .expect("add device should succeed"); + assert_eq!(resp.len(), 2); + + let UrbdrcServerDevicePdu::IfaceRelease(release) = decode_device_msg(&resp[0]) else { + panic!("expected device sink interface release"); + }; + assert_eq!(release.iface_id, proxy_iface_id(InterfaceId::DEVICE_SINK)); + + let UrbdrcServerDevicePdu::RegReqCb(register) = decode_device_msg(&resp[1]) else { + panic!("expected request callback registration"); + }; + assert_eq!(register.udev_iface, udev_iface); + assert_eq!(register.request_completion, Some(completion_iface)); + + announcement_rx + .try_recv() + .expect("backend should receive device announcement"); + assert!( + matches!(announcement_rx.try_recv(), Err(TryRecvError::Empty)), + "device should be announced exactly once" + ); +}