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

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

98 changes: 93 additions & 5 deletions crates/ironrdp-dvc/src/server.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::collections::btree_map::Entry;
use alloc::vec::Vec;
use core::any::TypeId;
use core::fmt;
use core::sync::atomic::AtomicUsize;

use ironrdp_core::{Decode as _, DecodeResult, ReadCursor, impl_as_any, invalid_field_err};
use ironrdp_pdu::{self as pdu, decode_err, encode_err, pdu_other_err};
Expand Down Expand Up @@ -42,6 +44,54 @@ impl Drop for DynamicChannel {
}
}

/// A reserved dynamic channel ID awaiting a channel processor.
///
/// The ID is provisional until [`Self::create`] is called. Reserved IDs increase
/// monotonically and are never reused for the lifetime of the server. Dropping a
/// reservation without creating the channel permanently skips its ID.
#[derive(Debug)]
pub struct DynamicChannelReservation {
server_id: usize,
channel_id: u32,
}

impl DynamicChannelReservation {
/// Returns the reserved dynamic channel ID.
pub fn channel_id(&self) -> u32 {
self.channel_id
}

/// Registers the channel and returns its DVC Create Request message.
///
/// # Panics
///
/// Panics if reservation finalized on a different DrdynvcServer than the one that minted it.
pub fn create<T>(self, server: &mut DrdynvcServer, channel: T) -> PduResult<SvcMessage>
where
T: DvcServerProcessor + 'static,
{
assert_eq!(
self.server_id, server.id,
"reservation finalized on a different DrdynvcServer than the one that minted it"
);
let channel_name = channel.channel_name().into();
let channel_id = self.channel_id();
server
.dynamic_channels
.insert_reserved(channel_id, channel, ChannelState::Creation);

// TODO: Align the TypeId lookup semantics of DrdynvcServer::create_channel and
// DrdynvcServer::with_dynamic_channel in a separate public-contract PR, then make
// DrdynvcServer::create_channel use the reservation path.
server
.type_id_to_channel_id
.entry(TypeId::of::<T>())
.or_insert(channel_id);
let req = DrdynvcServerPdu::Create(CreateRequestPdu::new(channel_id, channel_name));
as_svc_msg_with_flag(req)
}
}

struct DynamicChannelAllocator {
dynamic_channels: BTreeMap<u32, DynamicChannel>,
next_channel_id: u32,
Expand Down Expand Up @@ -77,13 +127,27 @@ impl DynamicChannelAllocator {
where
T: DvcServerProcessor + 'static,
{
let channel_id = self.reserve_channel();
self.insert_reserved(channel_id, processor, state);
channel_id
}

fn insert_reserved<T>(&mut self, channel_id: u32, processor: T, state: ChannelState)
where
T: DvcServerProcessor + 'static,
{
let Entry::Vacant(entry) = self.dynamic_channels.entry(channel_id) else {
unreachable!("reserved dynamic channel ID must be vacant")
};
entry.insert(DynamicChannel::new(processor, channel_id, state));
}

fn reserve_channel(&mut self) -> u32 {
let channel_id = self.next_channel_id;
self.dynamic_channels
.insert(channel_id, DynamicChannel::new(processor, channel_id, state));
self.next_channel_id = self
.next_channel_id
.checked_add(1)
.expect("dynamic channels reaches `u32::MAX`");
.expect("dynamic channels reached `u32::MAX`");
channel_id
}

Expand Down Expand Up @@ -117,10 +181,14 @@ impl DynamicChannel {
self.processor.as_any().type_id()
}
}

static NEXT_SERVER_ID: AtomicUsize = AtomicUsize::new(0);

/// DRDYNVC Static Virtual Channel (the Remote Desktop Protocol: Dynamic Virtual Channel Extension)
///
/// It adds support for dynamic virtual channels (DVC).
pub struct DrdynvcServer {
id: usize,
dynamic_channels: DynamicChannelAllocator,
type_id_to_channel_id: BTreeMap<TypeId, u32>,
}
Expand All @@ -145,6 +213,7 @@ impl DrdynvcServer {

pub fn new() -> Self {
Self {
id: NEXT_SERVER_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed),
dynamic_channels: DynamicChannelAllocator::new(),
type_id_to_channel_id: BTreeMap::new(),
}
Expand All @@ -169,7 +238,7 @@ impl DrdynvcServer {
///
/// # Panics
///
/// Panics if the number of registered dynamic channels reaches `u32::MAX`.
/// Panics if the number of registered dynamic channels reached `u32::MAX`.
#[must_use]
pub fn with_dynamic_channel<T>(mut self, channel: T) -> Self
where
Expand Down Expand Up @@ -223,17 +292,36 @@ impl DrdynvcServer {
{
let channel_name = channel.channel_name().into();

// TODO: Reuse self.reserve_channel() after the TypeId lookup contract is aligned
// in a separate PR.
let channel_id = self.dynamic_channels.insert_channel(channel, ChannelState::Creation);
let req = DrdynvcServerPdu::Create(CreateRequestPdu::new(channel_id, channel_name));
as_svc_msg_with_flag(req)
}

/// Reserves the next dynamic channel ID without creating the channel.
///
/// Reserved IDs increase monotonically and are never reused. Dropping the returned
/// reservation without creating the channel skips its ID, preventing ABA issues.
///
/// # Panics
///
/// Panics if the dynamic channel ID space is exhausted.
#[must_use]
pub fn reserve_channel(&mut self) -> DynamicChannelReservation {
let channel_id = self.dynamic_channels.reserve_channel();
DynamicChannelReservation {
channel_id,
server_id: self.id,
}
}

fn remove_by_channel_id(&mut self, id: u32) -> Option<DynamicChannel> {
self.dynamic_channels.remove(id).inspect(|dvc| {
let type_id = dvc.processor_type_id();

// Only matters for pre-registered channels
if let alloc::collections::btree_map::Entry::Occupied(entry) = self.type_id_to_channel_id.entry(type_id)
if let Entry::Occupied(entry) = self.type_id_to_channel_id.entry(type_id)
&& entry.get() == &id
{
entry.remove();
Expand Down
1 change: 1 addition & 0 deletions crates/ironrdp-rdpeusb/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ std = []
ironrdp-core = { path = "../ironrdp-core", version = "0.2", features = ["alloc"] } # public
ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9", features = ["alloc"] } # public
ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.8" } # public
ironrdp-usb = { path = "../ironrdp-usb", version = "0.1" } # public
ironrdp-str = { path = "../ironrdp-str", version = "0.1" }

[lints]
Expand Down
5 changes: 5 additions & 0 deletions crates/ironrdp-rdpeusb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
Implements [Remote Desktop Protocol: USB Devices Virtual Channel Extension][spec]
used to redirect USB devices from a terminal client to a terminal server.

The `usb` module translates protocol-independent operations and descriptor
semantics from `ironrdp-usb` into complete backend-facing RDPEUSB transfer
requests. It is sans-I/O and does not allocate request IDs or manage pending
request lifetimes.

[spec]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/a1004d0e-99e9-4968-894b-0b924ef2f125

This crate is part of the [IronRDP] project.
Expand Down
25 changes: 2 additions & 23 deletions crates/ironrdp-rdpeusb/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,36 +41,15 @@ pub trait DeviceManagerBackend: Send {
pub struct UrbdrcListener {
on_capability_exchanged: Option<OnCapabilityExchanged>,
device_man: Box<dyn DeviceManagerBackend>,
iface_man: InterfaceAlloc,
iface_man: crate::InterfaceAlloc,
}

impl UrbdrcListener {
pub fn new(callback: OnCapabilityExchanged, device_man: Box<dyn DeviceManagerBackend>) -> Self {
Self {
on_capability_exchanged: Some(callback),
device_man,
iface_man: InterfaceAlloc::new(),
}
}
}

struct InterfaceAlloc {
id: u32,
}

impl InterfaceAlloc {
#[inline]
const fn new() -> Self {
Self { id: 3 }
}

#[inline]
const fn alloc(&mut self) -> Option<InterfaceId> {
self.id += 1;
if self.id > 0x3F_FF_FF_FF {
None
} else {
Some(InterfaceId::from_raw(self.id))
iface_man: crate::InterfaceAlloc::default(),
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions crates/ironrdp-rdpeusb/src/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ pub struct DeviceText {
pub description: String,
}

/// Wrapper for the completion of RDPEUSB request.
#[derive(Debug)]
pub enum CompletionData {
IoControl(IoControlCompletionResult),
InternalIoControl(IoControlCompletionResult),
TransferIn(TransferInCompletionResult),
TransferOut(TransferOutCompletionResult),
}

/// Completion of an I/O control request.
///
/// This completes either an [`IoControlPacket`] or an [`InternalIoControlPacket`]. The request ID
Expand Down
23 changes: 23 additions & 0 deletions crates/ironrdp-rdpeusb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod client;
pub mod io;
pub mod pdu;
pub mod server;
pub mod usb;

/// Error returned when a per-device USB interface ID conflicts with an RDPEUSB default interface.
///
Expand Down Expand Up @@ -46,3 +47,25 @@ impl<T> core::fmt::Display for InvalidDeviceInterfaceId<T> {
)
}
}

pub struct InterfaceAlloc {
id: u32,
}

impl Default for InterfaceAlloc {
fn default() -> Self {
Self { id: 3 }
}
}

impl InterfaceAlloc {
#[inline]
pub const fn alloc(&mut self) -> Option<pdu::header::InterfaceId> {
self.id += 1;
if self.id > 0x3F_FF_FF_FF {
None
} else {
Some(pdu::header::InterfaceId::from_raw(self.id))
}
}
}
Loading
Loading