Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/ironrdp-rdpeusb/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ impl DvcProcessor for UrbdrcDeviceClient {
return Ok(Vec::new());
};

let internal_io_ctl_packet = internal_io_ctl_pdu.into();
let internal_io_ctl_packet = internal_io_ctl_pdu.try_into()?;
if let Some(internal_io_ctl_response) =
self.backend
.internal_io_control(channel_id, request_id, internal_io_ctl_packet)?
Expand Down
48 changes: 26 additions & 22 deletions crates/ironrdp-rdpeusb/src/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ pub struct IoControlCompletionResult {
pub information: u32,
/// Data produced by the request.
///
/// Its length must not exceed the request's [`IoControlPacket::output_buffer_size`] or
/// [`InternalIoControlPacket::output_buffer_size`]. For failures other than an
/// Its length must not exceed the request's output buffer size. For failures other than an
/// insufficient-buffer result, this must be empty.
pub output_buffer: Vec<u8>,
}
Expand Down Expand Up @@ -141,34 +140,39 @@ impl IoControlPacket {

/// Backend-facing form of an RDPEUSB `INTERNAL_IO_CONTROL` request.
#[derive(Debug, Clone)]
pub struct InternalIoControlPacket {
/// RDPEUSB-defined internal operation to perform.
pub ioctl_code: UsbInternalIoctlCode,
/// Raw input supplied to the operation.
pub input_buffer: Vec<u8>,
/// Maximum number of bytes that may be returned in the completion's output buffer.
pub output_buffer_size: u32,
pub enum InternalIoControlPacket {
QueryBusTime,
}

impl InternalIoControlPacket {
pub(crate) fn into_pdu(self, msg_id: MessageId, req_id: RequestId, udev_iface: InterfaceId) -> InternalIoControl {
InternalIoControl {
msg_id,
udev_iface,
ioctl_code: self.ioctl_code,
input_buffer: self.input_buffer,
output_buffer_size: self.output_buffer_size,
req_id,
match self {
Self::QueryBusTime => InternalIoControl {
msg_id,
udev_iface,
ioctl_code: UsbInternalIoctlCode::QUERY_BUS_TIME,
input_buffer: Vec::new(),
output_buffer_size: 4,
req_id,
},
}
}
}

impl From<InternalIoControl> for InternalIoControlPacket {
fn from(value: InternalIoControl) -> Self {
Self {
ioctl_code: value.ioctl_code,
input_buffer: value.input_buffer,
output_buffer_size: value.output_buffer_size,
impl TryFrom<InternalIoControl> for InternalIoControlPacket {
type Error = PduError;
fn try_from(value: InternalIoControl) -> PduResult<Self> {
match value.ioctl_code {
UsbInternalIoctlCode::QUERY_BUS_TIME => {
if !value.input_buffer.is_empty() {
return Err(pdu_other_err!("internal io control input buffer must be empty"));
}
if value.output_buffer_size != 4 {
return Err(pdu_other_err!("internal io control output buffer size must be 4"));
}
Ok(Self::QueryBusTime)
}
_ => Err(pdu_other_err!("unsupported InternalIoControl ioctl code")),
}
}
}
Expand Down
76 changes: 24 additions & 52 deletions crates/ironrdp-rdpeusb/src/pdu/usb_dev/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,10 @@ impl IoControl {
};
let input_buffer_size = src.read_u32().try_into().map_err(|e| other_err!(source: e))?;
ensure_size!(in: src,
size: input_buffer_size /* InputBuffer */ + 4 /* OutputBufferSize */ + 4 /* RequestId */);
size: input_buffer_size);
// TODO: size limit
Comment thread
uchouT marked this conversation as resolved.
let input_buffer = src.read_slice(input_buffer_size).to_vec();
ensure_size!(in: src, size: 4 /*output buffer size */ + 4 /* request id */);
let output_buffer_size = src.read_u32();
let req_id = src.read_u32();
let io_control = Self {
Expand Down Expand Up @@ -374,11 +375,11 @@ impl IoctlInternalUsb {
/// [\[MS-RDPEUSB\] 2.2.13 USB Internal IO Control Code][1].
///
/// [1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/55d1cd44-eda3-4cba-931c-c3cb8b3c3c92
#[repr(u32)]
#[non_exhaustive]
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Copy)]
#[doc(alias = "IOCTL_TSUSBGD_IOCTL_USBDI_QUERY_BUS_TIME")]
pub enum UsbInternalIoctlCode {
pub struct UsbInternalIoctlCode(pub u32);

impl UsbInternalIoctlCode {
/// [\[MS-RDPEUSB\] 2.2.13.1 IOCTL_TSUSBGD_IOCTL_USBDI_QUERY_BUS_TIME][1].
///
/// Sent when the server receives a request its system to query the device's current frame
Expand All @@ -389,11 +390,9 @@ pub enum UsbInternalIoctlCode {
///
/// [1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/68506bc9-fedc-4fc1-b826-3cdbb1988774
#[doc(alias = "IOCTL_TSUSBGD_IOCTL_USBDI_QUERY_BUS_TIME")]
IoctlTsusbgdIoctlUsbdiQueryBusTime = 0x00224000,
pub const QUERY_BUS_TIME: Self = Self(0x00224000);
}

const IOCTL_TSUSBGD_IOCTL_USBDI_QUERY_BUS_TIME: u32 = 0x00224000;

/// [\[MS-RDPEUSB\] 2.2.6.4 Internal IO Control Message (INTERNAL_IO_CONTROL)][1] message.
///
/// Sent from the server to the client to submit an internal IO control request to the USB device.
Expand All @@ -404,28 +403,18 @@ const IOCTL_TSUSBGD_IOCTL_USBDI_QUERY_BUS_TIME: u32 = 0x00224000;
pub struct InternalIoControl {
pub msg_id: MessageId,
pub udev_iface: InterfaceId,
// Should make adding new codes easier.
pub ioctl_code: UsbInternalIoctlCode,
/// As of **v20240423**, all codes used for this message require sending an empty input buffer.
///
/// * [MS-RDPEUSB 2.2.13 USB Internal IO Control Code][1]
///
/// [1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/55d1cd44-eda3-4cba-931c-c3cb8b3c3c92
pub input_buffer: Vec<u8>,
pub output_buffer_size: u32,
pub req_id: RequestIdIoctl,
}

impl InternalIoControl {
#[expect(clippy::identity_op, reason = "for developer documentation purposes?")]
pub const PAYLOAD_SIZE: usize = 4 // IoControlCode
pub const PAYLOAD_MIN_SIZE: usize = 4 // IoControlCode
+ 4 // InputBufferSize
+ 0 // InputBuffer
+ 4 // OutputBufferSize
+ 4; // RequestId

pub const FIXED_PART_SIZE: usize = SharedMsgHeader::SIZE_REQ /* Header */ + Self::PAYLOAD_SIZE;

pub fn header(&self) -> SharedMsgHeader {
SharedMsgHeader {
iface_id: self.udev_iface.with_mask(Mask::Proxy),
Expand All @@ -435,40 +424,23 @@ impl InternalIoControl {
}

pub(crate) fn decode(src: &mut ReadCursor<'_>, msg_id: MessageId, udev_iface: InterfaceId) -> DecodeResult<Self> {
ensure_size!(in: src, size: Self::PAYLOAD_SIZE);
ensure_size!(in: src, size: Self::PAYLOAD_MIN_SIZE);

{
let code = src.read_u32();
if code != IOCTL_TSUSBGD_IOCTL_USBDI_QUERY_BUS_TIME {
return Err(unsupported_value_err!(
"INTERNAL_IO_CONTROL::IoControlCode",
format!("{code:#X}")
));
}
}
{
let size = src.read_u32(/* InputBufferSize */);
if size != 0 {
return Err(unsupported_value_err!(
"INTERNAL_IO_CONTROL::InputBufferSize",
format!("{size:#X}")
));
}
}
let code = src.read_u32();

let size = src.read_u32().try_into().map_err(|e| other_err!(source: e))?;
ensure_size!(in: src, size: size);
let input_buffer = src.read_slice(size).to_vec();
Comment thread
uchouT marked this conversation as resolved.

ensure_size!(in: src, size: 4 /*output buffer size */ + 4 /* request id */);
let output_buffer_size = src.read_u32(/* OutputBufferSize */);
if output_buffer_size != 0x4 {
return Err(unsupported_value_err!(
"INTERNAL_IO_CONTROL::OutputBufferSize",
format!("{output_buffer_size:#X}")
));
}
let req_id = src.read_u32();

Ok(Self {
msg_id,
udev_iface,
ioctl_code: UsbInternalIoctlCode::IoctlTsusbgdIoctlUsbdiQueryBusTime,
input_buffer: Vec::new(),
ioctl_code: UsbInternalIoctlCode(code),
input_buffer,
output_buffer_size,
req_id,
})
Expand All @@ -477,12 +449,12 @@ impl InternalIoControl {

impl Encode for InternalIoControl {
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
ensure_fixed_part_size!(in: dst);

ensure_size!(in: dst, size: self.size());
self.header().encode(dst)?;
dst.write_u32(IOCTL_TSUSBGD_IOCTL_USBDI_QUERY_BUS_TIME); // IoControlCode
dst.write_u32(0x0); // InputBufferSize
dst.write_u32(0x4); // OutputBufferSize
dst.write_u32(self.ioctl_code.0); // IoControlCode
dst.write_u32(self.input_buffer.len().try_into().map_err(|e| other_err!(source: e))?); // InputBufferSize
dst.write_slice(&self.input_buffer); // InputBuffer
dst.write_u32(self.output_buffer_size); // OutputBufferSize
dst.write_u32(self.req_id);

Ok(())
Expand All @@ -493,7 +465,7 @@ impl Encode for InternalIoControl {
}

fn size(&self) -> usize {
Self::FIXED_PART_SIZE
SharedMsgHeader::SIZE_REQ + Self::PAYLOAD_MIN_SIZE + self.input_buffer.len()
}
}

Expand Down
11 changes: 1 addition & 10 deletions crates/ironrdp-rdpeusb/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,20 +307,11 @@ impl UrbdrcDeviceServer {
let udev_iface = self.usb_device_iface()?;
let request_id = self.request_id_alloc.alloc();

// Currently, INTERNAL_IO_CONTROL is specified with an empty input buffer and a fixed 4-byte output buffer.
if !internal_io_ctl_packet.input_buffer.is_empty() {
return Err(pdu_other_err!("internal io control input buffer must be empty"));
}
if internal_io_ctl_packet.output_buffer_size != 4 {
return Err(pdu_other_err!("internal io control output buffer size must be 4"));
}

let output_buffer_size = 4;
let request = internal_io_ctl_packet.into_pdu(self.msg_alloc.alloc(), request_id, udev_iface);
self.insert_pending_io(
request_id,
Pending::InternalIoCtl {
max_output_buf_size: output_buffer_size,
max_output_buf_size: request.output_buffer_size,
},
)?;

Expand Down
Loading