Skip to content

Commit 42707c5

Browse files
committed
feat(NowAgent): refactoring after review
1 parent cc434ac commit 42707c5

6 files changed

Lines changed: 66 additions & 27 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ironrdp-dvc-pipe-proxy/Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ tracing = { version = "0.1", features = ["log"] }
2525

2626

2727
[target.'cfg(windows)'.dependencies]
28-
2928
widestring = "1"
30-
smallvec = "1"
3129
windows = { version = "0.61", features = [
3230
"Win32_Foundation",
3331
"Win32_Security",
Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# IronRDP DVC pipe proxy
22

3-
Generic DVC handler which makes IronRDP connect to specific DVC channel and create a named pipe
4-
server, which will be used for proxying DVC messages to/from user-defined DVC logic
5-
implemented as named pipe clients (either in the same process or in a different process).
3+
This crate provides a Device Virtual Channel (DVC) handler for IronRDP, enabling proxying of RDP DVC
4+
traffic over a named pipe.
5+
6+
It was originally designed to simplify custom DVC integration within Devolutions Remote Desktop
7+
Manager (RDM). By implementing a thin pipe proxy for target RDP clients (such as IronRDP, FreeRDP,
8+
mstsc, etc.), the main client logic can be centralized and reused across all supported clients via a
9+
named pipe.
10+
11+
This approach allows you to implement your DVC logic in one place, making it easier to support
12+
multiple RDP clients without duplicating code.
13+
14+
Additionally, this crate can be used for other scenarios, such as testing your own custom DVC
15+
channel client, without needing to patch or rebuild IronRDP itself.

crates/ironrdp-dvc-pipe-proxy/src/platform/windows/worker.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub(crate) fn worker_thread_func(worker_ctx: WorkerCtx) -> Result<(), DvcPipePro
4545
if !connect_ctx.overlapped_connect()? {
4646
const EVENT_ID_ABORT: usize = 0;
4747
let events = [abort_event.borrow(), connect_ctx.borrow_event()];
48-
let wait_result = match wait_any_with_timeout(events, PIPE_CONNECT_TIMEOUT_SECS) {
48+
let wait_result = match wait_any_with_timeout(&events, PIPE_CONNECT_TIMEOUT_SECS) {
4949
Ok(idx) => idx,
5050
Err(WindowsError::WaitForMultipleObjectsTimeout) => {
5151
warn!(%channel_name, %pipe_name, "DVC pipe proxy connection timed out");
@@ -83,7 +83,7 @@ pub(crate) fn worker_thread_func(worker_ctx: WorkerCtx) -> Result<(), DvcPipePro
8383
read_ctx.borrow_event(),
8484
to_pipe_semaphore.borrow(),
8585
];
86-
let wait_result = wait_any(events)?;
86+
let wait_result = wait_any(&events)?;
8787

8888
if wait_result == EVENT_ID_ABORT {
8989
info!(%channel_name, %pipe_name, "DVC pipe proxy connection has been aborted");
@@ -132,7 +132,7 @@ pub(crate) fn worker_thread_func(worker_ctx: WorkerCtx) -> Result<(), DvcPipePro
132132
overlapped_write.overlapped_write()?;
133133

134134
let events = [abort_event.borrow(), overlapped_write.borrow_event()];
135-
let wait_result = wait_any_with_timeout(events, PIPE_WRITE_TIMEOUT_SECS)?;
135+
let wait_result = wait_any_with_timeout(&events, PIPE_WRITE_TIMEOUT_SECS)?;
136136

137137
if wait_result == EVENT_ID_ABORT {
138138
info!(%channel_name, %pipe_name, "DVC pipe proxy write aborted");

crates/ironrdp-dvc-pipe-proxy/src/windows/mod.rs

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ pub(crate) use event::Event;
1212
pub(crate) use pipe::MessagePipeServer;
1313
pub(crate) use semaphore::Semaphore;
1414

15-
use smallvec::SmallVec;
1615
use windows::Win32::Foundation::{
1716
ERROR_IO_PENDING, HANDLE, WAIT_ABANDONED_0, WAIT_EVENT, WAIT_FAILED, WAIT_OBJECT_0, WAIT_TIMEOUT,
1817
};
@@ -21,17 +20,19 @@ use windows::Win32::System::Threading::{WaitForMultipleObjects, INFINITE};
2120
/// Thin wrapper around borrowed `windows` crate `HANDLE` reference.
2221
/// This is used to ensure handle lifetime when passing it to FFI functions
2322
/// (see `wait_any_with_timeout` for example).
23+
#[repr(transparent)]
2424
pub(crate) struct BorrowedHandle<'a>(&'a HANDLE);
2525

2626
/// Safe wrapper around `WaitForMultipleObjects`.
27-
pub(crate) fn wait_any_with_timeout<'a, T>(handles: T, timeout: u32) -> Result<usize, WindowsError>
28-
where
29-
T: IntoIterator<Item = BorrowedHandle<'a>>,
30-
{
31-
let handles: SmallVec<[HANDLE; 8]> = handles.into_iter().map(|h| *h.0).collect();
27+
pub(crate) fn wait_any_with_timeout(handles: &[BorrowedHandle<'_>], timeout: u32) -> Result<usize, WindowsError> {
28+
let handles = cast_handles(handles);
3229

33-
// SAFETY: FFI call with no outstanding preconditions.
34-
let result = unsafe { WaitForMultipleObjects(&handles, false, timeout) };
30+
// SAFETY:
31+
// - BorrowedHandle alongside with rust type system ensures that the HANDLEs are valid for
32+
// the duration of the call.
33+
// - All handles in this module have SYNCHRONIZE access rights.
34+
// - cast_handles ensures no handle duplicates.
35+
let result = unsafe { WaitForMultipleObjects(handles, false, timeout) };
3536

3637
match result {
3738
WAIT_FAILED => Err(WindowsError::WaitForMultipleObjectsFailed(
@@ -47,17 +48,45 @@ where
4748
}
4849

4950
/// Safe `WaitForMultipleObjects` wrapper with infinite timeout.
50-
pub(crate) fn wait_any<'a, T>(handles: T) -> Result<usize, WindowsError>
51-
where
52-
T: IntoIterator<Item = BorrowedHandle<'a>>,
53-
{
51+
pub(crate) fn wait_any(handles: &[BorrowedHandle<'_>]) -> Result<usize, WindowsError> {
5452
// Standard generic syntax is used instead if `impl` because of the following lint:
5553
// > warning: lifetime parameter `'a` only used once
5654
//
5755
// Fixing this lint (use of '_ lifetime) produces compiler error.
5856
wait_any_with_timeout(handles, INFINITE)
5957
}
6058

59+
fn cast_handles<'a>(handles: &'a [BorrowedHandle<'a>]) -> &'a [HANDLE] {
60+
// Very basic sanity checks to ensure that the handles are valid
61+
// and there are no duplicates.
62+
// This is only done in debug builds to avoid performance overhead in release builds, while
63+
// still catching undefined behavior early in development.
64+
#[cfg(debug_assertions)]
65+
{
66+
// Ensure that there are no duplicate handles without hash.
67+
for (i, handle) in handles.iter().enumerate() {
68+
for other_handle in &handles[i + 1..] {
69+
if handle.0 == other_handle.0 {
70+
panic!("Duplicate handle found in wait_any_with_timeout");
71+
}
72+
}
73+
}
74+
}
75+
76+
for handle in handles {
77+
// Ensure that the handle is valid.
78+
if handle.0.is_invalid() {
79+
panic!("Invalid handle in wait_any_with_timeout");
80+
}
81+
}
82+
83+
// SAFETY:
84+
// - BorrowedHandle is #[repr(transparent)] over *const c_void, and so is HANDLE,
85+
// so the layout is the same.
86+
// - We ensure the lifetime is preserved.
87+
unsafe { core::slice::from_raw_parts(handles.as_ptr() as *const HANDLE, handles.len()) }
88+
}
89+
6190
/// Maps ERROR_IO_PENDING to Ok(()) and returns other errors as is.
6291
fn ensure_overlapped_io_result(result: windows::core::Result<()>) -> Result<windows::core::Result<()>, WindowsError> {
6392
if let Err(error) = &result {

crates/ironrdp-dvc-pipe-proxy/src/windows/pipe.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,10 @@ impl<'a> OverlappedPipeReadCtx<'a> {
186186
}
187187

188188
pub(crate) fn overlapped_read(&mut self) -> Result<(), WindowsError> {
189-
// SAFETY: hfile is a valid handle to a named pipe; lpBuffer
190-
// is a valid pointer which should be alive until the operation is completed;
189+
// SAFETY: self.pipe.raw() returns a valid handle. The read buffer pointer returned
190+
// by self.buffer.as_mut_slice() is valid and remains alive for the entire duration
191+
// of the overlapped I/O operation. The OVERLAPPED structure is pinned and not moved
192+
// in memory, ensuring its address remains stable until the operation completes.
191193
let result = unsafe {
192194
ReadFile(
193195
self.pipe.raw(),
@@ -251,8 +253,10 @@ impl<'a> OverlappedWriteCtx<'a> {
251253
}
252254

253255
pub(crate) fn overlapped_write(&mut self) -> Result<(), WindowsError> {
254-
// SAFETY: hfile is a valid handle to a named pipe; lpBuffer
255-
// is a valid pointer which should be alive until the operation is completed;
256+
// SAFETY: self.pipe.raw() returns a valid handle. The write buffer pointer (&self.data) is valid
257+
// and remains alive for the entire duration of the overlapped I/O operation. The OVERLAPPED
258+
// structure is pinned and not moved in memory, ensuring its address remains stable until the
259+
// operation completes.
256260
let result = unsafe {
257261
WriteFile(
258262
self.pipe.raw(),
@@ -271,8 +275,7 @@ impl<'a> OverlappedWriteCtx<'a> {
271275

272276
pub(crate) fn get_result(&mut self) -> Result<u32, WindowsError> {
273277
let mut bytes_written = 0u32;
274-
275-
// SAFETY: The handle is valid and we are the owner of the handle.
278+
// SAFETY: The pipe handle is valid and we are the owner of the handle.
276279
unsafe {
277280
GetOverlappedResult(
278281
self.pipe.raw(),

0 commit comments

Comments
 (0)