Skip to content
Open
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
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions crates/ironrdp-rdpeusb/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ categories.workspace = true

publish = false

[features]
default = []
std = []

[dependencies]
ironrdp-core = { path = "../ironrdp-core", version = "0.1", features = ["alloc"] } # public
ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.7", features = ["alloc"] } # public
ironrdp-str = { path = "../ironrdp-str", version = "0.1" }

[lints]
workspace = true
5 changes: 5 additions & 0 deletions crates/ironrdp-rdpeusb/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
#![cfg_attr(doc, doc = include_str!("../README.md"))]
#![cfg_attr(not(feature = "std"), no_std)]

extern crate alloc;

pub mod pdu;
124 changes: 124 additions & 0 deletions crates/ironrdp-rdpeusb/src/pdu/caps.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use ironrdp_core::{
DecodeError, DecodeResult, Encode, EncodeResult, ReadCursor, WriteCursor, ensure_fixed_part_size,
unsupported_value_err,
};

use crate::ensure_payload_size;
use crate::pdu::header::SharedMsgHeader;
use crate::pdu::utils::HResult;

/// Identifies the interface manipulation capabilties of server/client.
#[repr(u32)]
#[non_exhaustive]
#[derive(Debug, Clone, Copy)]
pub enum Capability {
#[doc(alias = "RIM_CAPABILITY_VERSION_01")]
RimCapabilityVersion01 = 0x1,
}

impl Capability {
pub const FIXED_PART_SIZE: usize = size_of::<Self>();
}

impl TryFrom<u32> for Capability {
type Error = DecodeError;

fn try_from(value: u32) -> Result<Self, Self::Error> {
if value == 0x1 {
Ok(Self::RimCapabilityVersion01)
} else {
Err(unsupported_value_err!(
"CapabilityValue",
"is not: `RIM_CAPABILITY_VERSION_01 = 0x1`".into()
))
}
}
}

#[doc(alias = "RIM_EXCHANGE_CAPABILITY_REQUEST")]
pub struct RimExchangeCapabilityRequest {
pub header: SharedMsgHeader,
pub capability: Capability,
}

impl RimExchangeCapabilityRequest {
pub const PAYLOAD_SIZE: usize = Capability::FIXED_PART_SIZE;

pub const FIXED_PART_SIZE: usize = Self::PAYLOAD_SIZE + SharedMsgHeader::SIZE_WHEN_NOT_RSP;

pub fn decode(src: &mut ReadCursor<'_>, header: SharedMsgHeader) -> DecodeResult<Self> {
ensure_payload_size!(in: src);
let capability = Capability::try_from(src.read_u32())?;

Ok(Self { header, capability })
}
}

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

#[expect(clippy::as_conversions)]
dst.write_u32(self.capability as u32);

Ok(())
}

fn name(&self) -> &'static str {
"RIM_EXCHANGE_CAPABILITY_REQUEST"
}

fn size(&self) -> usize {
Self::FIXED_PART_SIZE
}
}

#[doc(alias = "RIM_EXCHANGE_CAPABILITY_RESPONSE")]
pub struct RimExchangeCapabilityResponse {
pub header: SharedMsgHeader,
pub capability: Capability,
pub result: HResult,
}

impl RimExchangeCapabilityResponse {
pub const PAYLOAD_SIZE: usize = Capability::FIXED_PART_SIZE + size_of::<HResult>();

pub const FIXED_PART_SIZE: usize = Self::PAYLOAD_SIZE + SharedMsgHeader::SIZE_WHEN_RSP;

pub fn decode(src: &mut ReadCursor<'_>, header: SharedMsgHeader) -> DecodeResult<Self> {
ensure_payload_size!(in: src);
let capability = Capability::try_from(src.read_u32())?;

let result = src.read_u32();

Ok(Self {
header,
capability,
result,
})
}
}

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

self.header.encode(dst)?;

#[expect(clippy::as_conversions)]
dst.write_u32(self.capability as u32);

dst.write_u32(self.result);

Ok(())
}

fn name(&self) -> &'static str {
"RIM_EXCHANGE_CAPABILITY_RESPONSE"
}

fn size(&self) -> usize {
Self::FIXED_PART_SIZE
}
}
84 changes: 84 additions & 0 deletions crates/ironrdp-rdpeusb/src/pdu/chan_notify.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//! PDU's specific to the [Channel Notification][1] interface.
//!
//! Used by both the client and the server to communicate with the other side. For server-to-client
//! notifications, the default interface ID is `0x00000002`; for client-to-server notifications, the
//! default interface ID is `0x00000003`.
//!
//! [1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/a7ea1b33-80bb-4197-a502-ee62394399c0

use alloc::borrow::ToOwned as _;

use ironrdp_core::{
DecodeResult, Encode, EncodeResult, ReadCursor, WriteCursor, ensure_fixed_part_size, unsupported_value_err,
};

use crate::ensure_payload_size;
use crate::pdu::header::SharedMsgHeader;

/// The `CHANNEL_CREATED` message is sent from both the client and the server to inform the other
/// side of the RDP USB device redirection version supported.
//
/// * [MS-RDPEUSB § 2.2.5.1 Channel Created Message (CHANNEL_CREATED)][1]
///
/// [1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/e2859c23-acda-47d4-a2fc-9e7415e4b8d6
///
// NOTE: Implementation of IO_CONTROL message's input_buffer_size and input_buffer fields may
// need modification if a newer version of MS-RDPEUSB is realeased.
#[doc(alias = "CHANNEL_CREATED")]
pub struct ChannelCreated {
pub header: SharedMsgHeader,
}

impl ChannelCreated {
pub const PAYLOAD_SIZE: usize = size_of::<u32>() * 3;

pub const FIXED_PART_SIZE: usize = Self::PAYLOAD_SIZE + SharedMsgHeader::SIZE_WHEN_NOT_RSP;

/// The major version of RDP USB redirection supported.
#[doc(alias = "MajorVersion")]
pub const MAJOR_VER: u32 = 1;

/// The minor version of RDP USB redirection supported.
#[doc(alias = "MinorVersion")]
pub const MINOR_VER: u32 = 0;

/// The capabilities of RDP USB redirection supported.
#[doc(alias = "Capabilities")]
pub const CAPS: u32 = 0;

pub fn decode(src: &mut ReadCursor<'_>, header: SharedMsgHeader) -> DecodeResult<Self> {
ensure_payload_size!(in: src);
if src.read_u32() != Self::MAJOR_VER {
return Err(unsupported_value_err!("MajorVersion", "is not: 1".to_owned()));
}
if src.read_u32() != Self::MINOR_VER {
return Err(unsupported_value_err!("MinorVersion", "is not: 0".to_owned()));
}
if src.read_u32() != Self::CAPS {
return Err(unsupported_value_err!("Capabilities", "is not: 0".to_owned()));
}
Ok(Self { header })
}
}

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

self.header.encode(dst)?;

dst.write_u32(Self::MAJOR_VER);
dst.write_u32(Self::MINOR_VER);
dst.write_u32(Self::CAPS);

Ok(())
}

fn name(&self) -> &'static str {
"CHANNEL_CREATED"
}

fn size(&self) -> usize {
Self::FIXED_PART_SIZE
}
}
Loading
Loading