Compare commits

...

1288 commits

Author SHA1 Message Date
devolutionsbot
87f8d073c8
chore(release): prepare for publishing (#1020)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
2025-12-20 11:42:23 +00:00
Will Warner
bd2aed7686
feat(ironrdp-tls)!: return x509_cert::Certificate from upgrade() (#1054)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
This allows client applications to verify details of the certificate,
possibly with the user, when connecting to a server using TLS.
2025-12-18 04:14:51 +00:00
dependabot[bot]
b50b648344
build(deps): bump the patch group across 1 directory with 3 updates (#1055)
Some checks failed
CI / FFI (push) Has been cancelled
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / Success (push) Has been cancelled
2025-12-16 02:09:49 -05:00
dependabot[bot]
113284a053
build(deps): bump uuid from 1.18.1 to 1.19.0 (#1056) 2025-12-16 02:09:12 -05:00
glamberson
d587b0c4c1
fix(cliprdr): allow servers to announce clipboard ownership (#1053)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
Servers can now send Format List PDU via initiate_copy() regardless of
internal state. The existing state machine was designed for clients
where clipboard initialization must complete before announcing
ownership.

MS-RDPECLIP Section 2.2.3.1 specifies that Format List PDU is sent by
either client or server when the local clipboard is updated. Servers
should be able to announce clipboard changes immediately after channel
negotiation.

This change enables RDP servers to properly announce clipboard ownership
by bypassing the Initialization/Ready state check when R::is_server() is
true. Client behavior remains unchanged.

Co-authored-by: lamco-office <office@lamco.io>
2025-12-11 16:49:07 +00:00
dependabot[bot]
0903c9ae75
build(deps): bump the patch group across 1 directory with 4 updates (#1051)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
2025-12-09 00:18:06 -05:00
dependabot[bot]
f31af061b9
build(deps): bump rstest from 0.25.0 to 0.26.1 (#1052) 2025-12-09 00:17:45 -05:00
Benoît Cortier
7123150b63
fix(ffi): preserve full error context across FFI boundary (#1050)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
Replaced generic From implementation that used to_string() with specific
implementations for each error type to ensure source error chains are
preserved. IronRDP errors now use .report() for full context, while
standard library errors are converted to anyhow::Error for proper
alternate formatting with {:#}.
2025-12-05 15:43:28 +00:00
Richard Markiewicz
632ad86f67
chore: bump iOS nuget package to net9.0 (#1049)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Release crates (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
The `net8.0-ios` workload is out of support and won't build
out-of-the-box anymore. Bump the target framework to `net9.0-ios`.
2025-12-04 11:45:49 -05:00
Richard Markiewicz
da5db5bf11
chore(release): prepare for Devolutions.IronRdp v2025.12.4.0 (#1048)
We still don't have a means to set the nuget version directly in the
workflow. I thought of adding it, but on closer inspection we have logic
to handle package and product version differently (and the same is true
of sspi-rs etc) and probably needs a closer look. It can be troublesome
to deploy a newer nuget package that doesn't increment the assembly
versions (for example - and it shouldn't be an issue for Devolutions,
but maybe for other consumers - Windows Installers generally might not
overwrite a DLL if the version number is not newer than what is already
installed.

For now, I just bump the package version manually.
2025-12-04 15:11:34 +00:00
Richard Markiewicz
a2af587e60
fix(cliprdr): prevent window class registration error on multiple sessions (#1047)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
When starting a second clipboard session, `RegisterClassA` would fail
with `ERROR_CLASS_ALREADY_EXISTS` because window classes are global to
the process. Now checks if the class is already registered before
attempting registration, allowing multiple WinClipboard instances to
coexist.
2025-12-04 07:38:05 +00:00
Alex Yusiuk
924330159a
chore: enable large_futures clippy lint (#1046)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Check typos (push) Waiting to run
CI / Checks [macos] (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
> It checks for the size of a Future created by async fn or async {}.

The maximum byte size a `Future` can have, before it triggers the
clippy::large_futures lint is by default 16384, but we can adjust it.
2025-12-03 08:07:33 -05:00
dependabot[bot]
cf978321d3
build(deps): bump the patch group across 1 directory with 6 updates (#1044)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Release crates / Release crates (push) Waiting to run
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
2025-12-03 07:52:21 +00:00
dependabot[bot]
b303ae3a90
build(deps): bump criterion from 0.7.0 to 0.8.0 (#1045)
Bumps [criterion](https://github.com/criterion-rs/criterion.rs) from
0.7.0 to 0.8.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/criterion-rs/criterion.rs/releases">criterion's
releases</a>.</em></p>
<blockquote>
<h2>criterion-plot-v0.8.0</h2>
<p>No release notes provided.</p>
<h2>criterion-v0.8.0</h2>
<h3>BREAKING</h3>
<ul>
<li>Drop async-std support</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Bump MSRV to 1.86, stable to 1.91.1</li>
</ul>
<h3>Added</h3>
<ul>
<li>Add ability to plot throughput on summary page.</li>
<li>Add support for reporting throughput in elements and bytes -
<code>Throughput::ElementsAndBytes</code> allows the text summary to
report throughput in both units simultaneously.</li>
<li>Add alloca-based memory layout randomisation to mitigate memory
effects on measurements.</li>
<li>Add doc comment to benchmark runner in criterion_group macro
(removes linter warnings)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fix plotting NaN bug</li>
</ul>
<h3>Other</h3>
<ul>
<li>Remove Master API Docs links temporarily while we restore the docs
publishing.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/criterion-rs/criterion.rs/blob/master/CHANGELOG.md">criterion's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/criterion-rs/criterion.rs/compare/criterion-v0.7.0...criterion-v0.8.0">0.8.0</a>
- 2025-11-29</h2>
<h3>BREAKING</h3>
<ul>
<li>Drop async-std support</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Bump MSRV to 1.86, stable to 1.91.1</li>
</ul>
<h3>Added</h3>
<ul>
<li>Add ability to plot throughput on summary page.</li>
<li>Add support for reporting throughput in elements and bytes -
<code>Throughput::ElementsAndBytes</code> allows the text summary to
report throughput in both units simultaneously.</li>
<li>Add alloca-based memory layout randomisation to mitigate memory
effects on measurements.</li>
<li>Add doc comment to benchmark runner in criterion_group macro
(removes linter warnings)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fix plotting NaN bug</li>
</ul>
<h3>Other</h3>
<ul>
<li>Remove Master API Docs links temporarily while we restore the docs
publishing.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="b49ade728c"><code>b49ade7</code></a>
chore: release v0.8.0</li>
<li>See full diff in <a
href="https://github.com/criterion-rs/criterion.rs/compare/criterion-plot-v0.7.0...criterion-v0.8.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=criterion&package-manager=cargo&previous-version=0.7.0&new-version=0.8.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-03 02:36:06 -05:00
Benoît Cortier
bca6d190a8
fix(ironrdp-async)!: use static dispatch for NetworkClient trait (#1043)
- Rename `AsyncNetworkClient` to `NetworkClient`
- Replace dynamic dispatch (`Option<&mut dyn ...>`) with static dispatch
using generics (`&mut N where N: NetworkClient`)
- Reorder `connect_finalize` parameters for consistency across crates
2025-12-03 02:31:12 -05:00
Raphaël Larivière
cca323adab
ci: migrate iron crates to trusted publishing (#1042)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
CI / Web Client (push) Has been cancelled
All crates migrated:

- ironrdp
- ironrdp-acceptor
- ironrdp-ainput
- ironrdp-async
- ironrdp-blocking
- ironrdp-cliprdr
- ironrdp-cliprdr-format
- ironrdp-cliprdr-native
- ironrdp-connector
- ironrdp-core
- ironrdp-displaycontrol
- ironrdp-dvc
- ironrdp-dvc-pipe-proxy
- ironrdp-error
- ironrdp-futures
- ironrdp-graphics
- ironrdp-input
- ironrdp-pdu
- ironrdp-rdcleanpath
- ironrdp-rdpdr
- ironrdp-rdpdr-native
- ironrdp-rdpsnd
- ironrdp-rdpsnd-native
- ironrdp-server
- ironrdp-session
- ironrdp-svc
- ironrdp-tls
- ironrdp-tokio
- iron-remote-desktop
2025-11-26 15:01:04 -05:00
dependabot[bot]
742607240c
build(deps): bump clap from 4.5.52 to 4.5.53 in the patch group across 1 directory (#1039)
Some checks failed
CI / Check typos (push) Has been cancelled
CI / Check formatting (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
2025-11-25 04:13:45 -05:00
dependabot[bot]
866a6d8674
build(deps): bump bytesize from 2.2.0 to 2.3.0 (#1040) 2025-11-25 04:13:24 -05:00
Benoît Cortier
430f70b43f
ci(release): set publish = false in ironrdp-client release-plz config (#1038)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
2025-11-20 10:01:39 -05:00
Allan Zhang
5bd319126d
build(deps): bump picky and sspi (#1028)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
This fixes build issues with some dependencies.
2025-11-19 14:08:42 +00:00
Dion Gionet Mallet
bfb0cae2f8
ci(nuget): use Trusted Publishing auth (#1035)
Some checks failed
Release crates / Release crates (push) Has been cancelled
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
Issue: DEVOPS-3949
2025-11-18 01:24:12 -05:00
Yuval Marcus
a70e01d9c5
fix(server): send TLS close_notify during graceful RDP disconnect (#1032)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
Add support for sending a proper TLS close_notify message when the RDP
client initiates a graceful disconnect PDU.
2025-11-17 07:36:31 -05:00
Yuval Marcus
f2326ef046
fix(cliprdr)!: receiving a TemporaryDirectory PDU should not fail the svc (#1031)
- Fixes the Cliprdr `SvcProcessor` impl. to support handling a
`TemporaryDirectory` Clipboard PDU.
- Removes `ClipboardError::UnimplementedPdu` since it is no longer used
2025-11-17 10:13:47 +00:00
Yuval Marcus
966ba8a53e
feat(ironrdp-tokio): add MovableTokioFramed for Send+!Sync context (#1033)
The `ironrdp-tokio` crate currently provides the following two
`Framed<S>` implementations using the standard `tokio::io` traits:
- `type TokioFramed<S> = Framed<TokioStream<S>>` where `S: Send + Sync +
Unpin`
- `type LocalTokioFramed<S> = Framed<LocalTokioStream<S>>` where `S:
Unpin`

The former is meant for multi-threaded runtimes and the latter is meant
for single-threaded runtimes.

This PR adds a third `Framed<S>` implementation:

`pub type MovableTokioFramed<S> = Framed<MovableTokioStream<S>>` where
`S: Send + Unpin`

This is a valid usecase as some implementations of the `tokio::io`
traits are `Send` but `!Sync`. Without this new third type, consumers of
`Framed<S>` who have a `S: Send + !Sync` trait for their streams are
forced to downgrade to `LocalTokioFramed` and do some hacky workaround
with `tokio::task::spawn_blocking` since the defined associated futures,
`ReadFut` and `WriteAllFut`, are neither `Send` nor `Sync`.
2025-11-17 10:11:16 +00:00
dependabot[bot]
79e71c4f90
build(deps): bump windows from 0.61.3 to 0.62.1 (#1010)
Some checks failed
CI / Check typos (push) Has been cancelled
CI / Check formatting (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Success (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
Co-authored-by: Vladyslav Nikonov <mail@pacmancoder.xyz>
2025-11-10 14:12:28 -05:00
Alex Yusiuk
9622619e8c
refactor: add as_conversions clippy correctness lint (#1021)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
Co-authored-by: Benoît CORTIER <git.divisible626@passmail.com>
2025-11-05 22:23:22 +00:00
irvingouj@Devolutions
d3e0cb17e1
feat(ffi): expose RDCleanPath (#1014)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
Add RDCleanPath support for Devolutions.IronRDP .NET package
2025-10-30 16:38:41 +00:00
Raphaël Larivière
2cedc05722
ci(npm): migrate publishing to OIDC authentication (#1026)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
Issue: DEVOPS-3952
2025-10-30 08:40:10 -04:00
Alex Yusiuk
abc391c134
refactor: add partial_pub_fields clippy style and readability lint (#976)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
2025-10-23 10:58:41 -04:00
dependabot[bot]
bbdecc2aa9
build(deps): bump clap from 4.5.49 to 4.5.50 in the patch group across 1 directory (#1023)
Some checks failed
CI / Check formatting (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Success (push) Has been cancelled
2025-10-21 05:04:36 -04:00
Alex Yusiuk
e87048c19b
refactor: get rid of lazy_static (#1022)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-10-20 15:07:19 -04:00
dependabot[bot]
52225cca3e
build(deps): bump the patch group across 1 directory with 3 updates (#1017)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
2025-10-14 05:25:35 -04:00
Alex Yusiuk
6214c95c6f
test(extra): move the mod.rs to the correct tests directory (#1019) 2025-10-14 05:11:58 -04:00
rhammonds-teleport
a0a3e750c9
fix(rdpdr): fix incorrect padding when parsing NDR strings (#1015)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
When parsing Network Data Representation (NDR) messages, we're supposed
to account for padding at the end of strings to remain aligned on a
4-byte boundary. The existing code doesn't seem to cover all cases, and
the resulting misalignment causes misleading errors when processing the
rest of the message.
2025-10-09 13:25:08 -04:00
dependabot[bot]
d24dbb1e2c
build(deps): bump tokio-tungstenite from 0.27.0 to 0.28.0 (#1009)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Success (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
2025-10-08 04:19:40 -04:00
dependabot[bot]
a24a1fa9e8
build(deps): bump bytemuck from 1.23.2 to 1.24.0 (#1008)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-10-07 09:43:36 -04:00
Alex Yusiuk
cca53fd79b
chore(web): bump iron-remote-desktop to 0.10.1 (#1011)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-10-07 06:47:11 -04:00
Alex Yusiuk
da38fa20a3
chore(web): bump iron-remote-desktop-rdp to 0.6.1 (#1012) 2025-10-07 06:46:35 -04:00
Alex Yusiuk
82dbb6460f
refactor(ironrdp-pdu)!: fix as_conversions clippy lint warnings (#967)
Co-authored-by: Benoît CORTIER <git.divisible626@passmail.com>
2025-10-07 09:36:58 +00:00
Alex Yusiuk
af8ebdcfa2
refactor: enable missing_panics_doc clippy lint (#1006)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Success (push) Blocked by required conditions
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
> Checks the doc comments of publicly visible functions that may panic
and warns if there is no # Panics section.

Co-authored-by: Benoît Cortier <3809077+CBenoit@users.noreply.github.com>
2025-10-06 09:19:22 +00:00
Alex Yusiuk
ce298d1c19
refactor: enable self_named_module_files clippy lint (#1007)
Some checks failed
Coverage / Coverage Report (push) Has been cancelled
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
> Checks that module layout uses only mod.rs files.
2025-10-03 05:37:14 -04:00
Alex Yusiuk
a8289bf63f
refactor: enable unnecessary_self_imports clippy lint (#1005)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-10-02 05:10:55 -04:00
Alex Yusiuk
bbc38db750
chore: enable try_err clippy lint (#1004) 2025-10-02 08:34:02 +00:00
Alex Yusiuk
49a0a9e6d2
chore: enable rest_pat_in_fully_bound_structs clippy lint (#1003) 2025-10-02 04:05:34 -04:00
devolutionsbot
c6b5487559
chore(release): prepare for publishing (#1002) 2025-10-02 03:34:02 +00:00
Gabriel Bauman
18c81ed5d8
feat(web): human-readable descriptions for RDCleanPath errors (#999)
More munging to give human-readable webclient-side errors for
RDCleanPath general/negotiation errors, including strings for WSA and
TLS and HTTP error conditions.
2025-10-01 22:54:37 -04:00
Alex Yusiuk
b91a4eeb01
refactor: enable redundant_type_annotations clippy lint (#1001)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
2025-09-30 09:45:22 -04:00
devolutionsbot
209108dc2c
chore(release): prepare for publishing (#997)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-09-29 11:07:48 +00:00
Vladyslav Nikonov
a660d7f960
chore(release): remove leading zero from nuget version (#1000)
leading zero in Nuget version caused CI to fail on ffi crate build:

```
 error: invalid leading zero in minor version number
 --> ffi/Cargo.toml:3:11
  |
3 | version = "2025.09.24"
  |           ^^^^^^^^^^^^
  |
error: failed to load manifest for workspace member `/Users/runner/work/IronRDP/IronRDP/ffi`
referenced by workspace at `/Users/runner/work/IronRDP/IronRDP/Cargo.toml`
```
2025-09-29 06:39:28 -04:00
Vladyslav Nikonov
3198abae2f
chore(release): prepare for Devolutions.IronRdp v2025.09.24.0 (#998)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
2025-09-25 08:52:17 -04:00
Alex Yusiuk
6127e13c83
fix(web): fix this.lastSentClipboardData being nulled (#992)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Release crates (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
```js
await this.remoteDesktopService.onClipboardChanged(...)
```
Consumes the clipboard data we pass, so we need to clone the data to
prevent `this.lastSentClipboardData` from being null.
2025-09-24 08:49:32 +00:00
devolutionsbot
8dc41e2feb
chore(release): prepare for publishing (#989) 2025-09-24 08:01:35 +00:00
dependabot[bot]
2994edf320
build(deps): bump bytesize from 2.0.1 to 2.1.0 (#995)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-09-24 02:03:05 -04:00
Alex Yusiuk
ac2964d406
fix(web): send first clipboard only after the connection is established (#996)
Fix a bug where, when the clipboard is in automatic mode,
`iron-remote-desktop` could send the first clipboard PDU to
the server before the connection is established.
This packet was ignored, and the clipboard on the server
not synced with the current host's clipboard.
2025-09-24 02:02:21 -04:00
dependabot[bot]
0b39078c26
build(deps): bump inquire from 0.7.5 to 0.8.0 (#984)
Some checks failed
CI / Success (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
2025-09-19 12:31:19 +00:00
Alex Yusiuk
4f518f0a7b
chore: add infinite_loop clippy style and readability lint (#981)
> Checks for infinite loops in a function where the return type is not !
and lint accordingly.
> Making the return type ! serves as documentation that the function
does not return. If the function is not intended to loop infinitely,
then this lint may detect a bug.
2025-09-19 12:14:08 +00:00
Alex Yusiuk
0141178ae2
chore: add empty_enum_variants_with_brackets clippy style and readability lint (#980)
> Finds enum variants without fields that are declared with empty
brackets.
> Empty brackets after a enum variant declaration are redundant and can
be omitted, and it may be desirable to do so consistently for style.
2025-09-19 11:58:28 +00:00
Alex Yusiuk
c1fcf7a0c3
chore(release): prepare for iron-remote-desktop v0.10.0 (#991) 2025-09-19 11:53:55 +00:00
Alex Yusiuk
c417012b38
refactor: add deref_by_slicing clippy style and readability lint (#979)
> Checks for slicing expressions which are equivalent to dereferencing
the value.
> Some people may prefer to dereference rather than slice.
2025-09-19 11:43:30 +00:00
Alex Yusiuk
42fbd5f378
refactor: add multiple_inherent_impl clippy style and readability lint (#978)
From the lint
[documentation](https://rust-lang.github.io/rust-clippy/master/index.html#/multiple_inherent_impl):
> Checks for multiple inherent implementations of a struct
> Splitting the implementation of a type makes the code harder to
navigate.
2025-09-19 11:27:49 +00:00
Alex Yusiuk
4cf86ffdba
refactor: add map_with_unused_argument_over_ranges clippy style and readability lint (#982)
> Checks for Iterator::map over ranges without using the parameter which
could be more clearly expressed using std::iter::repeat(...).take(...)
or std::iter::repeat_n.
2025-09-19 11:05:53 +00:00
Alex Yusiuk
6c0014d5b3
fix(web)!: rework error handling (#975)
Improves the error handling in _iron-remote-desktop_ by
replacing the session events with throwing errors for terminated and
error events and callbacks for warnings and the clipboard remote update
event.
2025-09-19 11:05:20 +00:00
Vladyslav Nikonov
3182a018e2 fix(dvc-pipe-proxy): add blocking logic for sending dvc pipe messages 2025-09-19 07:52:43 +00:00
Vladyslav Nikonov
5f52a44b84
fix(dvc-pipe-proxy): change dvc proxy pipe mode from Message to Byte on Windows (#986)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-09-18 19:40:23 +00:00
dependabot[bot]
e6421b509c
build(deps): bump the patch group across 1 directory with 5 updates (#990) 2025-09-18 13:36:15 -04:00
Benoît Cortier
e5042a7d81
build(deps): replace opus by opus2 (#985)
opus is unmaintained and ponits to a 4-year old commit of the opus C
library. This does not compile anymore on our CI, because their
CMakeList.txt requires an older version of cmake that is not available
in the runners we use. opus2 is a fork that points to a more recent
version of it.
2025-09-18 15:20:15 +00:00
Alex Yusiuk
fe47ced857
chore: add pub_without_shorthand clippy style and readability lint (#977)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
[pub_without_shorthand](https://rust-lang.github.io/rust-clippy/master/index.html#/pub_without_shorthand):

> Checks for usage of pub(<loc>) without in.
> Note: As you cannot write a module’s path in pub(<loc>), this will
only trigger on pub(super) and the like.
2025-09-11 11:53:10 +00:00
Alex Yusiuk
630525deae
refactor!: enable unwrap_used clippy correctness lint (#965)
Co-authored-by: Benoît CORTIER <git.divisible626@passmail.com>
2025-09-11 11:41:02 +00:00
Alex Yusiuk
a8b6fb1d74
chore: add suspicious_xor_user_as_pow clippy correctness lint (#973)
Some checks are pending
CI / Checks [macos] (push) Blocked by required conditions
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
From the lint
[docs](https://rust-lang.github.io/rust-clippy/stable/index.html#suspicious_xor_used_as_pow):
> Warns for a Bitwise XOR (^) operator being probably confused as a
powering. It will not trigger if any of the numbers are not in decimal.
It’s most probably a typo and may lead to unexpected behaviours.
2025-09-10 13:04:12 +00:00
Alex Yusiuk
898df8c0fa
chore: add unused_result_ok clippy correctness lint (#974)
This lint checks for calls to `Result::ok()` without using the returned
`Option`.
2025-09-10 08:47:12 -04:00
Benoît CORTIER
bd8f2743d6 chore(release): prepare for Devolutions.IronRdp v2025.10.9.0
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-09-09 13:24:10 -04:00
Benoît CORTIER
8a8027481f build: optimize binary size for FFI builds
Still optimizing for performance, but enabled the following options:

> strip = "symbols"
> codegen-units = 1
> lto = true

- Baseline: 8.9M
- New: 5.6M
2025-09-09 13:24:10 -04:00
Vladyslav Nikonov
5ddf68de79
fix(cliprdr): add missing std feature to ironrdp-code dependency (#971)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-09-09 09:07:35 -04:00
Alex Yusiuk
2259bd7706
refactor: add string_slice clippy correctness lint (#970)
This lint checks for slice operations on strings.

From `string_slice`
[docs](https://rust-lang.github.io/rust-clippy/master/index.html#/string_slice):

> UTF-8 characters span multiple bytes, and it is easy to inadvertently
confuse character counts and string indices. This may lead to panics,
and should warrant some test cases containing wide UTF-8 characters.
This lint is most useful in code that should avoid panics at all costs.
2025-09-09 04:22:29 -04:00
dependabot[bot]
fd43129b13
build(deps): bump the patch group across 2 directories with 4 updates (#968)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-09-08 23:46:33 +00:00
Alex Yusiuk
e8d7570cd1
refactor(pdu)!: fix unwrap_used clippy lint warnings (#964)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
Co-authored-by: Benoît CORTIER <git.divisible626@passmail.com>
2025-09-04 13:06:48 -04:00
devolutionsbot
4beab02353
chore(release): prepare for publishing (#960) 2025-09-04 13:06:32 -04:00
Vladyslav Nikonov
17833fe009
feat: add support for DVC pipe proxy in FFI (#938) 2025-09-04 12:35:34 -04:00
Benoît Cortier
8cf9f3dda4
ci(npm-publish): automatically push tags (#966) 2025-09-04 16:06:19 +00:00
dependabot[bot]
21fa028dff
build(deps): bump png from 0.17.16 to 0.18.0 (#961)
Some checks failed
CI / Check typos (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / Success (push) Has been cancelled
CI / Check formatting (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / FFI (push) Has been cancelled
2025-09-03 06:58:50 +00:00
dependabot[bot]
598cd3f76e
build(deps): bump the patch group across 1 directory with 2 updates (#962)
Some checks are pending
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-09-02 20:03:18 -04:00
rhammonds-teleport
50574c570f
feat(rdpdr): support device removal (#947)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-09-02 14:41:41 +00:00
Alex Yusiuk
729ecf965e
feat(web): add warning for clipboard unavailability in non-secure context (#959)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-09-02 07:15:43 -04:00
Benoît Cortier
94ca3e25a8
refactor: add a FIXME related to FastPathUpdate handling (#958)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-09-01 20:49:09 +03:00
Alex Yusiuk
cd2f25f97a
chore(release): release iron-remote-desktop 0.9.0 (#957)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-09-01 06:55:27 -04:00
Alex Yusiuk
6b626d4fca
fix(web): implement text-only clipboard for outdated versions of Firefox browser (#951)
Firefox versions before v127 do not support the write() method - only
writeText(). Therefore, the Extended Clipboard checkbox must be
automatically disabled.
2025-09-01 09:46:24 +00:00
Alex Yusiuk
d31291362c
style(web): follow-up to PR #935 (#955)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
2025-08-29 14:46:10 +00:00
devolutionsbot
7f57e12fab
chore(release): prepare for publishing (#953) 2025-08-29 14:40:26 +00:00
Benoît Cortier
6de7f4bf60
fix(dvc-pipe-proxy): enable missing "fs" feature for tokio (#954)
Changelog: ignore
2025-08-29 14:38:50 +00:00
Alex Yusiuk
23c0cc2c36
feat(web)!: extend DeviceEvent.wheelRotations event to support passing rotation units other than pixels (#952) 2025-08-29 14:10:22 +00:00
devolutionsbot
a3b2017e5f
chore(release): prepare for publishing (#885) 2025-08-29 13:59:13 +00:00
Benoît Cortier
5d0c74df91
chore(release): prepare web packages for publishing (#950)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
* iron-remote-desktop v0.8.0
* iron-remote-desktop-rdp v0.5.0
2025-08-29 07:30:03 +03:00
Alex Yusiuk
5b948e2161
fix(web)!: replace current clipboard logic with auto and manual clipboard modes (#935)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
Adds:
- auto clipboard mode. 
  When it's enabled, the clipboard will be automatically monitored for
  changes, and updates from the server will be automatically saved to the
  local clipboard (this is the old logic; nothing has changed).
- manual clipboard mode. 
  One calls dedicated functions to interact with the clipboard.
2025-08-28 22:07:58 -04:00
Benoît Cortier
303bee0456
refactor: follow up to PR 930 (#949)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-08-28 11:24:08 +00:00
Gabriel Bauman
ca11e338d7
feat: preserve RDP negotiation failure details in RDCleanPath error responses (#930)
* Both web and desktop clients check for X.224 negotiation failure data
in RDCleanPath error responses before falling back to generic errors
* When X.224 Connection Confirm failure is found, convert to specific
NegotiationFailure error type instead of generic RDCleanPath error
* Enable clients to show meaningful error messages like "CredSSP
authentication required" instead of generic connection failures
* Maintain backward compatibility - existing proxies sending empty
x224_connection_pdu continue working as before
* Helper for proxies creating an RDCleanPath error with server response
2025-08-28 07:06:21 -04:00
Alex Yusiuk
ae99d14a69
refactor: add same_name_method clippy correctness lint (#948) 2025-08-28 11:05:42 +00:00
Alex Yusiuk
cec3fa70fc
refactor(web): raise TERMINATED event on connection error (#944)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
This PR changes the session to not return an error on the connection
stage. The `iron-remote-desktop` raises `TERMINATED` instead of `ERROR`,
because `ERROR` is now handled as non-critical and does not close the
session.
2025-08-28 06:33:31 -04:00
Alex Yusiuk
2c7f976ecf
chore: add rc_mutex clippy correctness lint (#945)
Some checks are pending
CI / Checks [macos] (push) Blocked by required conditions
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-08-27 12:13:00 -04:00
Alex Yusiuk
998ef87f96
chore: add float_cmp_const clippy correctness lint (#943)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-08-27 05:03:32 -04:00
Alex Yusiuk
76e4ac230a
chore: add precedence_bits clippy correctness lint (#942) 2025-08-27 05:03:01 -04:00
Alex Yusiuk
a996d02a66
fix(web): remove contenteditable=true from top level element (#922)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
Setting `contenteditable=true` causes spurious edits of that element
(when pressing dead keys using the BEPO or AZERTY keyboard).
2025-08-26 18:19:20 -04:00
dependabot[bot]
9e23597c50
build(deps): bump hyper from 1.6.0 to 1.7.0 (#940) 2025-08-26 14:25:11 -04:00
dependabot[bot]
fe31cf2c57
build(deps): bump picky from 7.0.0-rc.16 to 7.0.0-rc.17 (#941) 2025-08-26 14:24:34 -04:00
dependabot[bot]
c8c70975dd
build(deps): bump the patch group across 2 directories with 10 updates (#939)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-08-26 12:45:49 -04:00
Alex Yusiuk
f34a9f2500
refactor: add panic clippy correctness lint (#934) 2025-08-26 10:52:01 -04:00
Alex Yusiuk
b1f6004ab1
refactor: add non_ascii_literal clippy correctness lint (#932) 2025-08-26 08:18:22 -04:00
Alex Yusiuk
8fe288400d
chore: add filetype_is_file clippy correctness lint (#933) 2025-08-26 07:57:08 -04:00
Alex Yusiuk
b0c145d0d9
fix(clipbrdr-native): map E_ACCESSDENIED WinAPI error code to ClipboarAccessDenied error (#936)
When the system clipboard updates, we receive an `Updated` event. Then
we try to open it, but we can get `AccessDenied` error because the
clipboard may still be locked for another window (like _Notepad_). To
handle this, we have special logic that attempts to open the clipboard
in the event of such errors.
The problem is that nothing in the code actually sets the
`ClipboardAccessDenied` error to be able to run the retrieval logic.
This PR fixes it.
2025-08-26 07:56:28 -04:00
Alex Yusiuk
6fddcaae38
refactor: add needless_raw_strings clippy correctness lint (#931)
Some checks are pending
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Check formatting (push) Waiting to run
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Release crates / Release crates (push) Waiting to run
CI / Web Client (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
2025-08-25 07:17:25 -04:00
Alex Yusiuk
ff2c968052
chore: add mixed_read_write_in_expression clippy correctness lint (#929)
Some checks failed
CI / Check formatting (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
`mixed_read_write_in_expression` checks for a read and a write to the
same variable, where whether the read occurs before or after the write
depends on the evaluation order of sub-expressions.
2025-08-22 06:28:26 -04:00
Alex Yusiuk
5fe6043208
chore: add mem_forget clippy correctness lint (#928)
This lint warns on usage of `std::mem::forget`. If it's needed, we
should silence the lint and justify that it's used mindfully.
2025-08-22 05:42:06 -04:00
Alex Yusiuk
dc6fd0433d
fix(web): make check for isUnicodeCharacter in sendKeyboard more strict (#927)
This resolves an issue with Unicode input that happens under Chrome.
where entering `Alt` codes results in broken modifier state and broken
input in general.

This happens because of how _Chrome_ handles `KeyboardEvent` key and
code in this particular case. For example, holding `Alt`, pressing 1, 2,
3 on a numpad, then releasing `Alt` will result in events with the
following key/code values being passed to `sendKeyboard`:
`{ "key": "Alt", "code": "AltLeft" ,"type": "keydown" }`
`{ "key": "1", "code": "Numpad1" ,"type": "keydown" }`
`{ "key": "1", "code": "Numpad1" ,"type": "keyup" }`
`{ "key": "2", "code": "Numpad2" ,"type": "keydown" }`
`{ "key": "2", "code": "Numpad2" ,"type": "keyup" }`
`{ "key": "3", "code": "Numpad3" ,"type": "keydown" }`
`{ "key": "3", "code": "Numpad3" ,"type": "keyup" }`
`{ "key": "{", "code": "AltLeft" ,"type": "keyup" }`

Without this fix, this will send Unicode `{` instead of plain `Alt` to
the RDP server, messing up the `Alt` code sequence and leaving `Al` in
pressed state.

For comparison, in _Firefox_ last event looks like this: `{ "key":
"Alt", "code": "AltLeft","type": "keyup" }`
2025-08-22 03:39:55 -04:00
Alex Yusiuk
514400f6fc
chore: add lossy_float_literal clippy correctness lint (#926)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
This lint checks the float literals that cannot be represented as the
underlying type without loss.
2025-08-21 04:18:41 -04:00
Dan Share
6fab9f8228
feat: add an option to specify a timezone (#917)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
Allows to pass a timezone to the remote desktop.
2025-08-20 15:40:32 +00:00
Alex Yusiuk
e01f38f3a8
chore: add trivial_numeric_casts rustc style/readability lint (#925) 2025-08-20 11:27:38 -04:00
Dan Share
119c7077c9
fix!: update timezone info to use i32 bias (#921)
Some checks are pending
CI / Checks [linux] (push) Blocked by required conditions
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
Switches `bias` from an unsigned to a signed integer.
This matches the updated specification from Microsoft.
2025-08-20 11:03:22 +00:00
Alex Yusiuk
a6e0364be7
chore: add redundant_lifetimes rustc style/readability lint (#924) 2025-08-20 06:28:55 -04:00
Alex Yusiuk
1fdbfd29ed
refactor: add redundant_imports rustc style/readability lint (#923) 2025-08-20 03:12:31 -04:00
Benoît Cortier
27f5504508
refactor(mstsgu): follow up to PR 913 (#920)
Some checks are pending
CI / Fuzzing (push) Blocked by required conditions
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
- Update tokio-tungstenite to latest
- Fix the dependencies
- Move the top-level documentation into a README.md that we re-include
in the source code
- Re-order the imports using the nightly formater
- Audit the unwraps and remove them
- Fix the UTF-16 string length computation
2025-08-19 10:01:49 -04:00
Alex Yusiuk
c84b46be91
refactor: add macro_use_extern_crate rustc style/readability lint (#919)
This lint warns on each `macro_use` attribute on an extern crate. Such a
way of importing macros from a crate is being phased out. Instead of it,
we should directly import the needed macro using `use` import.
2025-08-19 09:45:34 -04:00
Alex Yusiuk
35c19ce444
build: fix failing CI on master (#918) 2025-08-19 02:52:42 -04:00
Alex Yusiuk
64535c5559
refactor: add wildcard_imports clippy extra-pedantic lint (#902)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-08-18 13:49:02 -04:00
Steffen Butzer
7d28ef83a6
feat: add MS-TSGU (Microsoft RD Gateway) support (#913)
Some checks are pending
CI / Fuzzing (push) Blocked by required conditions
CI / Check typos (push) Waiting to run
CI / Check formatting (push) Waiting to run
CI / Success (push) Blocked by required conditions
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
This adds a working state to connect with the ironrdp-client CLI against
a server behind a microsoft remote desktop gateway.
During my testing this was robust enough to work with sessions for more
than 30 minutes.

CLI Flags and prompts are implemented and can be mixed, so the following
would prompt only for 2 passwords:
> ironrdp-client --gw-user username@domain --gw-endpoint rdp.gw.host:443
-u username@domain rdp.internal.host

[MS-TSGU]
0007d661-a86d-4e8f-89f7-7f77f8824188
* This implements a MVP (in terms of recentness) state needed to connect
through microsoft rdp gateway.
* This only supports the HTTPS protocol with Websocket (and not the
legacy HTTP, HTTP-RPC or UDP protocols).
* This does not implement reconnection/reauthentication.
* This only supports basic auth.

Mostly looking for rough initial feedback (e.g. in terms of if there are
parts that dont align with projects architecture or other areas needing
major rework) as well as if the implemented scope would be considered
complete enough to land this in the first place.
2025-08-18 06:11:24 -04:00
Benoît Cortier
cd184d30bd
build: fix xtask web build task (#912)
Some checks failed
CI / Check typos (push) Has been cancelled
CI / Check formatting (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Success (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
2025-08-13 07:56:52 -04:00
dependabot[bot]
9ff3cffb59
build(deps): bump the patch group across 2 directories with 6 updates (#914)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Success (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
2025-08-12 10:27:56 +00:00
dependabot[bot]
a682d9cc48
build(deps): bump uuid from 1.17.0 to 1.18.0 (#915) 2025-08-12 06:19:55 -04:00
Gabriel Bauman
ac291423de
refactor(web): add NegotiationFailure to IronErrorKind (#905)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
Adds a general NegotiationFailure to IronErrorKind so that web embedders
can handle failures that occur during protocol negotiation, before
authentication.

Changes:
- RDP errors during the negotiation phase become
IronErrorKind.NegotiationError
- User-friendly RDP negotiation error messages to ironrdp-connector
- Update TypeScript definitions
2025-08-08 04:52:28 -04:00
Benoît Cortier
5fc9fefa02
refactor(server): remove needless sync primitives (#911)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
Instead use a `Option` as a slot from which the value can be moved out
temporarily when necessary.
2025-08-05 11:15:55 +00:00
dependabot[bot]
85fc1c7bb3
build(deps): bump tokio from 1.47.0 to 1.47.1 in the patch group across 1 directory (#909) 2025-08-05 06:35:05 -04:00
Benoît Cortier
867ed221da
docs: clarify STYLE.md (#910)
For error messages: use proper abbreviation casing, e.g., IPv4 and IPv6
(not ipv4/ipv6).

For inline comments, no period for brief comments
(e.g., `// VER`, `// RSV`, `// ATYP`)
2025-08-05 09:12:59 +00:00
Vladyslav Nikonov
166b76010c
feat(dvc): make dvc named pipe proxy cross-platform (#896)
Some checks are pending
CI / Check typos (push) Waiting to run
CI / Success (push) Blocked by required conditions
CI / Check formatting (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
### Changes
- Make dvc named pipe proxy cross-platform (Unix implementation via
`tokio::net::unix::UnixStream`)
- Removed unsafe code for Windows implementation, switched to
`tokio::net::windows::named_pipe`

### Testing
This feature can be used in the [same
way](https://github.com/Devolutions/IronRDP/pull/791) as on Windows,
however instead of GUI test app there is new basic
[CLI](https://github.com/Devolutions/now-proto/pull/31) app
2025-08-04 17:56:02 +03:00
Benoît Cortier
4df5dd8762
refactor: follow up to PR #906 (#908) 2025-08-04 11:05:38 +00:00
Benoît CORTIER
5d8a487001 fix: rename option no_audio_playback into enable_audio_playback 2025-08-04 06:46:10 -04:00
Benoît CORTIER
218fed03c7 fix: rename option no_server_pointer into enable_server_pointer 2025-08-04 06:46:10 -04:00
Steffen Butzer
0f9e8b1017
fix(client): fix special key modifiers on linux (#906)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
This makes CTRL+C, CTRL+V, etc. work.
2025-08-04 04:11:21 +00:00
Alex Yusiuk
39566bff58
chore: add clippy cfg_not_test extra-pedantic lint (#903)
Some checks failed
CI / Check formatting (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
2025-07-31 13:10:19 -04:00
Benoît Cortier
424e46d225
chore(release): prepare for iron-remote-desktop-rdp v0.5.2 (#901)
Some checks are pending
CI / Success (push) Blocked by required conditions
CI / Check typos (push) Waiting to run
CI / Check formatting (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-07-31 08:23:29 -04:00
Gabriel Bauman
100765f98f
feat(web): add outbound WebSocket message size limit extension (#889)
Add support for chunking outbound WebSocket messages when they exceed a
configurable size limit. This helps avoid browser- or proxy-specific
WebSocket message size restrictions while maintaining wire
compatibility.

Changes:
- Add outbound_message_size_limit field to SessionBuilderInner
- Implement extension handler with safe f64->u32 casting and validation
- Update writer_task to chunk large messages when limit is set
- Add outboundMessageSizeLimit() helper function to JavaScript API

---------

Co-authored-by: Benoît Cortier <3809077+CBenoit@users.noreply.github.com>
2025-07-31 11:18:36 +00:00
Alex Yusiuk
32b0e40eca
refactor: add unused_trait_names clippy extra-pedantic lint (#900) 2025-07-31 03:57:09 -04:00
dependabot[bot]
5d513dcf09
build(deps): bump tokio from 1.46.1 to 1.47.0 (#893)
Some checks are pending
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Check formatting (push) Waiting to run
CI / Success (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Checks [linux] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
2025-07-30 10:08:47 +00:00
dependabot[bot]
07b1e13d69
build(deps): bump criterion from 0.6.0 to 0.7.0 (#892) 2025-07-30 10:08:32 +00:00
dependabot[bot]
3ef5cf0c20
build(deps): bump the patch group across 1 directory with 2 updates (#899) 2025-07-30 05:51:31 -04:00
Alex Yusiuk
9a9ab63e1c
refactor: add non_zero_suggestions clippy extra-pedantic lint (#895) 2025-07-30 09:11:53 +00:00
Alex Yusiuk
00f2f1b067
refactor: add renamed_function_params clippy extra-pedantic lint (#898) 2025-07-30 08:56:01 +00:00
Alex Yusiuk
8634ab05bc
ci(xtask): pin nightly version (#897) 2025-07-30 04:40:38 -04:00
Alex Yusiuk
ae052ed835
feat(pdu): improve ExtendedClientOptionalInfoBuilder API (#891)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Success (push) Has been cancelled
2025-07-27 22:51:25 -04:00
Alex Yusiuk
f30bb99746
refactor: remove unneeded pub(crate)s (#890) 2025-07-27 22:49:26 -04:00
Marc-Andre Lureau
aafd45229b
fix(server): keep looping on compress_stream2() > 0 (#888)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
Fixes: 87df67fdc7 ("feat: add QOIZ image codec")

---------

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Co-authored-by: Benoît Cortier <3809077+CBenoit@users.noreply.github.com>
Changelog: ignore
2025-07-25 17:11:17 +00:00
Alex Yusiuk
721c680f9b
chore: add rustc "Correctness" lints (#886) 2025-07-25 12:51:30 -04:00
Benoît Cortier
8c574f254e
style(web): extern crate getrandom2 as _ (#884)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
Just for consistency.
2025-07-24 10:54:37 +00:00
Marc-André Lureau
87df67fdc7 feat: add QOIZ image codec
Add a new QOIZ codec (UUID 229cc6dc-a860-4b52-b4d8-053a22b3892b) for
SetSurface command. The PDU data contains the same data as the QOI
codec, with zstd compression.

Some benchmarks showing interesting results (using ironrdp/perfenc)

QOI: 10s user CPU, 96.20% compression
QOIZ: 11s user CPU, 99.76% compression

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-07-24 06:53:10 -04:00
Marc-André Lureau
613fd51f26 feat: add QOI image codec
The Quite OK Image format ([1]) losslessly compresses images to a
similar size of PNG, while offering 20x-50x faster encoding and 3x-4x
faster decoding.

Add a new QOI codec (UUID 4dae9af8-b399-4df6-b43a-662fd9c0f5d6) for
SetSurface command. The PDU data contains the QOI header (14 bytes) +
data "chunks" and the end marker (8 bytes).

Some benchmarks showing interesting results (using ironrdp/perfenc)

Bitmap: 74s user CPU, 92.5% compression
RemoteFx (lossy): 201s user CPU, 96.72% compression
QOI: 10s user CPU, 96.20% compression

Note: the "qoicoubeh" crate is my own fork of "qoi-rust" project. The
plan is to switch back to it as soon as the maintainer resume its
activites (https://github.com/aldanor/qoi-rust/issues/14).

[1]: https://qoiformat.org/

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-07-24 06:53:10 -04:00
Marc-André Lureau
d3aaa43c23 feat(server)!: add server_codecs_capabilities()
Teach the server to support customizable codecs set. Use the same
logic/parsing as the client codecs configuration.

Replace "with_remote_fx" with "codecs".

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-07-24 06:53:10 -04:00
Marc-André Lureau
727c9b7710 refactor(session): generalize apply_rgb24()
Add an optional "flip" argument for inverting bitmaps.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-07-24 06:53:10 -04:00
Marc-André Lureau
fc604a567b chore(bench): fix could not find time in tokio
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-07-24 06:53:10 -04:00
Marc-André Lureau
03cac54ada build(deps): bump der-parser to 10.0
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-07-24 06:34:50 -04:00
Marc-André Lureau
de0877188c build(deps): bump rand to 0.9
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-07-24 06:34:50 -04:00
Marc-André Lureau
8d09e327a1 build(deps): bump getrandom to 0.3
We still need to enable "js" feature for transitive dependencies.
Also, we need to explicitely enable the wasm backend via rustflags.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-07-24 06:34:50 -04:00
Marc-André Lureau
971ad922a5 build(deps): bump nix to 0.30
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-07-24 06:34:50 -04:00
Marc-André Lureau
12edc04c0c build(deps): bump criterion to 0.6
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-07-24 06:34:50 -04:00
Marc-André Lureau
b4fb0aa0c7 build(deps): bump thiserror to 2.0
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-07-24 06:34:50 -04:00
Marc-André Lureau
cb99c82c7d refactor(graphics): hand-implement Error trait
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-07-24 06:34:50 -04:00
Marc-André Lureau
a47a12ce94 refactor(cliprdr): hand-implement Error trait
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-07-24 06:34:50 -04:00
Marc-André Lureau
4a40883f2b refactor(cliprdr-native): hand-implement Error trait
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-07-24 06:34:50 -04:00
Marc-André Lureau
83ad04dd56 refactor(cliprdr-format): hand-implement Error trait
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-07-24 06:34:50 -04:00
Marc-André Lureau
fc8f8ee279 build(deps): bump rstest to 0.25
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-07-24 06:34:50 -04:00
Marc-André Lureau
eeac1fee1f build(deps): bump cpal to 0.16
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-07-24 06:34:50 -04:00
Benoît Cortier
555894ecc8
chore(release): prepare iron-remote-desktop-rdp v0.5.1 (#883)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-07-24 04:23:05 +00:00
Gabriel Bauman
7a0fcce203
feat(web): add enableCredssp extension (#878)
Allows users to disable CredSSP/NLA, which is otherwise enabled by default.
This is necessary to connect to target hosts bound to AzureAD/Entra domains.
In this case, user authentication is handled in the interactive session.
2025-07-23 23:31:11 -04:00
Alex Yusiuk
e7393f50d0
chore: add disallowed_script_idents clippy extra-pedantic lint (#881)
Some checks are pending
CI / Checks [linux] (push) Blocked by required conditions
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-07-23 11:29:27 -04:00
Alex Yusiuk
f96b1b2ce8
refactor: add allow_attributes clippy to "Extra-pedantic clippy" section (#880)
Some checks are pending
CI / Checks [windows] (push) Blocked by required conditions
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
2025-07-23 08:55:40 +00:00
Benoît Cortier
4fc295db4c
chore(release): prepare iron-remote-desktop-rdp v0.5.0 (#879) 2025-07-23 08:54:46 +03:00
Alex Yusiuk
f7cbb08e2e
feat(web): add RdpFile helper to parse RDP configs (#877)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-07-22 20:33:37 -04:00
Alex Yusiuk
061ccf2f0a
chore: add todo clippy lint to "Let’s not merge unintended" section (#876)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-07-22 08:31:06 -04:00
Alex Yusiuk
aa82dfb1fd
refactor: add more clippy lints to "Compile-time / optimization" section (#875)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-07-21 12:28:33 -04:00
Alexandr Yusiuk
5484b11fbd refactor: add missing_safety_doc clippy safety lint
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
2025-07-18 16:39:23 -04:00
Alexandr Yusiuk
dc5da26fb9 refactor: add unnecessary_safety_comment clippy safety lint 2025-07-18 16:39:23 -04:00
Alexandr Yusiuk
c6d606e670 refactor: add as_pointer_underscore clippy safety lint 2025-07-18 16:39:23 -04:00
Alex Yusiuk
2307983178
feat: add rustc unit_bindings extra-pedantic lint (#873)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-07-18 04:37:06 -04:00
Alex Yusiuk
cbe905ba07
chore: add more rustc lints to "Safer unsafe" section (#874) 2025-07-18 04:35:03 -04:00
Danny Bédard
0732e87517
[DEVOPS-3822] fix: add iron-remote-desktop-rdp to package cache update (#871)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-07-17 13:52:37 -04:00
Danny Bédard
67e0c3c401
ci: add update artifactory cache step in npmjs publish (#870)
Issue: DEVOPS-3822
2025-07-17 12:16:56 -04:00
Alex Yusiuk
8287cf0681
chore: run cargo +nightly fmt --all (#869)
Some checks are pending
Coverage / Coverage Report (push) Waiting to run
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
Co-authored-by: Benoît Cortier <3809077+CBenoit@users.noreply.github.com>
2025-07-17 11:05:50 +00:00
dependabot[bot]
2b3f558c0a
build(deps): bump the patch group across 1 directory with 3 updates (#867)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
2025-07-15 02:59:13 -04:00
Alex Yusiuk
f6fb3a41b3
fix(web): apply correct key codes to scan codes mapping (#866)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
The root cause of the bug is the incorrect keycodes-to-scancodes
mappings. These mappings translated the browser's key codes (like
"KeyA") to OS-specific scancode (OS where the browser runs). However,
that's not exactly what we need to do. We need to map the browser's key
codes to _Windows_ scancodes - the only format that is accepted by _VNC_
and _RDP_) modules.
Let's look at an example for a better understanding. _Safari_ browser
used _Linux_'s _Gecko) mappings as a fallback (because there were no
MacOS-specific mappings). For a given key, _iron-remote-desktop_ was
providing a scancode that did not correspond to the _Windows_ scancode
for that same key. As a result, the `IronVNC` module incorrectly mapped
this scancode to a `KeySym`, which resulted in an incorrect `KeySym` or
`NO_SYMBOL`.
2025-07-11 15:08:53 +00:00
Alex Yusiuk
067d80314a
chore(release): prepare web packages for publishing (#865)
* iron-remote-desktop v0.7.0
* iron-remote-desktop-rdp v0.4.0
2025-07-11 07:53:22 -04:00
devolutionsbot
a3255610d8
chore(release): prepare for publishing (#863)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Success (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
2025-07-08 17:39:04 +00:00
Benoît Cortier
c710909a3c
feat: inital support for .RDP files (#862)
This is paving the way for .rdp file support.

Issue: ARC-339
Issue: ARC-355
2025-07-08 13:10:04 -04:00
dependabot[bot]
14e245d73e
build(deps): bump tokio-tungstenite from 0.26.2 to 0.27.0 (#848) 2025-07-08 14:13:30 +00:00
devolutionsbot
56f8ba4767
chore(release): prepare for publishing (#861) 2025-07-08 13:57:06 +00:00
Pavlo Myroniuk
33530212c4
feat!: update sspi dependency (#839)
Newer version of sspi adds support for server-side Kerberos.
This is relevant for the ironrdp-acceptor crate.
2025-07-08 08:45:30 -04:00
devolutionsbot
b09d46f8f2
chore(release): prepare for publishing (#860) 2025-07-08 12:42:46 +00:00
Pavlo Myroniuk
7e23a8bb97
feat(ironrdp-tokio): add async ReqwestNetworkClient::send method (#859)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-07-08 05:57:43 -04:00
Alex Yusiuk
4dc5945019
fix(web): run navigator.clipboard.write only when window has focus (#858)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Success (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
When we receive clipboard update from the server and the browser window
is not in focus (for example, when the user copies some text directly on
the machine, not via the browser's VNC viewer), we got an error that
`navigator.clipboard.write` is not allowed when window is not in focus.
This PR adds a window check that the window has focus, and now
`clipboard.write` runs only when the window is in focus.
2025-07-04 11:21:52 +00:00
Alex Yusiuk
a84a5c0571
fix(web): prevent onMonitorClipboard from re-scheduling after iron-remote-desktop component destroys (#856) 2025-07-04 05:59:33 -04:00
Alex Yusiuk
b0e87a3776
fix(web): fix issue when clipboard monitoring treats clipboard update from server as the local update (#857)
When the server sends the clipboard update, we write it to our
clipboard. But this new clipboard data was then processed as a new one,
so we sent it back to the server. This commit fixes this behavior by
tracking the data that we received from the server.
2025-07-04 05:58:45 -04:00
Benoît Cortier
9c99133569
refactor(client): remove redundant value_parser option (#853)
Some checks are pending
CI / Web Client (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
This option is only relevant when specifying a value parser different
than the default one.
2025-07-03 10:09:45 +00:00
devolutionsbot
cf21250dcc
chore(release): prepare for publishing (#851)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-07-03 05:53:00 +00:00
Benoît Cortier
48e02441d2
chore: update Rust toolchain to 1.88.0 (#852)
MSRV is also bumped to 1.84.
2025-07-03 07:38:28 +03:00
Benoît Cortier
eca256ae10
build(deps): bump picky to v7.0.0-rc.15 (#850)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
2025-07-01 10:36:47 +00:00
Benoît Cortier
6910a3ca36
style: apply cargo +nightly fmt (#849) 2025-07-01 10:24:41 +00:00
dependabot[bot]
2a49588b3d
build(deps): bump reqwest from 0.12.20 to 0.12.21 in the patch group across 1 directory (#847)
Some checks are pending
CI / Check formatting (push) Waiting to run
CI / Check typos (push) Waiting to run
CI / Checks [linux] (push) Blocked by required conditions
CI / Checks [macos] (push) Blocked by required conditions
CI / Checks [windows] (push) Blocked by required conditions
CI / Fuzzing (push) Blocked by required conditions
CI / Web Client (push) Blocked by required conditions
CI / FFI (push) Blocked by required conditions
CI / Success (push) Blocked by required conditions
Coverage / Coverage Report (push) Waiting to run
Release crates / Open release PR (push) Waiting to run
Release crates / Release crates (push) Waiting to run
2025-07-01 00:20:43 -04:00
Miha Markič
4260537c90
fix(ffi): avoid infinite loop in HandleClipboardEvents when not on Windows (#846)
When running on non-Windows, HandleClipboardEvents will create an
infinite loop consuming 100% CPU.
Now, we call HandleClipboardEvents only when on Windows.
2025-06-28 15:51:57 +00:00
Benoît Cortier
76a2a0b47b
chore(iron-remote-desktop): fix CHANGELOG for latest release (#844) 2025-06-27 11:27:13 +00:00
devolutionsbot
ad64c83814
chore(release): prepare for publishing (#836) 2025-06-27 07:10:42 -04:00
Benoît Cortier
8bcf362102
chore(release): prepare web packages for publishing (#843)
- iron-remote-desktop v0.6.0
- iron-remote-desktop-rdp v0.3.0
2025-06-27 11:09:06 +03:00
Alex Yusiuk
f6285c5989
feat(web): add canvasResizedCallback method to SessionBuilder (#842)
Co-authored-by: Benoît Cortier <3809077+CBenoit@users.noreply.github.com>
2025-06-27 07:55:26 +00:00
Benoît Cortier
7c4a496ece
chore(release): prepare web packages for publishing (#840)
- iron-remote-desktop v0.5.4
- iron-remote-desktop-rdp v0.2.3
2025-06-25 16:17:52 +00:00
Alex Yusiuk
dfb13ec499
fix(web): prevent scrolling when setting focus on the canvas (#838) 2025-06-24 10:19:42 -04:00
Alex Yusiuk
d3d758891b
fix(web): raise only TERMINATED message on session termination with the error backtrace (#837) 2025-06-24 10:12:56 -04:00
dependabot[bot]
51d6d1fcbe
build(deps): bump sspi from 0.15.11 to 0.15.13 in the patch group across 1 directory (#835) 2025-06-24 00:18:34 -07:00
Alex Yusiuk
bbf7ab3394
style(web): fix typo in the session termination message (#834) 2025-06-24 00:09:58 -07:00
Alex Yusiuk
d38c2013f0
fix(web): keyboard input not working after launching a session via Firefox (#833) 2025-06-23 08:33:23 -04:00
Benoît Cortier
153fe3c20d
chore(release): prepare iron-remote-desktop v0.5.3 (#832) 2025-06-20 15:29:52 +00:00
Alex Yusiuk
ae3066337f
fix(web): move onDestroy function out of the scaleSession function (#831) 2025-06-20 11:11:21 -04:00
Benoît Cortier
bfa71126bf
chore(release): prepare web packages for publishing (#830)
- iron-remote-desktop v0.5.2
- iron-remote-desktop-rdp v0.2.2
2025-06-20 11:51:35 +00:00
Alex Yusiuk
4f9ef0c21a
fix(web): fix invalid viewer style for dynamic resizing (#829) 2025-06-20 07:18:32 -04:00
Alex Yusiuk
cd19cfc526
fix(web): fix fullscreen mode scale calculations (#828) 2025-06-20 07:17:19 -04:00
Alex Yusiuk
7829f4adb1
fix(web): hide slider in fullscreen mode (#827) 2025-06-20 07:16:54 -04:00
Alex Yusiuk
727c30870b fix(web): fix softbuffer panic when resizing screen (#825) 2025-06-20 01:20:31 -04:00
Alex Yusiuk
4d9cf56e68
fix(web): remove window resize listener on component destroy (#823) 2025-06-20 00:17:56 -04:00
Benoît Cortier
c31aa58fe6
chore(release-plz): make release-plz happy with ironrdp-dvc-pipe-proxy (#822) 2025-06-17 11:20:53 +00:00
Vladyslav Nikonov
5482365655
feat(dvc): add DVC named pipe proxy support (#791) 2025-06-17 10:16:41 +00:00
dependabot[bot]
e5f92ae11c
build(deps): bump the patch group across 2 directories with 6 updates (#821) 2025-06-17 04:08:04 -04:00
Alex Yusiuk
dfb99029a9
fix(web): fix session dangling after calling run method (#820) 2025-06-16 16:35:48 +00:00
Benoît Cortier
0f49ea4608
chore(release): prepare npm packages (#819) 2025-06-11 14:41:43 +00:00
Alex Yusiuk
03f793940a
fix(web-client)!: remove dependency on RxJS (#818) 2025-06-11 10:10:02 -04:00
Benoît Cortier
112a1672d5
ci(npm-publish): fix jobs dependencies (#811) 2025-06-11 10:01:58 -04:00
dependabot[bot]
5c5f441bdd
build(deps): bump the patch group across 1 directory with 3 updates (#816) 2025-06-09 22:31:09 -04:00
Benoît Cortier
370a3f1104
chore(release): prepare ironrdp-server 0.6.1 (#815)
Patch bump including the ironrdp-tokio dependency.
2025-06-09 07:24:11 -04:00
devolutionsbot
c09f9719e0
chore(release): prepare for publishing (#813) 2025-06-06 09:18:24 +00:00
Benoît Cortier
9408789491
fix(tokio)!: adjust reqwest-related features (#812)
- Remove `reqwest` from the default feature set.
- Disable default TLS backend.
- Add `reqwest-rustls-ring` to enable rustls + ring backend.
- Add `reqwest-native-tls` to enable native-tls backend.
2025-06-06 11:34:50 +03:00
Benoît CORTIER
7f1a8be7f5 chore(release): prepare iron-remote-desktop-rdp v0.2.0 2025-06-05 21:47:23 -04:00
Benoît CORTIER
4340af89d8 chore(release): prepare iron-remote-desktop v0.4.0 2025-06-05 21:47:23 -04:00
Alex Yusiuk
cc0a17c269
fix!(web-client): rename callExtension to invokeExtension for UserInteraction (#808) 2025-06-05 21:17:18 -04:00
Alex Yusiuk
4c8c5318e3
chore(web-client): update iron-remote-desktop package version to 0.3.0 (#805) 2025-06-04 14:02:18 +00:00
jpy794
1236a9be99
feat(client): support for hardware cursor (#804) 2025-06-04 07:28:42 +00:00
devolutionsbot
bca455f158
chore(release): prepare for publishing (#800) 2025-06-03 15:22:28 +00:00
Alex Yusiuk
f68cd06ac3
fix!: rename callExtension to invokeExtension (#803) 2025-06-03 14:42:46 +00:00
dependabot[bot]
63a5cd6752
build(deps): bump the patch group across 1 directory with 4 updates (#802) 2025-06-03 02:39:35 -04:00
Benoît Cortier
4a81c5d7cc
ci: properly update apt mirrors for Android target (#798) 2025-06-02 17:46:25 -04:00
Alex Yusiuk
9f6647c341
feat(iron-remote-desktop): introduce callExtension method in iron-remote-desktop API (#799) 2025-06-02 14:15:58 -04:00
Emmanuel Ferdman
6024251985
docs: update fuzz location in ARCHITECTURE.md (#797) 2025-05-31 02:27:52 +00:00
Benoît Cortier
5e6e4e1627
chore(release): prepare ironrdp-core 0.1.5 (#795) 2025-05-27 12:25:54 -04:00
devolutionsbot
aa6777b56a
chore(release): prepare for publishing (#748) 2025-05-27 15:21:56 +00:00
Benoît CORTIER
5abd9ff8e0 feat(acceptor): make the CredsspSequence type public 2025-05-27 08:48:51 -04:00
Benoît CORTIER
9bc382348d style: run formatter for imports 2025-05-27 08:48:51 -04:00
dependabot[bot]
e76f15e8bc
build(deps): bump winit from 0.30.10 to 0.30.11 in the patch group across 1 directory (#793) 2025-05-27 08:21:01 -04:00
dependabot[bot]
87ed315bc2
build(deps): bump bitflags from 2.9.0 to 2.9.1 in the patch group across 1 directory (#792) 2025-05-20 01:37:48 -04:00
dependabot[bot]
3029c8f909
build(deps): bump the patch group across 2 directories with 2 updates (#789) 2025-05-13 07:05:19 -04:00
Alex Yusiuk
294dc39790
chore(release): prepare iron-remote-desktop v0.2.0 and iron-remote-desktop-rdp v0.1.1 (#788)
Co-authored-by: Benoît Cortier <3809077+CBenoit@users.noreply.github.com>
2025-05-09 10:55:22 +00:00
Marc-André Lureau
3d1762c777 feat(client): add --codecs
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-05-09 12:28:46 +02:00
Marc-André Lureau
783702962a feat(session): make client_codecs_capabilities() configurable
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-05-09 12:28:46 +02:00
Alex Yusiuk
0817d60910
refactor(iron-remote-desktop): use PartialObserver instead of single callback for onSessionEvent (#787) 2025-05-09 11:13:39 +02:00
Marc-André Lureau
ce7379be03 refactor(pdu): simplify Codec::decode
No need to special-case codec_properties_len == 0, defer to the decoding
of the properties instead.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-05-09 09:11:45 +02:00
Marc-André Lureau
f03ee393a3 refactor!: add supported codecs in BitmapConfig
"session" has a fixed set of supported codecs with associated IDs.

"connector" must expose the set of codecs during capabilities exchange.
It currently uses hard-codes codec IDs in different places.

Move the BitmapCodecs set to ironrdp-pdu. Shared code will be used by
the server, so this is a suitable common place.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-05-09 09:11:45 +02:00
Benoît Cortier
d995265724
chore: ensure internal crates are not published (#785) 2025-05-07 08:42:22 +00:00
dependabot[bot]
038c85f36d
build(deps): bump the patch group across 1 directory with 2 updates (#786) 2025-05-06 00:36:17 -04:00
Benoît Cortier
08c2a9d3b7
chore(web): prepare npm packages for publish (#784) 2025-04-30 15:00:19 +00:00
Marc-André Lureau
dd787af5a0 feat(benches): add perfenc
Make some internal APIs publicly visible thanks to "visibility" when
compiling with the "__bench" feature.

("testsuite-core" also learned "__bench", because fast_path.rs is a
shared file)

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-29 13:00:07 +02:00
Marc-André Lureau
fcb390140d refactor(server): introduce UpdateEncoderCodecs
This will simplify setting up the UpdateEncoder with further codecs.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-29 13:00:07 +02:00
dependabot[bot]
e6ee67353b
build(deps): bump sspi from 0.15.4 to 0.15.5 in the patch group across 1 directory (#781) 2025-04-29 09:08:15 +02:00
Benoît Cortier
7b8b207e21
chore(release): prepare for iron-remote-desktop 0.2.0 (#780) 2025-04-25 16:35:07 +00:00
Alex Yusiuk
927eea1707
style(ironrdp-web): improve naming of clipboard variables (#779) 2025-04-25 17:06:33 +02:00
Benoît Cortier
194ed07630
fix(web): improve make_bridge! macro hygiene (#777)
There was still some room for improvement.
2025-04-23 18:19:23 +00:00
Benoît Cortier
24e64d7589
refactor(web): consolidate WASM object constructors as create (#776) 2025-04-23 20:56:11 +03:00
Alex Yusiuk
aef4b924aa
refactor(web-client): refactor WASM interfaces to remove static consructor functions (#775) 2025-04-23 09:53:42 -04:00
Marc-Andre Lureau
d8ab533463
build(deps): yuvutils renamed to yuv (#774)
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-23 09:47:19 -04:00
Marc-André Lureau
fb3769c4a7 feat(server): find & send the damaged tiles
Keep a framebuffer and tile-diff against it, to save from
encoding/sending the same bitmap data regions.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-22 16:16:34 +02:00
Marc-André Lureau
20581bb6f1 feat(graphics): add helper to find diff between images
Add some helper to find "damaged" regions, as 64x64 tiles.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-22 16:16:34 +02:00
Benoît Cortier
cc78b1e3dc
fix(server)!: remove time_warn! from the public API (#773)
This is intended to be an internal macro.
2025-04-22 07:33:12 -04:00
Benoît CORTIER
16651e74a4 fix(web): improve make_bridge! macro hygiene 2025-04-22 13:09:56 +02:00
Benoît CORTIER
baafb20063 refactor(web): remove unrequired #[allow] attribute 2025-04-22 13:09:56 +02:00
Benoît CORTIER
b5bd48c986 docs(iron-remote-desktop): improve README.md 2025-04-22 13:09:56 +02:00
dependabot[bot]
ff798c91a7
build(deps): bump the patch group across 2 directories with 2 updates (#769) 2025-04-22 03:08:49 -04:00
Benoît Cortier
1ff4bfc62c
chore(release): prepare iron-remote-desktop (#766) 2025-04-22 09:30:34 +03:00
Benoît Cortier
8961b40012
refactor(web): iron_remote_desktop::export -> make_bridge (#765) 2025-04-21 14:50:06 +00:00
Benoît Cortier
e0eea449b8
refactor(web): rework clipboard API (#764) 2025-04-21 14:29:21 +00:00
Benoît Cortier
f287e168a8
refactor: rework extension API for iron-remote-desktop (#762) 2025-04-21 09:33:12 -04:00
Benoît Cortier
45884c5d38
refactor(web): iron_init/setup logic (#763)
- `iron_init` is renamed to `setup`, so that
  - `init` is the function for initializing the WASM module.
- `setup` is the function taking parameters for logger and performing
other setting up operations.
- Since `setup` may be called after `init` is called, it’s possible to
use some functions of the WASM module before calling `setup` if
necessity arises.

- Common initializion code is moved to an hidden "internal" module of
iron-remote-desktop crate, that is thus not part of the public API.

- Restored the "IronRDP is ready" debug log when `setup` is called.
2025-04-21 15:32:33 +03:00
Benoît Cortier
712da42ded
fix: inject socket local address for the client addr (#759)
We used to inject the resolved target server address, but that is not
what is expected. Server typically ignores this field so this was not a
problem up until now.
2025-04-21 09:08:50 +00:00
Alex Yusiuk
ec1832bba0
feat(ironrdp-web): iron-remote-desktop helper crate for remote desktop WASM modules (#755) 2025-04-18 07:30:45 -04:00
Gabriel Bauman
178670b4a8 fix(client): swap transposed width and height parameters so that dynamic resizing works properly 2025-04-17 21:03:57 -04:00
Marc-Andre Lureau
806f1d7694
fix(server): use desktop size for RFX channel size (#756) 2025-04-17 08:01:58 +02:00
Zac Bergquist
bdde2c76de
fix(client)!: fix name of client address field (#754) 2025-04-16 04:23:49 -04:00
Alex Yusiuk
a82e280b93
refactor(web-client): introduce ConfigBuilder for a better connection API (#749) 2025-04-15 11:49:53 -04:00
Benoît Cortier
c09531ef0c
fix(client): handle leftover bytes (#753) 2025-04-15 08:33:17 -04:00
dependabot[bot]
ecd2450a7a
build(deps): bump the patch group across 2 directories with 3 updates (#751) 2025-04-15 01:03:39 -04:00
Alex Yusiuk
fe676eeac5
refactor(web): follow-up to #722 (#747) 2025-04-14 14:05:19 +00:00
Benoît Cortier
cc3dbf124f
feat(client): add support for RDCleanPath (#745)
Issue: ARC-309
2025-04-14 13:08:44 +00:00
Benoît Cortier
5e6746c1b6
ci(npm-publish): update workflow (#746) 2025-04-14 13:00:13 +00:00
Alex Yusiuk
0ff1ed8de5
refactor(web-client): refactor iron-remote-gui into iron-remote-desktop (#722) 2025-04-11 08:28:27 -04:00
Marc-André Lureau
184cfd24ae test(server): run the fast_path tests from testsuite-core
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-10 10:21:54 +02:00
Marc-André Lureau
2c7556ba1e refactor(server): make UpdateEncoder::update() an iterator
A single display update can now result in multiple commands / update
code (FastPathUpdate).

The update dispatching and bitmap encoding is now done by the
UpdateEncoder itself.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-10 10:21:54 +02:00
Marc-André Lureau
fde18ad01a refactor(server): make UpdateFragmenter own its data
Trying to share a common buffer creates all sort of complicated lifetime
issues when trying to move the encoding to a 'static handler, or when
implementing iterators. It's not worth it.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-10 10:21:54 +02:00
Marc-André Lureau
f21a6bf7d0 test(server): add fragment test
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-10 10:21:54 +02:00
Marc-André Lureau
5ffeeea3ae refactor(server): move fast-path fragmenter to own unit
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-10 10:21:54 +02:00
Vladyslav Nikonov
135b8bc4f6
build(deps): update windows crate to 0.61.1 (#743) 2025-04-09 08:54:48 +00:00
Marc-Andre Lureau
a8b9614323
fix(rdpsnd): send client formats that match server (#742)
Windows seems to be confused if the client replies with more formats, or
unknown formats (opus).

---------

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Co-authored-by: Benoît Cortier <3809077+CBenoit@users.noreply.github.com>
2025-04-09 01:43:03 +00:00
Marc-André Lureau
4172571e8e refactor(rdpsnd)!: pass format_no instead of AudioFormat
This can help avoid extra lookups and cloning.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-08 16:49:05 +02:00
Marc-André Lureau
3d7bc28b97 fix(rdpsnd): lookup the associated format from the client list
This is an index to the client list, according to:
7df64d93-7594-4035-978d-229f2b15f1bc

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-08 16:49:05 +02:00
Marc-André Lureau
7bd92c0ce5 feat(rdpsnd): add support for client custom flags
Client can support various flags, but always set ALIVE.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-08 16:49:05 +02:00
Marc-André Lureau
5e78f91713 feat(rdpdr): add USER_LOGGEDON flag support
As I was debugging some related issue, I implemented that. It may become
useful some day.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-08 16:49:05 +02:00
Marc-André Lureau
f9b6992e74 fix(session): decrease verbosity of FastPath header
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-08 16:49:05 +02:00
Marc-André Lureau
b31b99eafb fix(session): decrease verbosity of Rfx frame_index
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-08 16:49:05 +02:00
dependabot[bot]
1df0737b0d
build(deps): bump the patch group across 1 directory with 2 updates (#738) 2025-04-08 12:55:21 +00:00
dependabot[bot]
e70e7e2c5f
build(deps): bump smallvec from 1.14.0 to 1.15.0 (#739) 2025-04-08 08:38:53 -04:00
Marc-André Lureau
cd7a60ba45 feat(session): add DecodeImage helpers
Having a helper to take the slice of updated region data is generally helpful.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-08 14:37:50 +02:00
Marc-André Lureau
45f66117ba feat(session)!: make DecodedImage Send
This will allow to share it between different threads.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-08 14:37:50 +02:00
Marc-André Lureau
7507a152f1 fix(session): return the correct updated region
"update_rectangle" is set to empty(). The surface updates are then added
by "union". But a union with an empty rectangle at (0,0) is still a
rectangle at (0,0). We end up with big region updates rooted at (0,0)...

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-08 14:37:50 +02:00
Marc-André Lureau
9f0edcc4c9 feat(client): Add no_audio_playback flag to Config struct
Enable audio playback on the client.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-08 13:35:44 +02:00
Marc-André Lureau
5dcc526f51 fix(rdpsnd): reply to TrainingPdu
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-08 13:35:44 +02:00
Marc-André Lureau
abcc42e01f fix(rdpsnd): correct TrainingPdu wPackSize field
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-08 13:35:44 +02:00
Marc-Andre Lureau
032c38be92
feat(tokio): add reqwest feature (#734)
Move the client ReqwestNetworkClient to ironrdp-tokio, so other clients
can optionally use the implementation.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-04-02 14:59:02 -04:00
dependabot[bot]
817abb9805
build(deps): bump whoami from 1.5.2 to 1.6.0 (#732)
Bumps [whoami](https://github.com/ardaku/whoami) from 1.5.2 to 1.6.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ardaku/whoami/releases">whoami's
releases</a>.</em></p>
<blockquote>
<h2>v1.6.0</h2>
<h1>Changelog</h1>
<h2>Added</h2>
<ul>
<li>Support for GNU/Hurd</li>
</ul>
<h2>Changed</h2>
<ul>
<li>Removed comment about hostname being limited to ASCII due to Unicode
hostnames being supported on Windows</li>
<li>More descriptive error messages on the web target</li>
</ul>
<h2>Fixed</h2>
<ul>
<li><code>account()</code> always returning username instead of user
principal name on Windows</li>
<li><code>langs()</code> now returns a list accurate to the POSIX locale
spec</li>
<li><code>hostname()</code> on Windows now returns
<code>PhysicalDnsHostname</code> instead of <code>NetBIOS</code></li>
<li><code>devicename()</code>: Fixed reading escaped unix pretty
names</li>
<li>Link error on Windows (in future Rust version)</li>
</ul>
<hr />
<h2>What's Changed</h2>
<ul>
<li>Remove comment about hostname being limited to ASCII by <a
href="https://github.com/davidkna"><code>@​davidkna</code></a> in <a
href="https://redirect.github.com/ardaku/whoami/pull/119">ardaku/whoami#119</a></li>
<li>Add support for GNU/Hurd by <a
href="https://github.com/pinotree"><code>@​pinotree</code></a> in <a
href="https://redirect.github.com/ardaku/whoami/pull/120">ardaku/whoami#120</a></li>
<li>Apply target rename wasm32-wasip1 by <a
href="https://github.com/AldaronLau"><code>@​AldaronLau</code></a> in <a
href="https://redirect.github.com/ardaku/whoami/pull/124">ardaku/whoami#124</a></li>
<li>Backport Update copyright (<a
href="https://redirect.github.com/ardaku/whoami/issues/129">#129</a>) by
<a href="https://github.com/AldaronLau"><code>@​AldaronLau</code></a> in
<a
href="https://redirect.github.com/ardaku/whoami/pull/130">ardaku/whoami#130</a></li>
<li>Bump version to 1.6.0-pre.0 by <a
href="https://github.com/AldaronLau"><code>@​AldaronLau</code></a> in <a
href="https://redirect.github.com/ardaku/whoami/pull/132">ardaku/whoami#132</a></li>
<li>Backport v2 -&gt; v1: Fix clippy duplicate attribute for MacOS by <a
href="https://github.com/AldaronLau"><code>@​AldaronLau</code></a> in <a
href="https://redirect.github.com/ardaku/whoami/pull/135">ardaku/whoami#135</a></li>
<li>v1: Update wasm-bindgen to v0.2.89 by <a
href="https://github.com/AldaronLau"><code>@​AldaronLau</code></a> in <a
href="https://redirect.github.com/ardaku/whoami/pull/138">ardaku/whoami#138</a></li>
<li>Backport v2 -&gt; v1: Check more environment variables for language
by <a href="https://github.com/AldaronLau"><code>@​AldaronLau</code></a>
in <a
href="https://redirect.github.com/ardaku/whoami/pull/146">ardaku/whoami#146</a></li>
<li>Backport v2 -&gt; v1: Switch Windows <code>hostname()</code> to
return <code>PhysicalDnsHostname</code> by <a
href="https://github.com/AldaronLau"><code>@​AldaronLau</code></a> in <a
href="https://redirect.github.com/ardaku/whoami/pull/148">ardaku/whoami#148</a></li>
<li>Backport v2 -&gt; v1: Improved error handling by <a
href="https://github.com/AldaronLau"><code>@​AldaronLau</code></a> in <a
href="https://redirect.github.com/ardaku/whoami/pull/149">ardaku/whoami#149</a></li>
<li>1.6.0-pre.1 and Backport v2 -&gt; v1: Adjusts langs() to match POSIX
locale spec by <a
href="https://github.com/AldaronLau"><code>@​AldaronLau</code></a> in <a
href="https://redirect.github.com/ardaku/whoami/pull/151">ardaku/whoami#151</a></li>
<li>Backport v2 -&gt; v1: Fix license link by <a
href="https://github.com/AldaronLau"><code>@​AldaronLau</code></a> in <a
href="https://redirect.github.com/ardaku/whoami/pull/154">ardaku/whoami#154</a></li>
<li>Add discriminants to ExtendedNameFormat enum by <a
href="https://github.com/Batch21"><code>@​Batch21</code></a> in <a
href="https://redirect.github.com/ardaku/whoami/pull/156">ardaku/whoami#156</a></li>
<li>Version 1.6.0 pre.2 by <a
href="https://github.com/AldaronLau"><code>@​AldaronLau</code></a> in <a
href="https://redirect.github.com/ardaku/whoami/pull/158">ardaku/whoami#158</a></li>
<li>Backport v2-&gt;v1: Fix reading escaped unix pretty names by <a
href="https://github.com/AldaronLau"><code>@​AldaronLau</code></a> in <a
href="https://redirect.github.com/ardaku/whoami/pull/178">ardaku/whoami#178</a></li>
<li>Release v1.6.0 by <a
href="https://github.com/AldaronLau"><code>@​AldaronLau</code></a> in <a
href="https://redirect.github.com/ardaku/whoami/pull/162">ardaku/whoami#162</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/davidkna"><code>@​davidkna</code></a>
made their first contribution in <a
href="https://redirect.github.com/ardaku/whoami/pull/119">ardaku/whoami#119</a></li>
<li><a href="https://github.com/pinotree"><code>@​pinotree</code></a>
made their first contribution in <a
href="https://redirect.github.com/ardaku/whoami/pull/120">ardaku/whoami#120</a></li>
<li><a href="https://github.com/Batch21"><code>@​Batch21</code></a> made
their first contribution in <a
href="https://redirect.github.com/ardaku/whoami/pull/156">ardaku/whoami#156</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/ardaku/whoami/compare/v1.5.2...v1.6.0">https://github.com/ardaku/whoami/compare/v1.5.2...v1.6.0</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ardaku/whoami/blob/v1.6.0/CHANGELOG.md">whoami's
changelog</a>.</em></p>
<blockquote>
<h2>[1.6.0] - 2025-03-23</h2>
<h3>Added</h3>
<ul>
<li>Support for GNU/Hurd</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Removed comment about hostname being limited to ASCII due to Unicode
hostnames being supported on Windows</li>
<li>More descriptive error messages on the web target</li>
</ul>
<h3>Fixed</h3>
<ul>
<li><code>account()</code> always returning username instead of user
principal name on
Windows</li>
<li><code>langs()</code> now returns a list accurate to the POSIX locale
spec</li>
<li><code>hostname()</code> on Windows now returns
<code>PhysicalDnsHostname</code> instead of
<code>NetBIOS</code></li>
<li><code>devicename()</code>: Fixed reading escaped unix pretty
names</li>
<li>Link error on Windows (in future Rust version)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="8170882f09"><code>8170882</code></a>
Release v1.6.0 (<a
href="https://redirect.github.com/ardaku/whoami/issues/162">#162</a>)</li>
<li><a
href="109da1cbe1"><code>109da1c</code></a>
Backport v2-&gt;v1: Fix reading escaped unix pretty names (<a
href="https://redirect.github.com/ardaku/whoami/issues/178">#178</a>)</li>
<li><a
href="bc489c07b3"><code>bc489c0</code></a>
Version 1.6.0 pre.2 (<a
href="https://redirect.github.com/ardaku/whoami/issues/158">#158</a>)</li>
<li><a
href="7aabbfa401"><code>7aabbfa</code></a>
Add discriminants to ExtendedNameFormat enum (<a
href="https://redirect.github.com/ardaku/whoami/issues/156">#156</a>)</li>
<li><a
href="7aba7a48ef"><code>7aba7a4</code></a>
Backport v2 -&gt; v1: Fix license link (<a
href="https://redirect.github.com/ardaku/whoami/issues/154">#154</a>)</li>
<li><a
href="d90a4f8746"><code>d90a4f8</code></a>
1.6.0-pre.1 and Backport v2 -&gt; v1: Adjusts langs() to match POSIX
locale spec...</li>
<li><a
href="a02709782b"><code>a027097</code></a>
Backport v2 -&gt; v1: Improved error handling (<a
href="https://redirect.github.com/ardaku/whoami/issues/149">#149</a>)</li>
<li><a
href="d9df51768d"><code>d9df517</code></a>
Backport v2 -&gt; v1: Switch Windows <code>hostname()</code> to return
`PhysicalDnsHostname...</li>
<li><a
href="85933dcbb3"><code>85933dc</code></a>
Backport v2 -&gt; v1: Check more environment variables for language (<a
href="https://redirect.github.com/ardaku/whoami/issues/146">#146</a>)</li>
<li><a
href="ef861597df"><code>ef86159</code></a>
v1: Update wasm-bindgen to v0.2.89 (<a
href="https://redirect.github.com/ardaku/whoami/issues/138">#138</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/ardaku/whoami/compare/v1.5.2...v1.6.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=whoami&package-manager=cargo&previous-version=1.5.2&new-version=1.6.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 10:38:50 -04:00
dependabot[bot]
ba488f956c
build(deps): bump the patch group across 1 directory with 2 updates (#731)
Bumps the patch group with 2 updates in the / directory:
[image](https://github.com/image-rs/image) and
[clap](https://github.com/clap-rs/clap).

Updates `image` from 0.25.5 to 0.25.6
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/image-rs/image/blob/main/CHANGES.md">image's
changelog</a>.</em></p>
<blockquote>
<h3>Version 0.25.6</h3>
<p>Features:</p>
<ul>
<li>Improved format detection (<a
href="https://redirect.github.com/image-rs/image/pull/2418">#2418</a>)</li>
<li>Implement writing ICC profiles for JPEG and PNG images (<a
href="https://redirect.github.com/image-rs/image/pull/2389">#2389</a>)</li>
</ul>
<p>Bug fixes:</p>
<ul>
<li>JPEG encoding bugfix (<a
href="https://redirect.github.com/image-rs/image/pull/2387">#2387</a>)</li>
<li>Expanded ICO format detection (<a
href="https://redirect.github.com/image-rs/image/pull/2434">#2434</a>)</li>
<li>Fixed EXR bug with NaNs (<a
href="https://redirect.github.com/image-rs/image/pull/2381">#2381</a>)</li>
<li>Various documentation improvements</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f337e27aad"><code>f337e27</code></a>
Release 0.25.6 (<a
href="https://redirect.github.com/image-rs/image/issues/2441">#2441</a>)</li>
<li><a
href="0166f687e9"><code>0166f68</code></a>
CI: add num-traits to public (<a
href="https://redirect.github.com/image-rs/image/issues/2446">#2446</a>)</li>
<li><a
href="ca9e2dceb4"><code>ca9e2dc</code></a>
add links to readme (<a
href="https://redirect.github.com/image-rs/image/issues/2437">#2437</a>)</li>
<li><a
href="95be33928e"><code>95be339</code></a>
Making clippy happy (<a
href="https://redirect.github.com/image-rs/image/issues/2439">#2439</a>)</li>
<li><a
href="c62d3ace61"><code>c62d3ac</code></a>
Detect image/vnd.microsoft.icon mime types as ImageFormat::Ico (<a
href="https://redirect.github.com/image-rs/image/issues/2434">#2434</a>)</li>
<li><a
href="85f2412d55"><code>85f2412</code></a>
Fix missing spaces in JpegDecoder error message (<a
href="https://redirect.github.com/image-rs/image/issues/2433">#2433</a>)</li>
<li><a
href="b22ba14127"><code>b22ba14</code></a>
Remove limits when parsing JPEG metadata (<a
href="https://redirect.github.com/image-rs/image/issues/2429">#2429</a>)</li>
<li><a
href="4ef6f1505c"><code>4ef6f15</code></a>
Fix unbalanced backticks in doc comments (<a
href="https://redirect.github.com/image-rs/image/issues/2427">#2427</a>)</li>
<li><a
href="d4054385a1"><code>d405438</code></a>
Reduce typo count (<a
href="https://redirect.github.com/image-rs/image/issues/2426">#2426</a>)</li>
<li><a
href="68159de1c1"><code>68159de</code></a>
Update resize and blurs doc (<a
href="https://redirect.github.com/image-rs/image/issues/2424">#2424</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/image-rs/image/compare/v0.25.5...v0.25.6">compare
view</a></li>
</ul>
</details>
<br />

Updates `clap` from 4.5.32 to 4.5.34
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/clap-rs/clap/releases">clap's
releases</a>.</em></p>
<blockquote>
<h2>v4.5.34</h2>
<h2>[4.5.34] - 2025-03-27</h2>
<h3>Fixes</h3>
<ul>
<li><em>(help)</em> Don't add extra blank lines with
<code>flatten_help(true)</code> and subcommands without arguments</li>
</ul>
<h2>v4.5.33</h2>
<h2>[4.5.33] - 2025-03-26</h2>
<h3>Fixes</h3>
<ul>
<li><em>(error)</em> When showing the usage of a suggestion for an
unknown argument, don't show the group</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/clap-rs/clap/blob/master/CHANGELOG.md">clap's
changelog</a>.</em></p>
<blockquote>
<h2>[4.5.34] - 2025-03-27</h2>
<h3>Fixes</h3>
<ul>
<li><em>(help)</em> Don't add extra blank lines with
<code>flatten_help(true)</code> and subcommands without arguments</li>
</ul>
<h2>[4.5.33] - 2025-03-26</h2>
<h3>Fixes</h3>
<ul>
<li><em>(error)</em> When showing the usage of a suggestion for an
unknown argument, don't show the group</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="5d2cdac3e6"><code>5d2cdac</code></a>
chore: Release</li>
<li><a
href="f1c10ebe58"><code>f1c10eb</code></a>
docs: Update changelog</li>
<li><a
href="a4d1a7fe2b"><code>a4d1a7f</code></a>
chore(ci): Take a break from template updates</li>
<li><a
href="e95ed396c4"><code>e95ed39</code></a>
Merge pull request <a
href="https://redirect.github.com/clap-rs/clap/issues/5775">#5775</a>
from vivienm/master</li>
<li><a
href="18f8d4c3f5"><code>18f8d4c</code></a>
chore(deps): Update Rust Stable to v1.82 (<a
href="https://redirect.github.com/clap-rs/clap/issues/5788">#5788</a>)</li>
<li><a
href="f35d8e09fb"><code>f35d8e0</code></a>
Merge pull request <a
href="https://redirect.github.com/clap-rs/clap/issues/5787">#5787</a>
from epage/template</li>
<li><a
href="1389d7d689"><code>1389d7d</code></a>
chore: Update from '_rust/main' template</li>
<li><a
href="dbc9faa79d"><code>dbc9faa</code></a>
chore(ci): Initialize git for template update</li>
<li><a
href="3dac2f3683"><code>3dac2f3</code></a>
chore(ci): Get history for template update</li>
<li><a
href="e1f77dacf1"><code>e1f77da</code></a>
chore(ci): Fix branch for template update</li>
<li>Additional commits viewable in <a
href="https://github.com/clap-rs/clap/compare/clap_complete-v4.5.32...clap_complete-v4.5.34">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 10:38:24 -04:00
Marc-André Lureau
aeb1193674 feat(server): keep last full-frame/desktop update
It should reflect client drawing state.

In following changes, we will fix it to draw bitmap updates on it, to
keep it up to date.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-31 18:11:03 +02:00
Marc-André Lureau
9d86c28865 refactor(server): pass bitmapUpdate by ref
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-31 18:11:03 +02:00
Marc-André Lureau
137d91ae7a feat(server): implement some Encoder Debug
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-31 18:11:03 +02:00
Marc-André Lureau
c2164716c3 refactor(server): split UpdateEncoder
The bitmap encoder dispatching code was becoming convoluted and the same
struct was handling PduEncoding and various bitmap encoding handling.
Instead, split UpdateEncoder in different types and concerns.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-31 18:11:03 +02:00
Marc-André Lureau
a76e84d459 feat(server): add BitmapUpdate::sub()
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-31 18:11:03 +02:00
Marc-André Lureau
229070a435 refactor(server)!: rename left/top -> x/y
This is more idiomatic, and thus less confusing.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-31 18:11:03 +02:00
Marc-André Lureau
1e87961d16 feat(server): add Framebuffer helper struct
This will hold the updated bitmap data for the whole framebuffer.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-31 18:11:03 +02:00
Marc-André Lureau
3c43fdda76 refactor(server)!: use bytes, allowing shareable bitmap data
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-31 18:11:03 +02:00
Marc-André Lureau
7f57817805 feat(server): add stride debug info
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-31 18:11:03 +02:00
Marc-André Lureau
db6f4cdb7f refactor(server)!: drop support for pixelOrder
Dealing with multiple formats is sufficiently annoying, there isn't much
need for awkward image layout. This was done for efficiency reason for
bitmap encoding, but bitmap is really inefficient anyway and very few
servers will actually provide bottom to top images (except with GL/GPU
textures, but this is not in scope yet).

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-31 18:11:03 +02:00
Marc-Andre Lureau
4e581e0f47
feat(cliprdr)!: add on_ready() callback (#729)
Give a hint to the backend when the channel is actually connected &
ready to process messages.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-30 18:39:10 -04:00
Marc-Andre Lureau
a50cd643dc
fix(session): update rectangle when applying None codecs updates (#728)
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-30 18:37:38 -04:00
Marc-André Lureau
ff26400822 debug(session): trace the surface codec
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-31 00:36:26 +02:00
Marc-André Lureau
b957c085b3 doc(session): misc doc annotation fix
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-31 00:36:26 +02:00
Marc-André Lureau
d47c1e6415 debug(server): trace ServerEvent dispatching
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-31 00:36:26 +02:00
Benoît Cortier
401cedf010
chore(release): prepare for iron-remote-gui 0.13.1 (#726) 2025-03-27 19:39:51 +00:00
Benoît Cortier
9f4e6d410b
fix(pdu): fix possible out of bound indexing in RFX module (#724)
An index bound check was missing in the RFX module. Found by fuzzer.
2025-03-27 12:00:20 -04:00
Benoît Cortier
f62104efe6
fix(ffi): use patched diplomat (#723)
This patched version of Diplomat is backporting an important fix from
the more up to date versions of Diplomat.
Without this patch, the code generated by the diplomat::bridge macro
contains an easy to trigger UB.
This UB is triggered by the generated C# code when passing an empty
byte[] array.
2025-03-26 00:55:30 +00:00
irvingouj@Devolutions
3159eeceec
fix(web): fix firefox selection issue (#708)
Co-authored-by: Benoît CORTIER <git.divisible626@passmail.com>
2025-03-25 22:09:48 +00:00
Benoît CORTIER
8ab98820bd ci(ffi): verify FFI code and examples 2025-03-25 12:26:28 +01:00
Benoît CORTIER
1bb6895422 docs(ffi): fix compilation of examples 2025-03-25 12:26:28 +01:00
dependabot[bot]
989a56f233
build(deps): bump the patch group across 1 directory with 3 updates (#719) 2025-03-24 22:54:56 -04:00
Benoît CORTIER
51c45df8c4 docs(ffi): document how to build the .NET bindings 2025-03-24 23:23:26 +01:00
Benoît CORTIER
aa210204a7 chore(xtask): add ffi install task
This task is for installing all the requirements for ffi tasks.
(That is: `diplomat-tool`.)
2025-03-24 23:23:26 +01:00
irvingouj@Devolutions
248588371a
fix(web): re-introduce shadow root but also delegate focus (#717) 2025-03-24 17:55:39 -04:00
Benoît Cortier
b72e0857bf
refactor: move padding module to ironrdp-core (#716) 2025-03-24 22:52:46 +02:00
irvingouj@Devolutions
bceb6c1492
feat(web-client): let iron-svelte-client use automatic token generation (#707)
https://github.com/user-attachments/assets/8a6c1238-e369-4f71-a077-5d592d853f58

---------

Co-authored-by: Benoît Cortier <3809077+CBenoit@users.noreply.github.com>
2025-03-24 16:25:42 -04:00
dependabot[bot]
35da41b20d
build(deps): bump async-trait from 0.1.87 to 0.1.88 in the patch group across 1 directory (#705) 2025-03-18 11:46:48 +09:00
dependabot[bot]
3700caba22
build(deps): bump uuid from 1.15.1 to 1.16.0 (#706) 2025-03-18 11:45:47 +09:00
Benoît Cortier
31ae40c206
chore: only publish GitHub releases for ironrdp-client (#704)
Creating a new GitHub release for the ironrdp crate is actually not very
relevant.
2025-03-14 11:00:44 +02:00
Benoît Cortier
43f05a712d
perf(web-client): enable WASM SIMD instructions (#703) 2025-03-13 14:48:03 +02:00
Benoît CORTIER
97f4f25813 style: run cargo +nightly fmt 2025-03-13 11:03:10 +01:00
Benoît CORTIER
19d6b1ea83 refactor: fix new clippy warnings 2025-03-13 11:03:10 +01:00
Benoît CORTIER
780c5383e1 chore: update Rust toolchain to 1.85.0 2025-03-13 11:03:10 +01:00
devolutionsbot
570cbe3c3f
chore(release): prepare for publishing (#701) 2025-03-13 09:57:35 +00:00
Benoît Cortier
0705840aa5
docs(ironrdp): fix documentation build (#700) 2025-03-13 09:37:13 +00:00
devolutionsbot
b19008c029
chore(release): prepare for publishing (#699) 2025-03-13 09:06:30 +00:00
Benoît Cortier
b5e6f2bb4f
chore(release): prepare for iron-remote-gui 0.13.0 (#697) 2025-03-13 08:50:20 +02:00
devolutionsbot
f2c8482ba6
build: bump ironrdp-pdu (#698) 2025-03-12 20:59:54 +00:00
Marc-André Lureau
7cb1ac99d1 refactor(pdu)!: remove RfxChannelWidth and RfxChannelHeight structs
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-12 21:34:47 +01:00
Marc-André Lureau
097cdb66f9 fix(pdu): TS_RFX_CHANNELT width/height SHOULD be within range
According to the specification, the value does not need to be in the range:
4060f07e-9d73-454d-841e-131a93aca675

(the ironrdp-server can send larger values)

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-12 21:34:47 +01:00
devolutionsbot
92dd927ec2
chore(release): prepare for publishing (#696) 2025-03-12 16:40:47 +00:00
Benoît Cortier
c21fa44fd6
build: do not use workspace dependencies (#695)
As written in the workspace Cargo.toml:

> Note that for better cross-tooling interactions, do not use workspace
dependencies for anything that is not "workspace internal" (e.g.: mostly
dev-dependencies). E.g.: release-plz can’t detect that a dependency has
been
updated in a way warranting a version bump in the dependant if no commit
is
touching a file associated to the crate. It is technically okay to use
that
for "private" (i.e.: not used in the public API) dependencies too, but
we
still want to make follow-up releases to stay up to date with the
community,
even for private dependencies.

Expectation is that release-plz will be able to auto-detect when bumping
dependents is necessary.

Closes #689
2025-03-12 09:25:01 -04:00
Marc-Andre Lureau
5c890d40ad
fix(client): set back control flow to wait (#693)
After resize event is sent, we should switch back to a normal mode,
instead of wait-duration(0)/polling.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-12 12:01:12 +00:00
dependabot[bot]
d01e667ebc
build(deps): bump the patch group across 1 directory with 2 updates (#690) 2025-03-11 06:48:09 +01:00
devolutionsbot
47a77d2b36
chore(release): prepare for publishing (#666) 2025-03-07 12:16:34 +00:00
Marc-Andre Lureau
3b9d558e9c
fix(pdu): fix FastPathHeader minimal size (#687)
The minimal_size() logic didn't properly take into account the overall
PDU size.

This fixes random error/disconnect in client.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-07 09:25:39 +01:00
dependabot[bot]
6e8a8236dc
build(deps): bump SixLabors.ImageSharp from 3.1.3 to 3.1.7 in /ffi/dotnet/Devolutions.IronRdp.ConnectExample (#686) 2025-03-07 05:00:39 +00:00
irvingouj@Devolutions
5214929218
fix(web): allow keyboard input even if the mouse is off the area of canvas (#685) 2025-03-05 02:03:42 +01:00
Marc-André Lureau
7f08a098e2 refactor(pdu): drop legacy trait
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-04 16:08:55 +01:00
Marc-André Lureau
81984f9377 refactor(pdu): move rfx to Encode/Decode traits
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-03-04 16:08:55 +01:00
Gyusun Yeom
6b4af94071
fix(pdu): make AddressFamily parsing resilient (#672)
Co-authored-by: Benoît Cortier <3809077+CBenoit@users.noreply.github.com>
2025-03-04 13:47:38 +00:00
irvingouj@Devolutions
c4595bda2f
chore(web): migrate to Svelte 5 and Vite 6 (#680)
Also changed build output type for better performance.
2025-03-03 12:59:53 -05:00
dependabot[bot]
2a98f8be3e
build(deps): bump the patch group across 1 directory with 5 updates (#679) 2025-02-25 20:30:10 +09:00
Marc-André Moreau
bf9adaa1a5 use cbake actions, deprecate ubuntu 20.04 runners 2025-02-20 19:33:23 -05:00
irvingouj@Devolutions
e76dc12485
feat(web): implement clipboard enable/disable (#676)
- Add setEnableClipboard function in user interface 
- Updated demo with clipboard control
2025-02-19 10:12:49 -05:00
Richard Markiewicz
58e1cb9034
ci: start updating ubuntu runners (#671)
Start updating Ubuntu GitHub runners away from 20.04
2025-02-17 10:34:32 -05:00
Marc-André Lureau
5555c7b9dd refactor(session): decode RFX to RgbX
Decode to the desired format, instead of converting from BgrX to the
DecodedImage format (typically RgbA) when applying the tile / drawing.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-02-09 18:09:15 +01:00
Marc-André Lureau
bb41724147 test: use the XRGB sample from the spec
It's unclear where the RGB_BUFFER was coming from, it's better to use
the one from the spec, like the YCBCR buffers.

Unfortunately, we don't match with the reference data after conversion.
This doesn't seem a big issue in practice.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-02-09 18:09:15 +01:00
Marc-André Lureau
d7ba22fbed refactor(graphics): drop now unused yuv/rgb code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-02-09 18:09:15 +01:00
Marc-André Lureau
5f1c44027a feat(graphics): replace hand-coded yuv/rgb with yuvutils
cargo bench:
to_ycbcr                time:   [2.2988 µs 2.3251 µs 2.3517 µs]
                        change: [-83.643% -83.534% -83.421%] (p = 0.00 < 0.05)
                        Performance has improved.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-02-09 18:09:15 +01:00
Marc-André Lureau
05c0c97262 graphics: add yuvutils-rs dependency
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-02-09 18:09:15 +01:00
Marc-André Lureau
62d809152a refactor(graphics): use 0xFF for opaque alpha
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-02-09 18:09:15 +01:00
devolutionsbot
de86e2b14a
chore(release): prepare for publishing (#664) 2025-02-06 21:44:04 +00:00
Benoît Cortier
0b5f691c1e
chore(xtask): update binary dependencies (#663) 2025-02-05 18:12:10 -05:00
Marc-Andre Lureau
ccf6348270
feat(rdpsnd): add Opus audio client decoding (#661)
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Co-authored-by: Benoît Cortier <3809077+CBenoit@users.noreply.github.com>
2025-02-05 15:47:31 +00:00
dependabot[bot]
9152132776
build(deps): bump clap from 4.5.27 to 4.5.28 in the patch group across 1 directory (#659) 2025-02-03 19:16:03 -05:00
devolutionsbot
11b92bfcbd
chore(release): prepare for publishing (#658) 2025-02-03 17:13:59 -05:00
Alex Yusiuk
9b2926ea12
fix(cliprdr-native): handle WM_ACTIVATEAPP in clipboard_subproc (#657)
This PR adds handling of `WM_ACTIVATEAPP` in `clipboard_subproc`.
Previously, the function handled only `WM_ACTIVATE`.
2025-02-03 13:38:31 -05:00
devolutionsbot
2a5e783c43
chore(release): prepare for publishing (#656) 2025-01-31 04:22:55 +00:00
Zac Bergquist
c8597733fe
fix(connector): decrease log verbosity for license exchange (#655) 2025-01-30 18:15:31 -05:00
Marc-Andre Lureau
cc0843838d
fix: use 'OR' for SPDX license expression (#654) 2025-01-29 07:06:14 -05:00
devolutionsbot
e6d6e9d8a7
chore(release): prepare for publishing (#628) 2025-01-28 23:24:35 +00:00
Marc-Andre Lureau
f14f3115d4
fix(connector): make LicenseCache RefUnwindSafe (#653)
This fixes commit dd221bf ("feat: support license caching (#634)") and
semver-checks is now happy:

type ProcessorOutput is no longer UnwindSafe, in
/tmp/.tmppi9kAf/IronRDP/crates/ironrdp-session/src/x224/mod.rs:15

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Changelog: ignore
2025-01-28 17:10:41 -05:00
dependabot[bot]
ff6c6e875b
build(deps): bump tokio from 1.42.0 to 1.43.0 (#650) 2025-01-28 08:26:37 -05:00
Benoît Cortier
fd9a597fd0
chore(dependabot): add /fuzz/ to dependabot directories (#647) 2025-01-28 07:55:05 -05:00
dependabot[bot]
ef33e14133
build(deps): bump uuid from 1.12.0 to 1.12.1 in the patch group (#645) 2025-01-28 04:57:09 -05:00
Marc-Andre Lureau
fa353765af
feat(example): encode audio with Opus (#643)
Demonstrates Opus audio codec support (and also fixes sine wave phase)

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-01-27 16:22:15 -05:00
Marc-Andre Lureau
a6c36511f6
feat(server): add volume support (#641)
Add server messages and API to support setting client volume.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-01-27 08:10:15 -05:00
Marc-André Lureau
0f9877ad39 fix(server): check client size
It's problematic when the client didn't resize, as we send bitmap
updates that don't fit. The client will likely drop the connection.
Let's have a warning for this case in the server.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-01-27 07:35:36 -05:00
Marc-André Lureau
e21c5568a4 refactor(server): factor out deactivate_reactivate()
This makes code slightly nicer and allow further code reuse.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-01-27 07:35:36 -05:00
Marc-André Lureau
a0fccf8d1a feat(server): advertize Bitmap::desktopResizeFlag
This makes freerdp keep the flag up and handle desktop
resize/deactivation-reactivation. It should be okay to advertize,
if the server doesn't resize anyway, I guess.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-01-27 07:35:36 -05:00
Marc-André Lureau
7c72a9f9bb fix(server): allow to use basic RDP/no security
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-01-27 07:35:36 -05:00
Marc-André Lureau
98b77b5ee5 fix(examples): fix server deps
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-01-27 07:35:36 -05:00
Marc-André Lureau
1a36fd3669 fix(examples): used import from std instead of core
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-01-27 07:35:36 -05:00
Marc-André Lureau
c26fab4a45 test(extra): add some client-server tests
This is just some basic tests, but hopefully it will grow to be more
friendly and cover more behaviours.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-01-27 07:35:36 -05:00
Marc-André Lureau
82c7c2f5b0 fix(server): do not restart static channels on reactivation
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-01-27 07:35:36 -05:00
Marc-André Lureau
c4587b537c fix(server): reattach existing channels
I couldn't find any explicit behaviour described in the specification,
but apparently, we must just keep the channel state as they were during
reactivation. This fixes various state issues during client resize.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-01-27 07:35:36 -05:00
Marc-André Lureau
63963182b5 fix(server): drop unexpected PDUs during deactivation-reactivation
The current behaviour of handling unmatched PDUs in fn read_by_hint()
isn't good enough. An unexpected PDUs may be received and fail to be
decoded during Acceptor::step().

Change the code to simply drop unexpected PDUs (as opposed to attempting
to replay the unmatched leftover, which isn't clearly needed)

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-01-27 07:35:36 -05:00
Marc-André Lureau
ab8a87d942 feat(dvc): add CreationStatus::NOT_FOUND
For completeness, this error is used by FreeRDP.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-01-27 07:35:36 -05:00
Marc-André Lureau
265b661b81 feat(dvc): some debug statement on invalid channel state
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2025-01-27 07:35:36 -05:00
Marc-André Lureau
3dc8a56070 build(xtask): bump wabt to 1.0.36
https://github.com/WebAssembly/wabt/releases/download/1.0.33/wabt-1.0.33-macos-14.tar.gz
is now 404.
2025-01-24 12:37:29 -05:00
dependabot[bot]
de9b9cbb6b
build(deps): bump the patch group with 7 updates (#637) 2025-01-20 18:37:45 -05:00
dependabot[bot]
059d775816
build(deps): bump uuid from 1.11.0 to 1.12.0 (#638) 2025-01-20 18:37:04 -05:00
dependabot[bot]
a16a131e43
build(deps): bump picky from 7.0.0-rc.11 to 7.0.0-rc.12 (#639) 2025-01-20 18:36:32 -05:00
Przemko Robakowski
dd221bf224
feat: support license caching (#634)
Adds support for license caching by storing the license obtained
from SERVER_UPGRADE_LICENSE message and sending
CLIENT_LICENSE_INFO if a license requested by the server is already
stored in the cache.

Co-authored-by: Benoît Cortier <3809077+CBenoit@users.noreply.github.com>
2025-01-18 14:34:58 +00:00
dependabot[bot]
a2378efb7a
build(deps): bump embed-resource from 2.5.0 to 3.0.1 (#577) 2025-01-17 07:40:50 -05:00
Vladyslav Nikonov
8b2ba27f45
chore: remove now-proto-pdu crate (#633) 2025-01-16 10:00:18 +00:00
Sébastien Duquette
dd249909a8
docs: use CDN URLs instead of the blob storage URLs for Devolutions logo (#631) 2025-01-09 10:06:10 -05:00
dependabot[bot]
25bbb2682c
build(deps): bump the patch group with 3 updates (#629)
Bumps the patch group with 3 updates:
[async-trait](https://github.com/dtolnay/async-trait),
[winit](https://github.com/rust-windowing/winit) and
[reqwest](https://github.com/seanmonstar/reqwest).

Updates `async-trait` from 0.1.83 to 0.1.85
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dtolnay/async-trait/releases">async-trait's
releases</a>.</em></p>
<blockquote>
<h2>0.1.85</h2>
<ul>
<li>Omit <code>Self: 'async_trait</code> bound in impl when not needed
by signature (<a
href="https://redirect.github.com/dtolnay/async-trait/issues/284">#284</a>)</li>
</ul>
<h2>0.1.84</h2>
<ul>
<li>Support <code>impl Trait</code> in return type (<a
href="https://redirect.github.com/dtolnay/async-trait/issues/282">#282</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="fee923d4a0"><code>fee923d</code></a>
Release 0.1.85</li>
<li><a
href="0c2e1083f2"><code>0c2e108</code></a>
Merge pull request <a
href="https://redirect.github.com/dtolnay/async-trait/issues/284">#284</a>
from dtolnay/selfinblock</li>
<li><a
href="9456e54b59"><code>9456e54</code></a>
Omit <code>Self: 'async_trait</code> bound in impl when not needed by
signature</li>
<li><a
href="b77d0d504c"><code>b77d0d5</code></a>
Add regression test for issue 283</li>
<li><a
href="4c8406dd47"><code>4c8406d</code></a>
Release 0.1.84</li>
<li><a
href="1cab7e43da"><code>1cab7e4</code></a>
Merge pull request <a
href="https://redirect.github.com/dtolnay/async-trait/issues/282">#282</a>
from dtolnay/impltrait</li>
<li><a
href="3af8236a3d"><code>3af8236</code></a>
Require Rust 1.75+ for RPITIT (return position impl trait in trait)</li>
<li><a
href="85b572c442"><code>85b572c</code></a>
Support impl Trait in return type</li>
<li><a
href="aff365fb74"><code>aff365f</code></a>
Add regression test for issue 281</li>
<li><a
href="7d8519d416"><code>7d8519d</code></a>
Update ui test suite to nightly-2024-12-09</li>
<li>Additional commits viewable in <a
href="https://github.com/dtolnay/async-trait/compare/0.1.83...0.1.85">compare
view</a></li>
</ul>
</details>
<br />

Updates `winit` from 0.30.7 to 0.30.8
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rust-windowing/winit/releases">winit's
releases</a>.</em></p>
<blockquote>
<h2>Winit version 0.30.8</h2>
<h3>Added</h3>
<ul>
<li><code>ActivationToken::from_raw</code> and
<code>ActivationToken::into_raw</code>.</li>
<li>On X11, add a workaround for disabling IME on GNOME.</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>On Windows, fixed the event loop not waking on accessibility
requests.</li>
<li>On X11, fixed cursor grab mode state tracking on error.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="58402b58cf"><code>58402b5</code></a>
Winit version 0.30.8</li>
<li><a
href="d7710f7264"><code>d7710f7</code></a>
api: add <code>ActivationToken::{from,into}_raw</code></li>
<li><a
href="61314cd50a"><code>61314cd</code></a>
x11: fix cursor grab mode tracking on error</li>
<li><a
href="6b5cc165dd"><code>6b5cc16</code></a>
x11: add workaround for disabling IME on gnome</li>
<li><a
href="43c323ccc0"><code>43c323c</code></a>
windows: fix the event loop not waking on accessibility requests</li>
<li>See full diff in <a
href="https://github.com/rust-windowing/winit/compare/v0.30.7...v0.30.8">compare
view</a></li>
</ul>
</details>
<br />

Updates `reqwest` from 0.12.11 to 0.12.12
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md">reqwest's
changelog</a>.</em></p>
<blockquote>
<h2>v0.12.12</h2>
<ul>
<li>(wasm) Fix compilation by not compiler <code>tokio/time</code> on
WASM.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="8b8fdd2552"><code>8b8fdd2</code></a>
v0.12.12</li>
<li><a
href="1ef87032c8"><code>1ef8703</code></a>
(wasm) fix: remove tower as dependency for wasm32-unknown-unknown (<a
href="https://redirect.github.com/seanmonstar/reqwest/issues/2510">#2510</a>)</li>
<li>See full diff in <a
href="https://github.com/seanmonstar/reqwest/compare/v0.12.11...v0.12.12">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-07 06:34:51 -05:00
Marc-Andre Lureau
a0d32d7245
fix: fix commit 9198284263 (#626)
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Changelog: ignore
2025-01-06 09:29:30 -05:00
dependabot[bot]
236a132120
build(deps): bump reqwest from 0.12.9 to 0.12.11 in the patch group (#627)
Bumps the patch group with 1 update:
[reqwest](https://github.com/seanmonstar/reqwest).

Updates `reqwest` from 0.12.9 to 0.12.11
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seanmonstar/reqwest/releases">reqwest's
releases</a>.</em></p>
<blockquote>
<h2>v0.12.11</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix decompression returning an error when HTTP/2 ends with an empty
data frame by <a
href="https://github.com/seanmonstar"><code>@​seanmonstar</code></a> in
<a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2508">seanmonstar/reqwest#2508</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seanmonstar/reqwest/compare/v0.12.10...v0.12.11">https://github.com/seanmonstar/reqwest/compare/v0.12.10...v0.12.11</a></p>
<h2>v0.12.10</h2>
<h2>What's Changed</h2>
<ul>
<li>Add <code>ClientBuilder::connector_layer()</code> to allow
customizing the connector stack. by <a
href="https://github.com/jlizen"><code>@​jlizen</code></a> in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2496">seanmonstar/reqwest#2496</a></li>
<li>Add <code>ClientBuilder::http2_max_header_list_size()</code> option
by <a href="https://github.com/DSharifi"><code>@​DSharifi</code></a> in
<a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2465">seanmonstar/reqwest#2465</a></li>
<li>Fix decompression of chunked bodies so the connections can be reused
more often by <a
href="https://github.com/Andrey36652"><code>@​Andrey36652</code></a> in
<a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2484">seanmonstar/reqwest#2484</a></li>
<li>Fix propagating body size hint (<code>content-length</code>)
information when wrapping bodies by <a
href="https://github.com/seanmonstar"><code>@​seanmonstar</code></a> in
<a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2503">seanmonstar/reqwest#2503</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/DSharifi"><code>@​DSharifi</code></a>
made their first contribution in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2465">seanmonstar/reqwest#2465</a></li>
<li><a
href="https://github.com/gretchenfrage"><code>@​gretchenfrage</code></a>
made their first contribution in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2464">seanmonstar/reqwest#2464</a></li>
<li><a href="https://github.com/hsivonen"><code>@​hsivonen</code></a>
made their first contribution in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2470">seanmonstar/reqwest#2470</a></li>
<li><a href="https://github.com/ovnicraft"><code>@​ovnicraft</code></a>
made their first contribution in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2469">seanmonstar/reqwest#2469</a></li>
<li><a href="https://github.com/Nuhvi"><code>@​Nuhvi</code></a> made
their first contribution in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2473">seanmonstar/reqwest#2473</a></li>
<li><a href="https://github.com/caojen"><code>@​caojen</code></a> made
their first contribution in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2488">seanmonstar/reqwest#2488</a></li>
<li><a
href="https://github.com/Andrey36652"><code>@​Andrey36652</code></a>
made their first contribution in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2484">seanmonstar/reqwest#2484</a></li>
<li><a href="https://github.com/jlizen"><code>@​jlizen</code></a> made
their first contribution in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2499">seanmonstar/reqwest#2499</a></li>
</ul>
<h2>Thanks</h2>
<ul>
<li><a
href="https://github.com/seanmonstar"><code>@​seanmonstar</code></a></li>
<li><a href="https://github.com/nyurik"><code>@​nyurik</code></a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seanmonstar/reqwest/compare/v0.12.9...v0.12.10">https://github.com/seanmonstar/reqwest/compare/v0.12.9...v0.12.10</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md">reqwest's
changelog</a>.</em></p>
<blockquote>
<h2>v0.12.11</h2>
<ul>
<li>Fix decompression returning an error when HTTP/2 ends with an empty
data frame.</li>
</ul>
<h2>v0.12.10</h2>
<ul>
<li>Add <code>ClientBuilder::connector_layer()</code> to allow
customizing the connector stack.</li>
<li>Add <code>ClientBuilder::http2_max_header_list_size()</code>
option.</li>
<li>Fix propagating body size hint (<code>content-length</code>)
information when wrapping bodies.</li>
<li>Fix decompression of chunked bodies so the connections can be reused
more often.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="224f0b89d8"><code>224f0b8</code></a>
v0.12.11</li>
<li><a
href="beea3320c4"><code>beea332</code></a>
fix decoding extra empty frame (<a
href="https://redirect.github.com/seanmonstar/reqwest/issues/2508">#2508</a>)</li>
<li><a
href="177cc7f8d9"><code>177cc7f</code></a>
cleanup: typo fix</li>
<li><a
href="409cff3cf7"><code>409cff3</code></a>
v0.12.10</li>
<li><a
href="ea48da723c"><code>ea48da7</code></a>
docs: fix a few spelling issues (<a
href="https://redirect.github.com/seanmonstar/reqwest/issues/2478">#2478</a>)</li>
<li><a
href="3ce98b5f22"><code>3ce98b5</code></a>
fix: propagate Body::size_hint when wrapping bodies (<a
href="https://redirect.github.com/seanmonstar/reqwest/issues/2503">#2503</a>)</li>
<li><a
href="44ca5ee864"><code>44ca5ee</code></a>
remove Clone from connect::Unnameable for now (<a
href="https://redirect.github.com/seanmonstar/reqwest/issues/2502">#2502</a>)</li>
<li><a
href="2a7c1b61e0"><code>2a7c1b6</code></a>
feat: allow pluggable tower layers in connector service stack (<a
href="https://redirect.github.com/seanmonstar/reqwest/issues/2496">#2496</a>)</li>
<li><a
href="8a2174f8a4"><code>8a2174f</code></a>
chore: in README, update requirements to mention rustls along with
vendored o...</li>
<li><a
href="d36c0f5fd9"><code>d36c0f5</code></a>
perf: fix decoder streams to make pooled connections reusable (<a
href="https://redirect.github.com/seanmonstar/reqwest/issues/2484">#2484</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/seanmonstar/reqwest/compare/v0.12.9...v0.12.11">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=reqwest&package-manager=cargo&previous-version=0.12.9&new-version=0.12.11)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-31 09:12:38 -05:00
dependabot[bot]
96d222c442
build(deps): bump the patch group with 4 updates (#625) 2024-12-24 03:41:15 -05:00
devolutionsbot
9292988a88
chore(release): prepare for publishing (#624)
Co-authored-by: Benoît Cortier <3809077+CBenoit@users.noreply.github.com>
2024-12-17 18:18:10 +00:00
Marc-Andre Lureau
9198284263
feat(server): make TlsIdentityCtx accept PEM files (#623)
This is in general more convenient than DER files.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-12-17 10:50:39 -05:00
devolutionsbot
114098b673
chore(release): prepare for publishing (#621) 2024-12-16 11:24:01 +00:00
Benoît Cortier
9757167df5
chore(release): prepare for publishing (#620)
- ironrdp-cliprdr-format
- ironrdp-futures
- ironrdp-rdcleanpath
- ironrdp-rdpdr-native
2024-12-16 08:24:47 +00:00
Benoît Cortier
cff5c1a59c
docs(ironrdp): inline documentation for re-exported items (#619) 2024-12-16 08:19:46 +00:00
Benoît Cortier
c01017fd41
chore(tools): update release-plz.toml (#618)
Pass `--no-verify` to `cargo publish` when publishing `*-native` crates.

`*-native` crates may have all kinds of system requirements depending
on the platform. We can only check for the current platform when cargo
publish is invoked, all the others are effectively unverified. For this
reason, it's not worth complexifying the release-crates.yml workflow. We
already verify all targets properly in the CI.
2024-12-15 20:43:11 -05:00
Benoît CORTIER
912c27cffe chore(release): prepare for publishing 2024-12-15 12:02:36 -05:00
Benoît CORTIER
52832598ec chore(tools): update git-cliff body template 2024-12-15 12:02:36 -05:00
Benoît CORTIER
d696c9b05c chore(tools): fix release-plz.toml configuration 2024-12-15 12:02:36 -05:00
Benoît Cortier
2139921c03
chore: update release-plz.toml configuration file (#617)
- Only create release PR for features, bug fixes, documentation updates,
  build and dependencies changes, and performance improvements.
- Only create GitHub releases for ironrdp and ironrdp-client crates.
- Enable the rustls feature flag when publishing ironrdp-tls, otherwise
  the compilation will errors out.
2024-12-15 08:17:23 -05:00
Benoît Cortier
02c6fd5dfe
docs(ironrdp): fix server example (#616)
The rt-multi-thread feature of tokio is not enabled when compiling the
example alone (without feature unification from other crates of the
workspace).
2024-12-15 08:17:04 -05:00
devolutionsbot
97ef9f0acb
chore(release): prepare for publishing (#611) 2024-12-14 14:32:28 +00:00
Benoît Cortier
bf26d6c108
ci(release-crates): the release branch is not fetched by default (#614) 2024-12-14 08:14:39 -05:00
Benoît Cortier
686553659e
ci(release-crates): also update fuzz/Cargo.lock (#613) 2024-12-13 22:57:11 -05:00
Benoît Cortier
a6b694b7b8
refactor(core): rename private macro in cursor module (#612)
This was causing a false-positive in cargo-semver-checks:

- https://github.com/obi1kenobi/cargo-semver-checks/issues/1042
2024-12-13 10:48:02 +02:00
Benoît Cortier
3650649914
ci(release-crates): fix the environment name (#610) 2024-12-12 12:59:22 -05:00
Benoît CORTIER
6c1bc32a09 chore: add release-plz.toml config file 2024-12-12 11:47:07 -05:00
Benoît CORTIER
8236148bbd ci(release-crates): fix usage of release-plz action 2024-12-12 11:47:07 -05:00
Benoît Cortier
50b848529c
ci(release-crates): use Devolutions/actions-public (#608) 2024-12-12 16:10:22 +00:00
Benoît Cortier
66590487c2
ci: use our custom action for release-plz (#607) 2024-12-12 15:23:49 +00:00
Benoît Cortier
a13149d5a1
ci: fix max-page-size=16384 for Android builds (#606) 2024-12-12 09:24:41 -05:00
Benoît Cortier
054f812f2e
ci: use the authorized version of release-plz (#605) 2024-12-11 14:22:34 +00:00
Benoît Cortier
d4ca10cdc2
ci: automatize crate publishing (#603) 2024-12-11 09:11:56 -05:00
Benoît Cortier
0c10367ebc
chore: symlinks to license files in packages (#604)
Add symlinks to the license files in crates that we are publishing on
crates.io.
2024-12-11 08:13:26 -05:00
Benoît Cortier
2d3bdffeb5
build(ffi): support 16 KB page sizes on Android (#602) 2024-12-10 09:26:44 -05:00
dependabot[bot]
fc4951780c
build(deps): bump the patch group across 1 directory with 11 updates (#601)
Bumps the patch group with 9 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [png](https://github.com/image-rs/image-png) | `0.17.14` | `0.17.15` |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.93` | `1.0.94` |
| [tracing-subscriber](https://github.com/tokio-rs/tracing) | `0.3.18` | `0.3.19` |
| [tokio-rustls](https://github.com/rustls/tokio-rustls) | `0.26.0` | `0.26.1` |
| [clap](https://github.com/clap-rs/clap) | `4.5.21` | `4.5.23` |
| [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) | `0.2.95` | `0.2.99` |
| [wasm-bindgen-futures](https://github.com/rustwasm/wasm-bindgen) | `0.4.45` | `0.4.49` |
| [chrono](https://github.com/chronotope/chrono) | `0.4.38` | `0.4.39` |
| [time](https://github.com/time-rs/time) | `0.3.36` | `0.3.37` |



Updates `png` from 0.17.14 to 0.17.15
- [Changelog](https://github.com/image-rs/image-png/blob/master/CHANGES.md)
- [Commits](https://github.com/image-rs/image-png/compare/v0.17.14...v0.17.15)

Updates `anyhow` from 1.0.93 to 1.0.94
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.93...1.0.94)

Updates `tracing-subscriber` from 0.3.18 to 0.3.19
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.18...tracing-subscriber-0.3.19)

Updates `tokio-rustls` from 0.26.0 to 0.26.1
- [Release notes](https://github.com/rustls/tokio-rustls/releases)
- [Commits](https://github.com/rustls/tokio-rustls/commits)

Updates `clap` from 4.5.21 to 4.5.23
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.21...clap_complete-v4.5.23)

Updates `wasm-bindgen` from 0.2.95 to 0.2.99
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/compare/0.2.95...0.2.99)

Updates `wasm-bindgen-futures` from 0.4.45 to 0.4.49
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

Updates `web-sys` from 0.3.72 to 0.3.76
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

Updates `js-sys` from 0.3.72 to 0.3.76
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

Updates `chrono` from 0.4.38 to 0.4.39
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.38...v0.4.39)

Updates `time` from 0.3.36 to 0.3.37
- [Release notes](https://github.com/time-rs/time/releases)
- [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md)
- [Commits](https://github.com/time-rs/time/compare/v0.3.36...v0.3.37)

---
updated-dependencies:
- dependency-name: png
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: tracing-subscriber
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: tokio-rustls
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: wasm-bindgen
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: wasm-bindgen-futures
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: web-sys
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: js-sys
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: chrono
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: time
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 22:42:52 -05:00
Benoît Cortier
2f57fd2de3
chore(release): prepare ironrdp-tokio 0.2.0 (#600) 2024-12-06 14:23:12 +00:00
Benoît Cortier
d1d13c8297
chore(release): bump ironrdp-connector (#599)
We need to release a patch which includes a newer version of
picky-asn1-x509.
2024-12-06 15:31:40 +02:00
Vladyslav Nikonov
4b1dbaa910
docs(now-proto): now-proto improvements (#596) 2024-12-06 13:33:46 +02:00
Benoît Cortier
755738ff9c
chore: prepare release (#598)
Crates to release:

- ironrdp-acceptor -> 0.2.0
- ironrdp-async -> 0.2.0
- ironrdp-blocking -> 0.2.0
- ironrdp-session -> 0.2.0
- ironrdp-server -> 0.3.0
- ironrdp -> 0.7.0
2024-12-06 09:51:14 +00:00
Przemko Robakowski
6e290ab366
fix(session): avoid panics when First fragmentation is missing (#597) 2024-12-04 23:11:53 -05:00
Marc-André Lureau
fe0d9e9773 example: replace bmp with image/png
bmp is a small crate, but it is not popular, and apparently
unmaintained. Use the ubiquitous image crate and the PNG format instead.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-11-29 05:31:05 -05:00
Marc-André Lureau
0ee5bfc561 fix(examples): fix screenshot deps
Set the required-features and use sspi with "network_client".

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-11-29 05:31:05 -05:00
Marc-André Lureau
20c899e464 chore: update sspi & picky to last release
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-11-29 05:31:05 -05:00
Marc-André Lureau
5c077f9e47 chore(ainput): update description
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-11-29 05:31:05 -05:00
dependabot[bot]
eb499de352
build(deps): bump url from 2.5.3 to 2.5.4 in the patch group (#590)
Bumps the patch group with 1 update: [url](https://github.com/servo/rust-url).


Updates `url` from 2.5.3 to 2.5.4
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/compare/v2.5.3...v2.5.4)

---
updated-dependencies:
- dependency-name: url
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 19:46:02 -05:00
dependabot[bot]
511b777ef6
build(deps): bump picky from 7.0.0-rc.9 to 7.0.0-rc.10 (#591)
Bumps [picky](https://github.com/Devolutions/picky-rs) from 7.0.0-rc.9 to 7.0.0-rc.10.
- [Changelog](https://github.com/Devolutions/picky-rs/blob/master/release.toml)
- [Commits](https://github.com/Devolutions/picky-rs/compare/picky-7.0.0-rc.9...picky-7.0.0-rc.10)

---
updated-dependencies:
- dependency-name: picky
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 19:41:07 -05:00
Benoît Cortier
267ef5ba3b
ci(nuget): fix aws-lc-rs build for iOS (#589) 2024-11-25 07:39:37 -05:00
Benoît Cortier
ea60c49b6b
chore: bump ironrdp-core version (#588) 2024-11-21 15:28:23 +00:00
Benoît Cortier
fc23992dea
chore: prepare release (#585)
Crates to release:

- ironrdp-core -> 0.1.1
- ironrdp-pdu -> 0.1.1
- ironrdp-svc -> 0.1.1
- ironrdp-connector -> 0.2.0
- ironrdp-server -> 0.2.0
- ironrdp -> 0.6.0
2024-11-21 10:14:01 -05:00
Benoît Cortier
2c1bec6496
fix(connector): do not send a Cookie when using a smart card (#586) 2024-11-21 10:04:29 -05:00
Benoît Cortier
2fe519da1a
docs: update ARCHITECTURE.md to mention ironrdp-core (#587) 2024-11-21 08:59:53 -06:00
Benoît CORTIER
49cba12c8b fix: add a few temporary, hidden re-exports for Teleport
Teleport is generating many errors when using the latest IronRDP crates.
This patch is re-exporting a few items from ironrdp_core so it’s
easier for them to incrementally migrate to the newer versions.
2024-11-21 22:54:57 +09:00
Benoît CORTIER
69eba11325 fix: macro hygiene
Some macros where not hygienic, requiring the user to have specific
items in scope.
2024-11-21 22:54:57 +09:00
Marc-Andre Lureau
2e59014c97
feat(server): add GetLocalAddr (#581)
Add a server event to lookup the actual server listen address.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-11-20 10:06:57 -05:00
Benoît Cortier
bab049aa00
docs(ironrdp): README.md dedicated to the meta crate (#582)
Using the top-level README.md of the repository is not ideal.

1/ It contains many information unrelated to the meta crate itself.

2/ The following attribute:

```
 #![doc = include_str!("../../../README.md")]
```

will fail at finding the README.md when built via `cargo package`.
2024-11-20 11:15:29 +00:00
Zac Bergquist
36da11c02e
feat(connector): allow clients to specify the x224 nego request data (#580)
The previous code would (correctly) set a cookie containining the
username, but only when using username/password credentials. When
smart card credentials are used, the cookie would always contain
the empty string.
2024-11-19 20:44:40 -05:00
Benoît CORTIER
294af1cc5c style: cargo +nightly fmt 2024-11-20 01:28:31 +09:00
Benoît CORTIER
807eb59b07 refactor: enable clippy::std_instead_of_core lint 2024-11-20 01:28:31 +09:00
Benoît CORTIER
d26e64e4c2 refactor: enable clippy::similar_names lint 2024-11-20 01:28:31 +09:00
Benoît CORTIER
7c7ca4dbd1 test(displaycontrol): doctest = false and test = false 2024-11-20 01:28:31 +09:00
Benoît CORTIER
d9b69c68f9 build: enable unused_crate_dependencies lint
All reported cases were actually unused dependencies.
2024-11-20 01:28:31 +09:00
Benoît CORTIER
9ec8f547f9 refactor: update workspace lints
This is based on the configuration we have in the Devolutions Gateway
workspace.
2024-11-20 01:28:31 +09:00
dependabot[bot]
5a7561bc19
build(deps): bump the patch group with 2 updates (#578)
Bumps the patch group with 2 updates: [clap](https://github.com/clap-rs/clap) and [xshell](https://github.com/matklad/xshell).


Updates `clap` from 4.5.20 to 4.5.21
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.20...clap_complete-v4.5.21)

Updates `xshell` from 0.2.6 to 0.2.7
- [Changelog](https://github.com/matklad/xshell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/xshell/compare/v0.2.6...v0.2.7)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: xshell
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 19:31:09 -05:00
dependabot[bot]
9cbac79596
build(deps): bump the patch group with 3 updates (#576)
Bumps the patch group with 3 updates: [anyhow](https://github.com/dtolnay/anyhow), [tokio](https://github.com/tokio-rs/tokio) and [arbitrary](https://github.com/rust-fuzz/arbitrary).


Updates `anyhow` from 1.0.92 to 1.0.93
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.92...1.0.93)

Updates `tokio` from 1.41.0 to 1.41.1
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.41.0...tokio-1.41.1)

Updates `arbitrary` from 1.4.0 to 1.4.1
- [Changelog](https://github.com/rust-fuzz/arbitrary/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-fuzz/arbitrary/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: arbitrary
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-11 18:57:17 -05:00
Vladyslav Nikonov
1cd5c4b7f3
docs(now-proto): add missing NOW-PROTO docs (#575) 2024-11-07 19:29:08 -05:00
dependabot[bot]
9470d2a7c2
build(deps): bump the patch group with 3 updates (#573)
Bumps the patch group with 3 updates: [anyhow](https://github.com/dtolnay/anyhow), [url](https://github.com/servo/rust-url) and [resize](https://github.com/PistonDevelopers/resize).


Updates `anyhow` from 1.0.91 to 1.0.92
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.91...1.0.92)

Updates `url` from 2.5.2 to 2.5.3
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/compare/v2.5.2...v2.5.3)

Updates `resize` from 0.8.7 to 0.8.8
- [Commits](https://github.com/PistonDevelopers/resize/commits/v0.8.8)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: url
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: resize
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-05 07:16:17 -05:00
Marc-André Lureau
2a4d357d27 perf(graphics): help Rust to inline iter_to_ycbcr with format
rgb2yuv                 time:   [11.706 µs 11.716 µs 11.727 µs]
                        change: [-24.083% -23.682% -23.394%] (p = 0.00 < 0.05)

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-11-05 20:16:43 +09:00
Marc-André Lureau
d0bacffc27 refactor(graphics): use an ExactSizeIterator for iter_to_ycbcr
Unfortunately, that doesn't seem to help unrolling & vectorizing: no
perf improvements.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-11-05 20:16:43 +09:00
Marc-André Lureau
4583de2c42 refactor(graphics): make sure Rust uses const YUV matrix values
Apparently it already did, I do not observe perf improvements.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-11-05 20:16:43 +09:00
Marc-André Lureau
de67e58dd5 perf(server): make tiles encoding parallel with rayon
This can help a lot wall-clock time, but depends on CPU.

rfx_enc                 time:   [9.7885 ms 10.123 ms 10.439 ms]
                        change: [-80.484% -79.847% -79.208%] (p = 0.00 < 0.05)
                        Performance has improved.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-11-05 20:16:43 +09:00
Marc-André Lureau
58afe285c2 refactor(graphics): const pixel_format_to_rgb_fn
That doesn't change the speed though, code isn't inlined afaict.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-11-05 20:16:43 +09:00
Marc-André Lureau
f12e6aa827 perf(graphics): use const generics for DWT
That seems to speed up a bit the code:
rfxenc                  time:   [46.040 µs 46.288 µs 46.698 µs]
                        change: [-9.2580% -8.6663% -7.8304%] (p = 0.00 < 0.05)
                        Performance has improved.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-11-05 20:16:43 +09:00
Marc-André Lureau
6dfd659099 refactor(graphics): use fixed-size slices in to_64x64_ycbcr_tile
In theory, this could help the compiler to unroll loops.. doesn't seem
to be the case though, but it allows to drop the assert_eq!() at least.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-11-05 20:16:43 +09:00
Marc-André Lureau
21d8ce38b1 feat(bench): benchmark rgb2yuv tile encoding
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-11-05 20:16:43 +09:00
Marc-André Lureau
6e567d401f feat(bench): benchmark the remotefx encoder
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-11-05 20:16:43 +09:00
Marc-André Lureau
ff81e7502c refactor(server): factor out remotefx tile encoding
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-11-05 20:16:43 +09:00
Marc-André Lureau
631964d615 feat(server): warn if encoding takes >10ms
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-11-05 20:16:43 +09:00
dependabot[bot]
58f31b88e0
build(deps): bump arbitrary from 1.3.2 to 1.4.0 (#574)
Bumps [arbitrary](https://github.com/rust-fuzz/arbitrary) from 1.3.2 to 1.4.0.
- [Changelog](https://github.com/rust-fuzz/arbitrary/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-fuzz/arbitrary/compare/v1.3.2...v1.4.0)

---
updated-dependencies:
- dependency-name: arbitrary
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-05 04:10:32 -05:00
Benoît Cortier
5831547811
build(ffi): enable required feature from ironrdp (#572)
This feature used to be enabled by default, but it's not anymore.
2024-11-04 18:27:55 +00:00
Marc-Andre Lureau
37cecc0a16
fix(server): pick one selected protocol (#570)
The spec says we should have "the" selected protocol, and mstsc expects that.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-11-04 13:50:07 +00:00
Marc-Andre Lureau
3e738a96ed
feat(server): add builder option to set remotefx (#569)
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-31 14:16:03 +00:00
Marc-André Lureau
5ea39d05af fix(acceptor): raise a credssp error when no creds
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-30 15:48:24 +09:00
Marc-André Lureau
e8d362d5ea chore: reference readme in workspace.package
Some tools require it.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-30 15:48:24 +09:00
Marc-André Lureau
3c503cb2d1 chore: change "default" feature to only enable "core" and "pdu"
Reorganize a bit the dev-dependencies for screenshot/server examples.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-29 21:48:11 +09:00
Marc-André Lureau
d1b95676f0 docs(example/server): use server TlsIdentityCtx
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-29 21:48:11 +09:00
Marc-André Lureau
7dd1787c52 feat(server): add a "helper" feature for TlsIdentityCtx
This snippet is useful for server implementations, as long as the server
must be configured with a tokio-rustls TlsAcceptor, and not directly
with certificate paths for examples.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-29 21:48:11 +09:00
Marc-André Lureau
a9356fc57b docs(example/server): refactor acceptor() to be a TlsIdentityCtx method
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-29 21:48:11 +09:00
Marc-André Lureau
5381b24444 docs: use Devolutions logo
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-29 19:14:20 +09:00
Marc-André Lureau
89c7549701 docs: add ironrdp docs.rs & crates.io badges
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-29 19:14:20 +09:00
Marc-André Lureau
3a1aeedb55 docs(rdpdr): fix rustdoc warnings
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-29 19:14:20 +09:00
Marc-André Lureau
a096b14488 docs(svc): fix rustdoc warnings
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-29 19:14:20 +09:00
Marc-André Lureau
c609fab780 docs(pdu): fix invalid rust syntax
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-29 19:14:20 +09:00
Marc-André Lureau
4e962431d2 docs(dvc): fix rustdoc warnings
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-29 19:14:20 +09:00
Marc-André Lureau
5338ea015c docs: fix rustdoc warning with bare URL
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-29 19:14:20 +09:00
Marc-André Lureau
bf56a7fc80 docs: use README.md for crates lib.rs doc
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-29 19:14:20 +09:00
Marc-André Lureau
7bf699cdcd docs(ironrdp): use absolute URLs
rustdoc can't resolve link outside of its generated directory.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-29 19:14:20 +09:00
Marc-André Lureau
8d15f0bca9 chore(ironrdp): use workspace README.md
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-29 19:14:20 +09:00
Marc-André Lureau
4ef36bf5fa docs: add project links to READMEs for consistency
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-29 19:14:20 +09:00
Marc-André Lureau
f55e82f02e chore: add missing README.md files
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-29 19:14:20 +09:00
Richard Markiewicz
5e5e1aa217
ci: update macos-12 runners to macos-14 (#566) 2024-10-29 04:51:16 -04:00
dependabot[bot]
876a47dd99
build(deps): bump the patch group with 2 updates (#567)
Bumps the patch group with 2 updates: [sspi](https://github.com/devolutions/sspi-rs) and [reqwest](https://github.com/seanmonstar/reqwest).


Updates `sspi` from 0.14.1 to 0.14.2
- [Commits](https://github.com/devolutions/sspi-rs/commits)

Updates `reqwest` from 0.12.8 to 0.12.9
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.12.8...v0.12.9)

---
updated-dependencies:
- dependency-name: sspi
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-29 04:50:49 -04:00
Marc-Andre Lureau
87014d4afb
fix(acceptor): raise credssp error to caller (#563)
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Co-authored-by: Benoît Cortier <bcortier@proton.me>
2024-10-28 12:19:33 +00:00
Benoît CORTIER
42cc02d6f6 docs(server): slightly adjust comments 2024-10-25 16:04:07 +09:00
Benoît CORTIER
c1a7c4de8c build: remove dead_code lint from our manifest
This lint is already warn-by-default.
2024-10-25 16:04:07 +09:00
Benoît CORTIER
26a12a69c7 build(deps): remove patch number in sspi dependency
This is to avoid overconstraining in the future.
2024-10-25 16:04:07 +09:00
Benoît CORTIER
4a7f233725 docs(example/server): fix default value for the bind address
Replaced --host and --port by a single --bind-addr parameter with
a default value which does not cause a runtime error.
2024-10-25 16:04:07 +09:00
Benoît CORTIER
59c2dc4675 docs(example/server): specify required features 2024-10-25 16:04:07 +09:00
Benoît CORTIER
2a2b555a11 docs(example/server): refactor for clarity 2024-10-25 16:04:07 +09:00
Marc-André Lureau
f5dd282271 chore: bump sspi to 0.14.1
The upcoming sspi release will be required to support Hybrid/CredSsp
server security.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-25 13:19:58 +09:00
Marc-André Lureau
b9db9ea645 feat(example): add hybrid security to server example
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-25 13:19:58 +09:00
Marc-André Lureau
8fc30cb22e feat(server): add hybrid security support
Add a RdpServerSecurity::Hybrid variant, to support both hybrid +
hybrid-ex, since they are very close / similar and I don't see much
point in selecting one without the other at this point.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-25 13:19:58 +09:00
Marc-André Lureau
4c4d93bc6f feat(acceptor): add CredSSP support
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-25 13:19:58 +09:00
Marc-André Lureau
ac24e15a3d feat(example): add user/pass to server
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-25 13:19:58 +09:00
Marc-André Lureau
86bc10bf95 feat(server): allow setting credentials
Modify Acceptor to take optionally Credentials at construct time.

The credentials can be changed via ServerEvent::SetCredentials.

Unauthorized connections are no longer accepted.

Note that I couldn't find a nice way to return an invalid logon with RDP
standard security. The next commits will add HYBRID support to improve
the situation.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-25 13:19:58 +09:00
Marc-André Lureau
aa8c34edf4 fix(server): add missing tokio rt feature
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-25 13:19:58 +09:00
Marc-André Lureau
5e1cd31c4c chore(connector): drop old winapi dependency
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-25 13:19:58 +09:00
Marc-André Lureau
bb1860d153 build: update Rust toolchain to 1.82.0
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-10-25 13:19:58 +09:00
dependabot[bot]
069e4345a1
build(deps): bump anyhow from 1.0.89 to 1.0.90 in the patch group (#560)
Bumps the patch group with 1 update: [anyhow](https://github.com/dtolnay/anyhow).


Updates `anyhow` from 1.0.89 to 1.0.90
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.89...1.0.90)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-22 01:23:49 -04:00
dependabot[bot]
70b83799f2
build(deps): bump bytes from 1.7.2 to 1.8.0 (#561)
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.7.2 to 1.8.0.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.7.2...v1.8.0)

---
updated-dependencies:
- dependency-name: bytes
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-22 01:23:36 -04:00
dependabot[bot]
6e1b00ee3e
build(deps): bump the patch group with 5 updates (#559)
Bumps the patch group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [clap](https://github.com/clap-rs/clap) | `4.5.19` | `4.5.20` |
| [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) | `0.2.93` | `0.2.95` |
| [wasm-bindgen-futures](https://github.com/rustwasm/wasm-bindgen) | `0.4.43` | `0.4.45` |
| [web-sys](https://github.com/rustwasm/wasm-bindgen) | `0.3.70` | `0.3.72` |
| [js-sys](https://github.com/rustwasm/wasm-bindgen) | `0.3.70` | `0.3.72` |


Updates `clap` from 4.5.19 to 4.5.20
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.19...clap_complete-v4.5.20)

Updates `wasm-bindgen` from 0.2.93 to 0.2.95
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/compare/0.2.93...0.2.95)

Updates `wasm-bindgen-futures` from 0.4.43 to 0.4.45
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

Updates `web-sys` from 0.3.70 to 0.3.72
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

Updates `js-sys` from 0.3.70 to 0.3.72
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: wasm-bindgen
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: wasm-bindgen-futures
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: web-sys
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: js-sys
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-15 00:35:48 -04:00
Vladyslav Nikonov
7c268d8630
fix(now-proto): expose NowProto::decode_from_body (#557) 2024-10-08 13:50:24 +00:00
dependabot[bot]
ef1be931c8
build(deps): bump the patch group with 12 updates (#556)
* build(deps): bump the patch group with 12 updates

Bumps the patch group with 12 updates:

| Package | From | To |
| --- | --- | --- |
| [png](https://github.com/image-rs/image-png) | `0.17.13` | `0.17.14` |
| [thiserror](https://github.com/dtolnay/thiserror) | `1.0.63` | `1.0.64` |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.86` | `1.0.89` |
| [async-trait](https://github.com/dtolnay/async-trait) | `0.1.82` | `0.1.83` |
| [bytes](https://github.com/tokio-rs/bytes) | `1.7.1` | `1.7.2` |
| [softbuffer](https://github.com/rust-windowing/softbuffer) | `0.4.5` | `0.4.6` |
| [clap](https://github.com/clap-rs/clap) | `4.5.16` | `4.5.19` |
| [whoami](https://github.com/ardaku/whoami) | `1.5.1` | `1.5.2` |
| [reqwest](https://github.com/seanmonstar/reqwest) | `0.12.7` | `0.12.8` |
| [futures-util](https://github.com/rust-lang/futures-rs) | `0.3.30` | `0.3.31` |
| [pretty_assertions](https://github.com/rust-pretty-assertions/rust-pretty-assertions) | `1.4.0` | `1.4.1` |
| [futures-channel](https://github.com/rust-lang/futures-rs) | `0.3.30` | `0.3.31` |


Updates `png` from 0.17.13 to 0.17.14
- [Changelog](https://github.com/image-rs/image-png/blob/master/CHANGES.md)
- [Commits](https://github.com/image-rs/image-png/compare/v0.17.13...v0.17.14)

Updates `thiserror` from 1.0.63 to 1.0.64
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.63...1.0.64)

Updates `anyhow` from 1.0.86 to 1.0.89
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.86...1.0.89)

Updates `async-trait` from 0.1.82 to 0.1.83
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.82...0.1.83)

Updates `bytes` from 1.7.1 to 1.7.2
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.7.1...v1.7.2)

Updates `softbuffer` from 0.4.5 to 0.4.6
- [Release notes](https://github.com/rust-windowing/softbuffer/releases)
- [Changelog](https://github.com/rust-windowing/softbuffer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-windowing/softbuffer/compare/v0.4.5...v0.4.6)

Updates `clap` from 4.5.16 to 4.5.19
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.16...clap_complete-v4.5.19)

Updates `whoami` from 1.5.1 to 1.5.2
- [Changelog](https://github.com/ardaku/whoami/blob/v1/CHANGELOG.md)
- [Commits](https://github.com/ardaku/whoami/compare/v1.5.1...v1.5.2)

Updates `reqwest` from 0.12.7 to 0.12.8
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.12.7...v0.12.8)

Updates `futures-util` from 0.3.30 to 0.3.31
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.30...0.3.31)

Updates `pretty_assertions` from 1.4.0 to 1.4.1
- [Release notes](https://github.com/rust-pretty-assertions/rust-pretty-assertions/releases)
- [Changelog](https://github.com/rust-pretty-assertions/rust-pretty-assertions/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-pretty-assertions/rust-pretty-assertions/compare/v1.4.0...v1.4.1)

Updates `futures-channel` from 0.3.30 to 0.3.31
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.30...0.3.31)

---
updated-dependencies:
- dependency-name: png
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: bytes
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: softbuffer
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: whoami
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: futures-util
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: pretty_assertions
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: futures-channel
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update crates/ironrdp-rdpsnd-native/Cargo.toml

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Benoît Cortier <bcortier@proton.me>
2024-10-08 06:42:16 +00:00
dependabot[bot]
0faebe13fe
build(deps): bump rustls-pemfile from 2.1.3 to 2.2.0 in the crypto group (#553)
Bumps the crypto group with 1 update: [rustls-pemfile](https://github.com/rustls/pemfile).


Updates `rustls-pemfile` from 2.1.3 to 2.2.0
- [Release notes](https://github.com/rustls/pemfile/releases)
- [Commits](https://github.com/rustls/pemfile/compare/v/2.1.3...v/2.2.0)

---
updated-dependencies:
- dependency-name: rustls-pemfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: crypto
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-01 08:11:36 -04:00
dependabot[bot]
f953889b72
build(deps): bump embed-resource from 2.4.3 to 2.5.0 (#555)
Bumps [embed-resource](https://github.com/nabijaczleweli/rust-embed-resource) from 2.4.3 to 2.5.0.
- [Release notes](https://github.com/nabijaczleweli/rust-embed-resource/releases)
- [Commits](https://github.com/nabijaczleweli/rust-embed-resource/compare/v2.4.3...v2.5.0)

---
updated-dependencies:
- dependency-name: embed-resource
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-01 05:24:40 -04:00
Benoît Cortier
a002057016
chore: update .NET bindings (#551) 2024-09-23 05:46:33 -04:00
Marc-André Lureau
c5a4abd112 perf(server): run server tasks concurrently
Instead of using tokio::select!() and following one branch at a time to
process one events, use multiple loop that process events independently.
This way, we can be at the same time reading and processing a PDU from
the client, while encoding display update and writing a Wave.

This greatly improves responsivness and usability of qemu-rdp.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-09-23 02:25:59 -04:00
Marc-André Lureau
648f73c995 refactor(server): lower function requirements
Prepare for the next patch, and take "impl FramedWrite" rather than a
Framed<W> for the various dispatch methods.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-09-23 02:25:59 -04:00
Marc-André Lureau
fa10362106 refactor(async): impl FramedWrite for Frame
This allows to specialize functions that want to simply write_all() with
a Framed.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-09-23 02:25:59 -04:00
Marc-André Lureau
3c6b2ef2e2 feat(server): move the encoder to blocking task
Since it is a CPU intesive task, we should spawn a task to avoid
blocking other async tasks.

The UpdateFragmenterOwned is quite a gross hack to allow returning the result
from a the task lifetime. I don't know how to accomplish this better, or
else we have to add some Arc<Mutex> stuff.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-09-23 02:25:59 -04:00
Marc-André Lureau
b7164ecc68 refactor(server): split read and write half
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-09-23 02:25:59 -04:00
Marc-André Lureau
9384723179 refactor(client): split read and write half
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-09-23 02:25:59 -04:00
Marc-André Lureau
303315c168 refactor: make reader/writer functions specific
No functional change.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-09-23 02:25:59 -04:00
Marc-André Lureau
649613877e feat(tokio): add {split, unsplit}_tokio_framed()
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-09-23 02:25:59 -04:00
Zac Bergquist
b55924ee0a
fix: save the disconnect reason from the set error info PDU (#547)
The Set Error Info PDU is sent as a precursor to a server-side
disconnect and informs the client of the reason for the disconnection
which will follow. Once this PDU has been processed, the client MUST
store the error code so that the reason for the server disconnect
which will follow can be accurately reported to the user.
2024-09-23 01:15:42 -04:00
irvingouj @ Devolutions
c04bc2d29c
feat(web): allow dynamic resize for web (#550) 2024-09-19 19:08:45 +00:00
dependabot[bot]
3d3d9f2c56
build(deps): bump the patch group with 3 updates (#544)
Bumps the patch group with 3 updates: [async-trait](https://github.com/dtolnay/async-trait), [resize](https://github.com/PistonDevelopers/resize) and [rgb](https://github.com/kornelski/rust-rgb).


Updates `async-trait` from 0.1.81 to 0.1.82
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.81...0.1.82)

Updates `resize` from 0.8.6 to 0.8.7
- [Commits](https://github.com/PistonDevelopers/resize/commits)

Updates `rgb` from 0.8.48 to 0.8.50
- [Changelog](https://github.com/kornelski/rust-rgb/blob/v0.8.50/CHANGELOG.md)
- [Commits](https://github.com/kornelski/rust-rgb/compare/v0.8.48...v0.8.50)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: resize
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: rgb
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-03 06:50:33 -04:00
Marc-André Lureau
880d5012e6 fix(pdu): Error occurred after refactor(pdu) #541
Fixes commit 7419467ad3 ("refactor(core): move cursor.rs")

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-09-03 06:48:31 -04:00
Marc-André Lureau
ba49456236 fix(server): fix unnecessary qualification
705 |             rdp::headers::ShareControlPdu::Data(header) => match header.share_data_pdu {
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: requested on the command line with `-W unused-qualifications`
help: remove the unnecessary path segments
    |
705 -             rdp::headers::ShareControlPdu::Data(header) => match header.share_data_pdu {
705 +             ShareControlPdu::Data(header) => match header.share_data_pdu {

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-09-03 06:48:31 -04:00
dependabot[bot]
158e78ef04
build(deps): bump tokio from 1.39.3 to 1.40.0 (#545)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.39.3 to 1.40.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.39.3...tokio-1.40.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-03 06:47:44 -04:00
logeable
7916997b0b
style: fix typos (#546) 2024-09-03 10:36:47 +00:00
Marc-André Lureau
402ffd56c9 refactor(core): move Encode/Decode to core
ironrdp-pdu contains lots of code that we don’t actually need in other crates such as the virtual channels.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
ab5760d47b refactor(pdu): add X224 newtype
This allows to implement external Encode/Decode traits in following change.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
fda9530ef6 refactor(pdu): use a newtype for Option<SystemTime>
This allows to implement the external Encode/Decode traits.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
23bc008d65 refactor(core): move {Decode/Encode}Error
& document the public API.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
c49f190d29 refactor(pdu): drop unused RdpError
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
1ef9dd3f37 refactor(pdu): rename PduEncode->Encode PduDecode->Decode
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
b4c4b7ef58 chore(pdu): introduce DecodeResult
Introduce a new error type to split encoding/decoding errors as well as
helper traits and functions to ease porting and conventions.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
f1c3f7aa60 refactor(pdu): remove PduError::Custom
It's similar to PduError::Other, except it has an error source.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
80e8d1b257 refactor(server): factorize RfxEncoder::encode() code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
b73a0a88c3 refactor(pdu): rename UnsupportedPdu->UnsupportedValue
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
00d4750e4b refactor(pdu): rename InvalidMessage->InvalidField
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
b495704455 refactor(now-proto): clean unexpected_message_kind_err
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
f6a45ca24b refactor(error): remove CatchAllKind
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
278a0506c2 refactor(core): move WriteBuf
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
76b0518afa refactor(core): move IntoOwned
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
7419467ad3 refactor(core): move cursor.rs
Add documentation.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
4154ceea05 refactor(core): move impl_as_any macros
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
40cd8405f2 refactor(core): move assert_*!() macros
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Marc-André Lureau
fb8f12a62e refactor: add ironrdp-core
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-30 00:05:16 -04:00
Benoît Cortier
703b245993
ci(nuget-publish): downgrade to NDK 26.x (#540) 2024-08-29 07:41:30 -04:00
dependabot[bot]
5357a462cc
build(deps): bump resize from 0.8.5 to 0.8.6 in the patch group (#536)
Bumps the patch group with 1 update: [resize](https://github.com/PistonDevelopers/resize).


Updates `resize` from 0.8.5 to 0.8.6
- [Commits](https://github.com/PistonDevelopers/resize/compare/v0.8.5...v0.8.6)

---
updated-dependencies:
- dependency-name: resize
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-26 20:50:07 -04:00
dependabot[bot]
af7deae70a
build(deps): bump the patch group with 7 updates (#534)
Bumps the patch group with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [tokio](https://github.com/tokio-rs/tokio) | `1.39.2` | `1.39.3` |
| [clap](https://github.com/clap-rs/clap) | `4.5.15` | `4.5.16` |
| [reqwest](https://github.com/seanmonstar/reqwest) | `0.12.5` | `0.12.7` |
| [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) | `0.2.92` | `0.2.93` |
| [wasm-bindgen-futures](https://github.com/rustwasm/wasm-bindgen) | `0.4.42` | `0.4.43` |
| [web-sys](https://github.com/rustwasm/wasm-bindgen) | `0.3.69` | `0.3.70` |
| [js-sys](https://github.com/rustwasm/wasm-bindgen) | `0.3.69` | `0.3.70` |


Updates `tokio` from 1.39.2 to 1.39.3
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.39.2...tokio-1.39.3)

Updates `clap` from 4.5.15 to 4.5.16
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.15...clap_complete-v4.5.16)

Updates `reqwest` from 0.12.5 to 0.12.7
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.12.5...v0.12.7)

Updates `wasm-bindgen` from 0.2.92 to 0.2.93
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/compare/0.2.92...0.2.93)

Updates `wasm-bindgen-futures` from 0.4.42 to 0.4.43
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

Updates `web-sys` from 0.3.69 to 0.3.70
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

Updates `js-sys` from 0.3.69 to 0.3.70
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: wasm-bindgen
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: wasm-bindgen-futures
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: web-sys
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: js-sys
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-19 19:58:39 -04:00
Marc-André Lureau
09ae0d043d feat(server): collect and postpone incoming PDUs during reactivation
The client may have pending messages while the activation-reactivation
sequence is ongoing. Let's collect them in this case and restore them
after successfull reconnection.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-19 06:44:15 -04:00
Marc-André Lureau
f8c0c0ed47 feat(blocking): teach single_sequence_step() to keep unmatched PDUs
The caller can gather the unmatching/unexpected PDUs as necessary.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-19 06:44:15 -04:00
Marc-André Lureau
e54fa5f4c8 feat(async): teach single_sequence_step() to keep unmatched PDUs
The caller can gather the unmatching/unexpected PDUs as necessary.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-19 06:44:15 -04:00
Marc-André Lureau
6f779406e6 refactor(blocking): let read_by_hint() optionally accumulate unmatched bytes
The caller can then decide what to do.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-19 06:44:15 -04:00
Marc-André Lureau
98e7dbab99 refactor(async): let read_by_hint() optionally accumulate unmatched bytes
The caller can then decide what to do.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-19 06:44:15 -04:00
Marc-André Lureau
46b703e813 refactor(pdu): return whether hint::find_size() matches the expected hint
When compiled in debug mode, the code checks the expected Action hint.
But in release mode, no checks are done and the it will have to fail
later.

Instead, return whether the PDU is matching the hint, so the caller can
decide what to do in this case.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-19 06:44:15 -04:00
Marc-André Lureau
d8f2d10558 fix(server): add BitmapUpdate stride support follow-up
Fixes a73e0b7870

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-19 06:44:15 -04:00
Marc-André Lureau
8843e11b55 fix(server): error early for bitmap encoding if width % 4 != 0
This is not supported yet, and I am not sure how to do it atm.

Generally, server uses multiple of 4 widths, and client has surface
capabilities, so this path is unlikely.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-19 06:44:15 -04:00
Marc-André Lureau
d082d029a2 feat(pdu): check scan_width during encoding too
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-19 06:44:15 -04:00
Marc-André Lureau
7646c3cddb chore(web): silence rust-analyzer warnings
#[wasm_bindgen] creates annoying warnings, such as:

 rust-analyzer: Function `__wasm_bindgen_generated_Session_apply_inputs` should have snake_case name, e.g. `__wasm_bindgen_generated_session_apply_inputs`

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-19 06:44:15 -04:00
Marc-André Lureau
676522d33f fix(glutin-renderer): fix missing ;
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-19 06:44:15 -04:00
Sandeep Bansal
d8e21b0bd4
fix!: remove dependency on ClientConnector in CredsspSequence (#519) 2024-08-16 13:09:02 -04:00
Benoît Cortier
a6d6c2728b
refactor: follow up to PR 530 (#531) 2024-08-14 07:30:51 -04:00
Marc-André Lureau
a73e0b7870 feat(server): add BitmapUpdate stride support
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-14 00:37:06 -04:00
Marc-André Lureau
c73330b491 fix(server): fix rfx channel dimensions
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-14 00:37:06 -04:00
Marc-André Lureau
aac0740d52 refactor(pdu): impl Debuf for ExtendedBitmapDataPdu
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-14 00:37:06 -04:00
Marc-André Lureau
36a08292d5 feat(server): add DisplayUpdate::Resize event
Trigger a deactivation-reactivation sequence to handle desktop resize.

dfc234ce-481a-4674-9a5d-2a7bafb14432

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-14 00:37:06 -04:00
Marc-André Lureau
2a7e0d9d7e feat(acceptor): add new_deactivation_reactivation() constructor
Teach the acceptor to resume from the CapabilitiesSendServer state.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-14 00:37:06 -04:00
Marc-André Lureau
1df06f43c6 refactor(server): factorize out attaching channels
So the function can be reused for deactivation-reactivation sequence.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-14 00:37:06 -04:00
Marc-André Lureau
2c036c4217 refactor(server): introduce RunState
Use an explicit enum to return the client loop/run state from the
handlers. This will allow to return a DeactivationReactivation sequence
state from the display handler next.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-14 00:37:06 -04:00
Marc-André Lureau
ba97d5ceb5 refactor(server): report error!() at run_connection() level
Instead of reporting the error in various places, move error reporting
code to one place and simplify a bit the code thanks for anyhow::Context
and Result handling.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-14 00:37:06 -04:00
Marc-André Lureau
194763fd6d refactor(server): move finalize phase
Move finalize phase to a separate function, so we can introduce common
code for looping next, with deactivation-reactivation sequence. Also use
anyhow::Context instead of manually handling errors.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-14 00:37:06 -04:00
Marc-André Lureau
e0657b07d2 feat(displaycontrol): add DisplayControlHandler
Teach the server to call a DisplayControlHandler to
notify of monitor layout requests.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-14 00:37:06 -04:00
Marc-André Lureau
757b50941c refactor(server): manually implement Debug for RGBAPointer
So we can skip dumping the "data" field.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-14 00:37:06 -04:00
Marc-André Lureau
6e18a4c475 chore(session): lower image events logging level
"debug" level is a bit too verbose here.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-14 00:37:06 -04:00
Marc-André Lureau
ee30a72d27 feat(client): redraw on request
This is the recommended way to handle redraw.

Furthermore, it now draws something when needed, like on resize.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-13 03:19:10 -04:00
Marc-André Lureau
31e36a1364 fix(client): delay by 1s second before sending resize
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-13 03:19:10 -04:00
Marc-André Lureau
b762c04274 fix(client): do not trash all pending events on resize
The code is bad, as it may lose other kind of events.

Instead, the client should filter/throttle sending resize events.

Also forward all client events details.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-13 03:19:10 -04:00
dependabot[bot]
2245440cdd
build(deps): bump the patch group with 3 updates (#528)
Bumps the patch group with 3 updates: [winit](https://github.com/rust-windowing/winit), [clap](https://github.com/clap-rs/clap) and [rgb](https://github.com/kornelski/rust-rgb).


Updates `winit` from 0.30.4 to 0.30.5
- [Release notes](https://github.com/rust-windowing/winit/releases)
- [Changelog](https://github.com/rust-windowing/winit/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-windowing/winit/compare/v0.30.4...v0.30.5)

Updates `clap` from 4.5.13 to 4.5.15
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.13...clap_complete-v4.5.15)

Updates `rgb` from 0.8.47 to 0.8.48
- [Changelog](https://github.com/kornelski/rust-rgb/blob/v0.8.48/CHANGELOG.md)
- [Commits](https://github.com/kornelski/rust-rgb/compare/v0.8.47...v0.8.48)

---
updated-dependencies:
- dependency-name: winit
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: rgb
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-13 02:10:30 -04:00
Benoît Cortier
cd4bf18f6d
ci(nuget-publish): fix build error caused by aws-lc (#526) 2024-08-12 07:48:59 -04:00
Benoît Cortier
0f238097e0 refactor: follow-up review for PR 523 2024-08-11 22:26:02 -04:00
Marc-André Lureau
c97b1f90bc chore(web): bump softbuffer to 0.4.5
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-11 22:26:02 -04:00
Marc-André Lureau
7a6173ab40 refactor(client): drop GuiContext, use App directly
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-11 22:26:02 -04:00
Marc-André Lureau
64e4437491 refactor(client): replace deprecated Loop::create_window()
It should now be done from an ActiveEventLoop, during ApplicationHandler
callbacks, like resumed().

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-11 22:26:02 -04:00
Marc-André Lureau
4417e62fc6 refactor(client): replace deprecated run() with run_app()
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-11 22:26:02 -04:00
Marc-André Lureau
c4bf76c33e refactor(client): bump to winit 0.30
This solves broken/crashing display on wayland with HiDPI.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-11 22:26:02 -04:00
Marc-André Lureau
1a878d950a chore(deps): use common workspace windows version
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-11 22:26:02 -04:00
Marc-André Lureau
cfd60a1412 refactor(cliprdr-native): bump windows dep to 0.58
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-11 22:26:02 -04:00
Marc-André Lureau
8f14abdf1e feat(cliprdr-native): drop need for win32 HWND instance
Create a hidden window instead. This allows for easier setup of
clipboard channels while the GUI may not be ready yet.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-11 22:26:02 -04:00
Marc-André Lureau
cc7ab39d1f fix(graphics): allow empty TS_COLORPOINTERATTRIBUTE andMaskData
Providing the andMask is not mandatory (71fad4fc-6ad4-4c7f-8103-a442bebaf7d2)

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-11 22:26:02 -04:00
Marc-Andre Lureau
d5f069cfd1
fix(client): let address resolve to either ipv4 or ipv6 (#524)
Let TcpStream::connect() handle address resolution, so that multiple
addresses (typically localhost -> ipv4 127.0.0.1 or ipv6 ::1) are
attempted.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-09 04:42:31 -04:00
dependabot[bot]
e58aa7111d
build(deps): bump gloo-net from 0.5.0 to 0.6.0 (#520) 2024-08-06 00:14:21 -04:00
Przemko Robakowski
92efe2adf7
feat(connector): allow smart card logins with CredSSP enabled (#513) 2024-08-05 21:05:26 +03:00
Benoît Cortier
422f79eafe
ci: add a slack notification on failed, scheduled runs (#515) 2024-08-05 11:30:41 -04:00
Marc-Andre Lureau
43157f64e9
RFC: feat(server): flush server events, avoid audio buffer filling up (#518)
When display updates and server events are competing, tokio::select!()
will randomly pick one branch and process one event.

This is good for fairness, except when the display updates are big, and
delay other messages for example wave data. We may end up in a situation
where the server events queue grows and audio data gets delayed.

To avoid this situation, let's flush all server events and drop audio
frames when they are queuing up.

This introduce some bias for server events, since all pending server
events will be processed before other updates/inputs.

There are many different ways to tackle the problem, this simple
approach seems to work fine atm.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-05 14:52:21 +00:00
Marc-André Lureau
902b5b392d refactor(server): refactor to split fn client_loop()
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-05 10:24:45 -04:00
Marc-André Lureau
9171cd7083 refactor(server): refactor to fn dispatch_server_event()
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-05 10:24:45 -04:00
Marc-André Lureau
c53b88ebac refactor(server): refactor to fn dispatch_display_update()
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-05 10:24:45 -04:00
Marc-André Lureau
da53d45b44 refactor(server): refactor to fn dispatch_pdu()
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-05 10:24:45 -04:00
Marc-André Lureau
3aaef7e484 fix(error): fix unnecessary qualification (again)
This time with a "use alloc::boxed::Box"

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-05 10:24:45 -04:00
Marc-André Lureau
f4476ec69a feat(server): derive PartialEq for PixelOrder
To allow simple comparisons.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-05 10:24:45 -04:00
Marc-Andre Lureau
867d7bfe9d feat(async): re-export bytes dependency whose types appear in API
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-08-05 10:24:45 -04:00
Benoît Cortier
ddfd0bde2a
ci(nuget-publish): fix build for Android (#516)
The Android 19 toolchain is not included anymore, so we bump the
minimum supported version to Android 21 for i686 (x86) architecture.
2024-08-05 07:53:09 -04:00
Vladyslav Nikonov
a0b82f1921
feat(now-proto): initial NOW spec implementation (#509) 2024-08-04 23:06:16 -04:00
Vladyslav Nikonov
5406c286f4
fix(ironrdp-error): fix build with alloc feature (#514) 2024-08-01 08:43:54 +00:00
zmrush
20e60bc415
feat(rdpdr): *nix backend for file upload/download (#497) 2024-07-31 10:11:25 +00:00
dependabot[bot]
ea60fcacbb
build(deps): bump the patch group with 2 updates (#511) 2024-07-29 23:24:24 -04:00
dependabot[bot]
f2d573d987
build(deps): bump inquire from 0.6.2 to 0.7.5 (#512) 2024-07-29 23:23:39 -04:00
Vladyslav Nikonov
00de2164be
fix(cliprdr): exposed HWND for Windows native cliprdr API (#510) 2024-07-26 11:47:03 -04:00
Marc-Andre Lureau
b6a839e248
feat(server): add Quit event (#505)
Learn to exit the run() loop from user.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-26 02:11:39 -04:00
Marc-André Lureau
9410f5356b chore: disable unused_crate_dependencies warning
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
4350058945 chore(xtask): check all targets
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
7307148cb8 chore: use workspace lints
Among other things, this allows tools and LSP to pick up our settings.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
05fc85bb13 fix(examples): various misc warning
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
db3fb61253 fix(examples): fix arithmetic_side_effects
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
22779a7a21 example: explicitly allow print_stdout
But not in the rest of the code base.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
ae78520681 fix(testsuite-core): fix clippy::unnecessary-box-returns
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
69fd1750bf fix: clippy::str_to_string
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
790b399718 fix: clippy::cloned-instead-of-copied
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
da2870506f fix: clippy cast warnings
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
51ccf555e6 fix: unused-crate-dependencies
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
77b06d62bd fix(pdu): fix TS_COMPRESSED_BITMAP_HEADER_EX FIXED_PART_SIZE
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
adf2797ef7 fix: unnecessary qualification
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
934177d772 fix: unreachable pub items
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
5945d24df2 fix: elided lifetimes warnings
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
27ead54bce fix(pdu): fix clippy::get_first
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
3a805761e7 fix(cliprdr-native): warn(renamed_and_removed_lints)
warning: lint `pointer_structural_match` has been removed: converted
into hard error, see RFC #3535
<https://rust-lang.github.io/rfcs/3535-constants-in-patterns.html> for
more information

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
4436e64434 fix: warn(clippy::doc_lazy_continuation)
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
b1c899a3ea fix: warn(manual_clamp)
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
a8a0f469be fix: warn(dead_code)
Export some now unused types.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
4b12d20aa3 fix(pdu): clippy::new_without_default
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
822197e463 fix(cliprdr-format): warn(clippy::byte_char_slices)
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
53b2484efd fix(graphics): clippy::needless_borrows_for_generic_args
read_with_offset() doesn't need to take output ownership.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Marc-André Lureau
d36391c164 fix(testsuite): fix clippy::useless_conversion
And fix the test.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-24 04:35:32 -04:00
Benoît Cortier
8309457b7e
chore(release): prepare for iron-remote-gui v0.12.0 (#502) 2024-07-23 18:23:41 +03:00
Marc-Andre Lureau
fe293cd144
feat(server): re-export external dependencies whose types appear in API (#501)
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-23 08:37:48 -04:00
irvingouj @ Devolutions
41ee9f6825
feat(web)!: use JS primitives instead of RxJS Observable in public API (#495) 2024-07-23 03:38:14 -04:00
dependabot[bot]
69f341b0ed
build(deps): bump the patch group with 3 updates (#499)
Bumps the patch group with 3 updates: [thiserror](https://github.com/dtolnay/thiserror), [rgb](https://github.com/kornelski/rust-rgb) and [embed-resource](https://github.com/nabijaczleweli/rust-embed-resource).


Updates `thiserror` from 1.0.62 to 1.0.63
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.62...1.0.63)

Updates `rgb` from 0.8.44 to 0.8.45
- [Changelog](https://github.com/kornelski/rust-rgb/blob/main/CHANGELOG.md)
- [Commits](https://github.com/kornelski/rust-rgb/compare/v0.8.44...v0.8.45)

Updates `embed-resource` from 2.4.2 to 2.4.3
- [Release notes](https://github.com/nabijaczleweli/rust-embed-resource/releases)
- [Commits](https://github.com/nabijaczleweli/rust-embed-resource/compare/v2.4.2...v2.4.3)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: rgb
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: embed-resource
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-23 02:49:34 -04:00
Benoît Cortier
abc0ee0617
ci: fix success job (#494)
GitHub considers skipped jobs as fulfilling requirements when merging
pull requests. We need to ensure that the success job fails if any
previous job has failed.
2024-07-16 07:32:35 -04:00
dependabot[bot]
2e1a9ac88e
build(deps): bump the patch group with 2 updates (#493)
Bumps the patch group with 2 updates: [thiserror](https://github.com/dtolnay/thiserror) and [clap](https://github.com/clap-rs/clap).


Updates `thiserror` from 1.0.61 to 1.0.62
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.61...1.0.62)

Updates `clap` from 4.5.8 to 4.5.9
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.8...v4.5.9)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-15 21:42:25 -04:00
Benoît Cortier
0c352c4711
chore(deps): update rustls dependency (#492) 2024-07-10 10:11:17 -04:00
Benoît Cortier
cae242bbe4
refactor: follow up to PR #467 (#491) 2024-07-10 08:41:08 -04:00
Marc-André Lureau
9017c1068d ci: install ALSA header on linux
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-09 21:55:01 -04:00
Marc-André Lureau
06132cf8c2 feat(client): add sound with the CPAL audio backend
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-09 21:55:01 -04:00
Marc-André Lureau
5976d5c231 feat: add rdpsnd-native crate
The crate purpose is to implement native audio support for RDPSND.

The initial implementation uses rust CPAL, which is already supposed to
be very versatile.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-09 21:55:01 -04:00
Marc-André Lureau
f89908c6eb feat(ironrdp): add sound playback to server example
Play a A sine wave.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-09 21:55:01 -04:00
Marc-André Lureau
43b6cdb93f feat(server): start sound support
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-09 21:55:01 -04:00
Marc-André Lureau
34540d1fd1 feat(rdpsnd): implement client side
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-09 21:55:01 -04:00
Marc-André Lureau
783ada1666 feat(rdpsnd): implement server side
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-09 21:55:01 -04:00
Marc-André Lureau
dec0c36fe9 test: add rdpsnd fuzzing
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-09 21:55:01 -04:00
Marc-André Lureau
9d11111129 feat(rdpsnd): implement PDUs
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-09 21:55:01 -04:00
Marc-André Lureau
d8d2326ad3 feat(server): drop static channels on end of client loop
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-09 21:55:01 -04:00
Marc-André Lureau
dfad96c3bc build(server): add tokio 'sync' feature as dev-dependency
In order to succesfully build the server example.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-09 21:55:01 -04:00
Marc-André Lureau
1cd7b5e0e0 test(client): replace deprecated hostname() call
whoami::hostname() is deprecated. The recommended function is fallible,
and returns case-sensitive name.

Use "ironrdp" as fallback.

Unfortunately, this API break is not reflected in their version.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-07-09 21:55:01 -04:00
Benoît Cortier
a4d1d74ed5
chore(deps): loosen dependencies requirements (#490) 2024-07-09 09:10:42 +00:00
dependabot[bot]
021d3c4fa0
build(deps): bump the patch group with 2 updates (#489)
Bumps the patch group with 2 updates: [async-trait](https://github.com/dtolnay/async-trait) and [rgb](https://github.com/kornelski/rust-rgb).


Updates `async-trait` from 0.1.80 to 0.1.81
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.80...0.1.81)

Updates `rgb` from 0.8.40 to 0.8.44
- [Changelog](https://github.com/kornelski/rust-rgb/blob/v0.8.44/CHANGELOG.md)
- [Commits](https://github.com/kornelski/rust-rgb/compare/v0.8.40...v0.8.44)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: rgb
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-08 22:15:50 -04:00
Mathieu Morrissette
8fb7300046
chore(deps): update cargo.lock (#488) 2024-07-03 20:18:09 -04:00
dependabot[bot]
41d28d1e1a
build(deps): bump the patch group with 3 updates (#484)
Bumps the patch group with 3 updates: [clap](https://github.com/clap-rs/clap), [num-bigint](https://github.com/rust-num/num-bigint) and [rgb](https://github.com/kornelski/rust-rgb).


Updates `clap` from 4.5.7 to 4.5.8
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.7...v4.5.8)

Updates `num-bigint` from 0.4.5 to 0.4.6
- [Changelog](https://github.com/rust-num/num-bigint/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.5...num-bigint-0.4.6)

Updates `rgb` from 0.8.37 to 0.8.40
- [Commits](https://github.com/kornelski/rust-rgb/compare/v0.8.37...v0.8.40)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: num-bigint
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: rgb
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-02 05:49:53 -04:00
Benoît Cortier
c45b0bd759
ci: aggregate success results into a single job (#483)
This greatly simplifies the terraform configuration, because we only
have to require the "Success" status check. It’s simpler than listing
all the checks in terraform which can be sometimes problematic:

- We need to list all the job names generated using `matrix`
- We need to open a PR in infrastructure-as-code AND to deploy the
  change when we change / add / remove a job

Here, we just list the required jobs directly as dependencies of the
`success` job, without having to modify multiple repositories. Job
dependencies also does not use the job names, but the job "key", so
there is much less things to enumerate. Less error prone overall.
2024-06-26 08:52:36 -04:00
Zac Bergquist
dfbe947e5b
fix(session): handle the set keyboard indicators PDU (#482)
This message doesn't require a response of any kind, but handling
it here will prevent an unknown PDU error which kills the session.

Co-authored-by: Benoît Cortier <bcortier@proton.me>
2024-06-26 02:17:10 +00:00
dependabot[bot]
87375cb4d4
build(deps): bump the patch group across 1 directory with 4 updates (#481)
Bumps the patch group with 4 updates in the / directory: [clap](https://github.com/clap-rs/clap), [url](https://github.com/servo/rust-url), [array-concat](https://github.com/inspier/array-concat) and [base64](https://github.com/marshallpierce/rust-base64).


Updates `clap` from 4.5.4 to 4.5.7
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.4...v4.5.7)

Updates `url` from 2.5.0 to 2.5.2
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/compare/v2.5.0...v2.5.2)

Updates `array-concat` from 0.5.2 to 0.5.3
- [Release notes](https://github.com/inspier/array-concat/releases)
- [Commits](https://github.com/inspier/array-concat/compare/0.5.2...0.5.3)

Updates `base64` from 0.22.0 to 0.22.1
- [Changelog](https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/marshallpierce/rust-base64/compare/v0.22.0...v0.22.1)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: url
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: array-concat
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: base64
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-25 03:20:00 -04:00
irvingouj @ Devolutions
d5c00d0c03
feat(ffi): expose InclusiveRectangle attributes (#480) 2024-06-19 03:21:36 -04:00
Richard Markiewicz
e339346f5e
ci: update macos runner version (#479) 2024-06-18 09:35:54 -04:00
dependabot[bot]
f81b909475
build(deps): bump base64 from 0.21.7 to 0.22.0 (#470)
Bumps [base64](https://github.com/marshallpierce/rust-base64) from 0.21.7 to 0.22.0.
- [Changelog](https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/marshallpierce/rust-base64/compare/v0.21.7...v0.22.0)

---
updated-dependencies:
- dependency-name: base64
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-17 04:08:47 -04:00
Benoît Cortier
f28a1d480a
ci(nuget-publish): fix build for net8.0 target (#476) 2024-06-12 17:46:55 +00:00
Benoît Cortier
67dac77a58
feat(ffi): deployable Devolutions.IronRdp package (#465) 2024-06-12 10:01:15 -04:00
Benoît Cortier
d92fa6c66e
refactor(ffi): ISequence interface for sequences (#475)
It’s possible to implement interfaces even on FFI types, because
they are declared as partials.

```csharp
public partial class MySequence : ISequence {}
```
So long as the type exposes the required functions, this one-liner
is enough.
2024-06-12 02:12:06 +00:00
irvingouj @ Devolutions
7302d605c1
feat(ffi): support for dynamic resizing (#472) 2024-06-10 16:31:33 +00:00
Benoît Cortier
ac29c3d537
chore(release): prepare for iron-remote-gui v0.11.6 (#474) 2024-06-10 14:14:45 +00:00
Marc-Andre Lureau
a659924fc5
fix(pdu): fix preamble_size in LicensingErrorMessage regression (#473)
According to
73170ca2-5f82-4a2d-9d1b-b439f3d8dadc

wMsgSize is "the size in bytes of the licensing packet (including the
size of the preamble)". It must thus exclude BASIC_SECURITY_HEADER_SIZE.

Fixes mstsc connection to IronRDP server/acceptor.

Fixes 5c42ade597 ("fix: make license parsing and protocol more resilient (#436)")
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-06-10 08:46:40 -04:00
irvingouj @ Devolutions
9f1b04634d
fix(web): fix wrong type assertion caused error (#471) 2024-06-06 13:02:24 -04:00
irvingouj @ Devolutions
63743e9fcc
chore(dotnet): house-keeping, fixed Rider warnings,style. Added extra comments (#468)
Code quality improvement on dotnet package.
2024-05-24 14:21:09 -04:00
irvingouj @ Devolutions
ed8883edc4
fix(dotnet): allow caller to use a different port for connection (#469)
Updated Devolutions.IronRdp package `Connect` function. Allow user to specify a port other than default 3389.
2024-05-24 13:50:48 -04:00
irving ou
5266147669 Update Connection.cs 2024-05-22 20:10:05 -04:00
irvingouj @ Devolutions
58755938c2
feat(ffi): add graceful shutdown API (#464) 2024-05-21 21:49:37 -04:00
Benoît Cortier
64e64d8cb4
ci(ffi): initial workflow for publishing NuGet package (#463) 2024-05-21 15:58:16 +00:00
dependabot[bot]
1422467750
--- (#461)
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-21 02:20:37 -04:00
irvingouj @ Devolutions
579823a1a9
feat(ffi): expose cliprdr to dotnet bindings (#459) 2024-05-14 07:07:34 +00:00
dependabot[bot]
e24fb56275
build(deps): bump the patch group with 6 updates (#460)
Bumps the patch group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [thiserror](https://github.com/dtolnay/thiserror) | `1.0.58` | `1.0.60` |
| [num-traits](https://github.com/rust-num/num-traits) | `0.2.18` | `0.2.19` |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.82` | `1.0.83` |
| [semver](https://github.com/dtolnay/semver) | `1.0.22` | `1.0.23` |
| [num-bigint](https://github.com/rust-num/num-bigint) | `0.4.4` | `0.4.5` |
| [paste](https://github.com/dtolnay/paste) | `1.0.14` | `1.0.15` |


Updates `thiserror` from 1.0.58 to 1.0.60
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.58...1.0.60)

Updates `num-traits` from 0.2.18 to 0.2.19
- [Changelog](https://github.com/rust-num/num-traits/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-traits/compare/num-traits-0.2.18...num-traits-0.2.19)

Updates `anyhow` from 1.0.82 to 1.0.83
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.82...1.0.83)

Updates `semver` from 1.0.22 to 1.0.23
- [Release notes](https://github.com/dtolnay/semver/releases)
- [Commits](https://github.com/dtolnay/semver/compare/1.0.22...1.0.23)

Updates `num-bigint` from 0.4.4 to 0.4.5
- [Changelog](https://github.com/rust-num/num-bigint/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.4...num-bigint-0.4.5)

Updates `paste` from 1.0.14 to 1.0.15
- [Release notes](https://github.com/dtolnay/paste/releases)
- [Commits](https://github.com/dtolnay/paste/compare/1.0.14...1.0.15)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: num-traits
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: semver
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: num-bigint
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: paste
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-14 02:42:02 -04:00
dependabot[bot]
5291a9addc
build(deps): bump getrandom from 0.2.14 to 0.2.15 in the crypto group (#458)
Bumps the crypto group with 1 update: [getrandom](https://github.com/rust-random/getrandom).


Updates `getrandom` from 0.2.14 to 0.2.15
- [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/getrandom/compare/v0.2.14...v0.2.15)

---
updated-dependencies:
- dependency-name: getrandom
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: crypto
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-07 03:32:25 -04:00
irvingouj @ Devolutions
590221469a
chore(ffi):allow Devolutions.IronRdp to be build and packed with Nuget with the dll on Windows (#457)
* Allow Devolutions.IronRdp to be build and packed with Nuget alone with the dll

* review fix
2024-05-03 15:29:04 -04:00
irvingouj @ Devolutions
f64ce02cf6
fix(ffi): include forgotten generated files (#456) 2024-05-03 12:27:02 -04:00
irvingouj @ Devolutions
0ec5be5dc4
feat(ffi): API for enabling tracing logs (#452) 2024-05-03 02:44:00 -04:00
Vladyslav Nikonov
7a1ff4a8e7
fix(fuzz): add missing ensure_size! in ServerUpgradeLicense (#455) 2024-05-02 20:36:55 +03:00
irvingouj @ Devolutions
083b5044ad
chore(ffi): add avalonia example to top level .sln (#453) 2024-05-02 01:36:26 -04:00
Isaiah Becker-Mayer
ca9527f8be
refactor(displaycontrol): make physical_size an Option (#449) 2024-05-01 23:07:53 +03:00
Przemko Robakowski
35839459aa
feat: expose keyboard_layout through config (#448) 2024-04-30 09:48:00 -04:00
dependabot[bot]
84a1b710d7
build(deps): bump rustls from 0.21.11 to 0.21.12 in the crypto group (#450)
Bumps the crypto group with 1 update: [rustls](https://github.com/rustls/rustls).


Updates `rustls` from 0.21.11 to 0.21.12
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustls/rustls/compare/v/0.21.11...v/0.21.12)

---
updated-dependencies:
- dependency-name: rustls
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: crypto
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-29 22:32:46 -04:00
Isaiah Becker-Mayer
fd105e4b56
feat(displaycontrol): hook up resize for ironrdp-client crate (#430) 2024-04-29 22:20:02 +03:00
Isaiah Becker-Mayer
5c42ade597
fix: make license parsing and protocol more resilient (#436)
* Converts `BlobType` to a resilient parsing style
* Makes the licensing parsing and handling more resilient to make it compatible with xrdp
2024-04-25 08:23:20 +03:00
irvingouj @ Devolutions
a232b4ee0f
feat(ffi): add a client example using Avalonia (#443) 2024-04-24 10:09:34 -04:00
Przemko Robakowski
fefcdc42fe
fix(connector): fix cursor rendering in Windows 2019 (#444)
This change fixes cursor rendering in Windows 2019 and older.
Previously only text cursor was rendered, all others defaulted to native cursor from client.

Set of flags matches what is used in FreeRDP.
2024-04-23 08:00:30 -04:00
dependabot[bot]
90b3f63e29
build(deps): bump rustls from 0.21.10 to 0.21.11 in the crypto group (#445)
Bumps the crypto group with 1 update: [rustls](https://github.com/rustls/rustls).


Updates `rustls` from 0.21.10 to 0.21.11
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustls/rustls/compare/v/0.21.10...v/0.21.11)

---
updated-dependencies:
- dependency-name: rustls
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: crypto
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-23 07:36:44 -04:00
dependabot[bot]
1f067069ba
build(deps): bump the patch group with 12 updates (#442)
Bumps the patch group with 12 updates:

| Package | From | To |
| --- | --- | --- |
| [thiserror](https://github.com/dtolnay/thiserror) | `1.0.57` | `1.0.58` |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.80` | `1.0.82` |
| [async-trait](https://github.com/dtolnay/async-trait) | `0.1.77` | `0.1.80` |
| [clap](https://github.com/clap-rs/clap) | `4.5.1` | `4.5.4` |
| [smallvec](https://github.com/servo/rust-smallvec) | `1.13.1` | `1.13.2` |
| [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) | `0.2.91` | `0.2.92` |
| [wasm-bindgen-futures](https://github.com/rustwasm/wasm-bindgen) | `0.4.41` | `0.4.42` |
| [web-sys](https://github.com/rustwasm/wasm-bindgen) | `0.3.68` | `0.3.69` |
| [js-sys](https://github.com/rustwasm/wasm-bindgen) | `0.3.68` | `0.3.69` |
| [chrono](https://github.com/chronotope/chrono) | `0.4.34` | `0.4.38` |
| [time](https://github.com/time-rs/time) | `0.3.34` | `0.3.36` |
| [xshell](https://github.com/matklad/xshell) | `0.2.5` | `0.2.6` |


Updates `thiserror` from 1.0.57 to 1.0.58
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.57...1.0.58)

Updates `anyhow` from 1.0.80 to 1.0.82
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.80...1.0.82)

Updates `async-trait` from 0.1.77 to 0.1.80
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.77...0.1.80)

Updates `clap` from 4.5.1 to 4.5.4
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.1...v4.5.4)

Updates `smallvec` from 1.13.1 to 1.13.2
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.13.1...v1.13.2)

Updates `wasm-bindgen` from 0.2.91 to 0.2.92
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/compare/0.2.91...0.2.92)

Updates `wasm-bindgen-futures` from 0.4.41 to 0.4.42
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

Updates `web-sys` from 0.3.68 to 0.3.69
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

Updates `js-sys` from 0.3.68 to 0.3.69
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

Updates `chrono` from 0.4.34 to 0.4.38
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.34...v0.4.38)

Updates `time` from 0.3.34 to 0.3.36
- [Release notes](https://github.com/time-rs/time/releases)
- [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md)
- [Commits](https://github.com/time-rs/time/compare/v0.3.34...v0.3.36)

Updates `xshell` from 0.2.5 to 0.2.6
- [Changelog](https://github.com/matklad/xshell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/xshell/compare/v0.2.5...v0.2.6)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: smallvec
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: wasm-bindgen
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: wasm-bindgen-futures
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: web-sys
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: js-sys
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: chrono
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: time
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: xshell
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-17 22:46:38 -04:00
Benoît CORTIER
c0031d34eb fix(dotnet): use ImageSharp to also support Linux 2024-04-09 11:35:48 -04:00
Benoît CORTIER
26421c7469 fix(dotnet): support IP address server names 2024-04-09 11:35:48 -04:00
Benoît CORTIER
a18fb34b6d fix(dotnet): include stacktrace in the error report 2024-04-09 11:35:48 -04:00
Benoît CORTIER
d70c9f0e98 style(dotnet): run dotnet format 2024-04-09 11:35:48 -04:00
Benoît CORTIER
bffe18d204 chore(dotnet): add EditorConfig file 2024-04-09 11:35:48 -04:00
Benoît CORTIER
6a53a0a803 chore(dotnet): add dotnet gitignore file 2024-04-09 11:35:48 -04:00
Benoît CORTIER
8523916acf build(dotnet): add solution file 2024-04-09 11:35:48 -04:00
Benoît CORTIER
b5a4ce19da build(xtask): Linux support for ffi build task 2024-04-09 11:35:48 -04:00
irvingouj @ Devolutions
ef2055235a
feat(ffi): screenshot example using C# bindings (#437) 2024-04-09 10:13:12 -04:00
dependabot[bot]
9ce4986668
build(deps): bump the crypto group with 2 updates (#440)
Bumps the crypto group with 2 updates: [rustls-pemfile](https://github.com/rustls/pemfile) and [getrandom](https://github.com/rust-random/getrandom).


Updates `rustls-pemfile` from 2.1.1 to 2.1.2
- [Release notes](https://github.com/rustls/pemfile/releases)
- [Commits](https://github.com/rustls/pemfile/compare/v/2.1.1...v/2.1.2)

Updates `getrandom` from 0.2.12 to 0.2.14
- [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/getrandom/compare/v0.2.12...v0.2.14)

---
updated-dependencies:
- dependency-name: rustls-pemfile
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: crypto
- dependency-name: getrandom
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: crypto
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-09 05:57:00 -04:00
Benoît Cortier
d8fd2ec3ae
ci(fuzz): merge artifacts even on failure (#439) 2024-04-08 11:10:39 +00:00
Isaiah Becker-Mayer
a64bd42d06
feat: scard ascii variants (#435) 2024-04-08 06:53:48 -04:00
Benoît Cortier
e85633233a
ci(fuzz): fix upload-artifact@v4 usage (#438) 2024-04-08 06:44:44 -04:00
Marc-André Lureau
4035543d13 feat(server): set the server event sender on the clipboard
Introduce a ServerEventSender trait to set the ServerEvent channel on a
clipboard handler/factory.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-04-05 13:05:04 -04:00
Marc-André Lureau
5962d0d2cb feat(cliprdr-native): publicly export StubCliprdrBackend
This will allow the server example to implement a custom CliprdrServerFactory.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-04-05 13:05:04 -04:00
Marc-André Lureau
649478baa4 feat(server): add clipboard events
Teach the clipboard implementation to emit events/reply.

This follows the client event dispatch mechanism, although I would
rather have a way for the clipboard backend to directly call to the
channel to emit messages (rather than having to dispatch explicitly all
possible events at the top-level server loop)

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-04-05 13:05:04 -04:00
Marc-André Lureau
3fe6577d15 fix(server): drop the need for Send on clipboard
It looks like this was introduced by mistake.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-04-05 13:05:04 -04:00
Marc-André Lureau
7b5c4a73fb feat(server): add ServerEvent
Add a channel to send events to the server loop.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-04-05 13:05:04 -04:00
Marc-André Lureau
98ffaccae2 feat(cliprdr): add FormatDataResponse::into_data()
Add a way to take inner data.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-04-05 13:05:04 -04:00
Marc-André Lureau
e68a875483 fix(cliprdr): fix channel initialization in server mode
The channel is considered initialized once the initial format-list is
received.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-04-05 13:05:04 -04:00
irvingouj @ Devolutions
3bcf51306d
feat(ffi): initial C# bindings (connector only) (#423) 2024-04-05 15:11:32 +00:00
Isaiah Becker-Mayer
ba3796f738
feat: implement the connection activation sequence rerun (#421) 2024-04-05 11:05:15 -04:00
Marc-André Lureau
d46e7964bf feat(server): add pointer DisplayUpdate
Add DisplayUpdate API to set the pointer simply (no cache, with 32bpp
RGBA etc)

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-04-03 11:59:10 -04:00
Marc-André Lureau
704084952f refactor(server): convert encoder to use anyhow::Result
This is a bit more idiomatic than returning None for errors, and allows
to push up the reporting.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-04-03 11:59:10 -04:00
Marc-André Lureau
2cef7e56fd refactor(server): factor out UpdateEncoder::encode_pdu()
The function is reusable.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-04-03 11:59:10 -04:00
Marc-André Lureau
f2ee34ffc2 feat(server): add some debug statements
Remove 'main label too.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-04-03 11:59:10 -04:00
Richard Markiewicz
a3ec56d875
ci: update actions versions (#434) 2024-04-03 14:41:03 +00:00
Marc-Andre Lureau
c8f6fe5181
feat: RemoteFX encoder (#379) 2024-04-02 11:38:38 -04:00
Isaiah Becker-Mayer
de25134007
feat: add a callback mechanism to the DisplayControlClient (#424) 2024-04-02 05:13:19 -04:00
dependabot[bot]
d066f8e409
build(deps): bump the crypto group with 2 updates (#428)
Bumps the crypto group with 2 updates: [rustls-pemfile](https://github.com/rustls/pemfile) and [der](https://github.com/RustCrypto/formats).


Updates `rustls-pemfile` from 2.1.0 to 2.1.1
- [Release notes](https://github.com/rustls/pemfile/releases)
- [Commits](https://github.com/rustls/pemfile/compare/v/2.1.0...v/2.1.1)

Updates `der` from 0.7.8 to 0.7.9
- [Commits](https://github.com/RustCrypto/formats/compare/der/v0.7.8...der/v0.7.9)

---
updated-dependencies:
- dependency-name: rustls-pemfile
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: crypto
- dependency-name: der
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: crypto
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-02 04:44:20 -04:00
Isaiah Becker-Mayer
04d78b6581
refactor: dynamic resize follow up (#425) 2024-04-01 12:21:54 -04:00
Isaiah Becker-Mayer
8e84fe50b0
feat: configurable performance flags (#426) 2024-03-31 11:23:01 -04:00
Isaiah Becker-Mayer
1e53669b11
feat: add support for dydynvc (#419) 2024-03-29 04:18:12 -04:00
dependabot[bot]
a1d8bb246e
build(deps): bump der-parser from 8.2.0 to 9.0.0 (#387)
Bumps [der-parser](https://github.com/rusticata/der-parser) from 8.2.0 to 9.0.0.
- [Release notes](https://github.com/rusticata/der-parser/releases)
- [Changelog](https://github.com/rusticata/der-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rusticata/der-parser/compare/der-parser-8.2.0...der-parser-9.0.0)

---
updated-dependencies:
- dependency-name: der-parser
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-27 06:53:47 +00:00
Isaiah Becker-Mayer
e3aa8bcf0d
feat: support for Server Deactivate All PDU (#418) 2024-03-27 02:39:35 -04:00
Vladyslav Nikonov
31ed09b909
fix(fuzz): fix failing fuzzing after pdu encode/decode refactoring (#420) 2024-03-21 22:53:09 +02:00
Marc-André Lureau
4da364367e refactor(pdu): drop PduParsing
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
c046e5396b refactor(pdu): drop impl_pdu_parsing!() macros
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
282731bfe8 refactor(acceptor): drop legacy::encode_send_data_indication
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
31feec5b79 refactor(pdu): convert ShareDataHeader and ShareControl to PduEncode/Decode 2024-03-21 02:43:50 +09:00
Marc-André Lureau
6d24fc7183 refactor(pdu): drop legacy string functions
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
5d0f6ce045 refactor(pdu): convert BER-related PDU to PduEncode/Decode
This patch could be split if we introduce a legacy module for the
transition of the few PDUs.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
a2a4c89e4f refactor(pdu): convert license exchange PDUs to PduEncode/Decode 2024-03-21 02:43:50 +09:00
Marc-André Lureau
2dc5742cd0 refactor(connector): change decode_user_data() for PduDecode version
Prepare for the following refactoring switching PDUs to PduDecode.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
f1b8014ba6 refactor(pdu): drop PduParsing for InclusiveRectangle
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
9faf1ff08d refactor(pdu): convert input module to PduEncode/Decode
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
2f955f16c3 refactor(pdu): convert FrameAcknowledgePdu to PduEncode/Decode
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
e88c9af70d refactor(pdu): convert GCC module to PduEncode/Decode
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
b0dd0677a4 refactor(dvc): switch to PduEncode/Decode
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
ed963cefb6 refactor(pdu): convert DVC PDUs to PduEncode/Decode
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
6b3a53829d refactor(pdu): DVC read/write_according_to_type()
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
a45deebc98 refactor(pdu): convert RDP module to PduEncode/Decode
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
825104bdf0 refactor(pdu): add helper macros for legacy PduEncoding
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
e12bb0fdb4 refactor(pdu): implement From<PduError> on legacy errors
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
079ef0273d feat(pdu): make InclusiveRectangle::FIXED_PART_SIZE public 2024-03-21 02:43:50 +09:00
Marc-André Lureau
57cff8008e feat(pdu): add ReadCursor::split_at()
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
047b3094fd feat(pdu): add i16 API to Read/WriteCursor 2024-03-21 02:43:50 +09:00
Marc-André Lureau
f7209006c5 feat(pdu): add u128 read/write cursor API
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
d240081f4d refactor(pdu): remove dead code
We may want to keep the TODO, although it's unclear to me. Perhaps
better in a github issue instead.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
2b585181ee fix(pdu): ReadCursor::remaining() returns &'a [u8]
Fix the lifetime annotations to reflect the inner slice lifetime.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
c0ca9fda2e test(pdu): compile encode_vec() when cfg(test)
Since some tests require it.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
86db302d17 fix(pdu): BitmapStream::decode should consume the src
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
3200aa9765 test(pdu): rdp6 tests require "alloc"
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Marc-André Lureau
bd14c6083b fix(pdu): fix ChannelDef::buffer_length()
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-03-21 02:43:50 +09:00
Vladyslav Nikonov
d9bbd4b1b3
feat(web): add API for cursor style override (#416) 2024-03-18 05:10:21 -04:00
Norbert Szetei
bfa3bdac26
test(fuzz): update dependencies (#413) 2024-03-12 05:53:57 -04:00
Norbert Szetei
8c4c640905
fix(cliprdr): OOM via decode_png and vector allocation (#411) 2024-03-12 00:34:28 +09:00
Norbert Szetei
ef2c3df761
fix(pdu): fixes three overflows in pointer.rs and one in bitmap.rs (#410) 2024-03-11 09:56:58 -04:00
Norbert Szetei
7e11d3e198
fix(pdu): overflow when decoding RsaPublicKey (#409) 2024-03-11 09:46:01 -04:00
Norbert Szetei
c4193371bd
fix(svc): rdpdr channel fuzzing harness and associated issues (#408) 2024-03-11 06:28:27 -04:00
Benoît Cortier
e92d8c3e17
test: regression test case for cliprdr_format target (#405) 2024-03-08 15:44:12 +00:00
Norbert Szetei
220df049b6
fix(cliprdr): inconsistent invariant for BitmapInfoHeader width/height methods (#407) 2024-03-08 15:36:17 +00:00
Vladyslav Nikonov
1ed913f891
fix(cliprdr): fixed typo in cliprdr-format which caused fuzzing to fail (#406) 2024-03-08 06:44:27 -05:00
Vladyslav Nikonov
cf4c9eae41
fix(cliprdr): panic when calling .abs() on i32::MIN (#404) 2024-03-08 03:47:09 +00:00
Benoît Cortier
def44ec32c
ci: enable the cliprdr_format fuzz harness (#398) 2024-03-07 08:16:51 -05:00
Isaiah Becker-Mayer
3ee44b5c9b Adds logic for sending Capabilities Response
even in cases where the server doesn't send a Capabilities Request
as expected. This mimics the logic in FreeRDP, and empirically appears
to fix a bug where DVC channels behave irregularly.
2024-03-06 11:36:22 -05:00
Benoît Cortier
ba38d2b7ea
chore(release): prepare for iron-remote-gui v0.11.5 (#392) 2024-03-01 09:22:58 +00:00
Vladyslav Nikonov
a3073b9a73
refactor(connector): compile-time verified ExtendedClientOptionalInfo builder (#391) 2024-03-01 08:27:01 +00:00
Vladyslav Nikonov
bce9436635
fix(connector): Fix not sent performance flags (#390) 2024-02-29 18:09:04 -05:00
Isaiah Becker-Mayer
86b8e1429f
fix(rdpdr): implement Clone and PartialEq on most of efs and esc (#388) 2024-02-29 01:42:13 +00:00
Benoît Cortier
5ca05f1f5f
chore(release): prepare for iron-remote-gui v0.11.4 (#389) 2024-02-29 01:39:05 +00:00
Mihnea Buzatu
5fd5ce946e
feat: add support for skip channel join flag (#373) 2024-02-28 08:39:50 -05:00
Przemko Robakowski
af7805a5fc
fix(session): DVC message length (#383) 2024-02-27 07:24:32 +00:00
Vladyslav Nikonov
1732e5e21e fix(connector): Updated default performance flags 2024-02-26 12:00:12 -05:00
Vladyslav Nikonov
0d2b25a546
fix(web): Fixed bug with '0' and '1' keys not being registered (#382) 2024-02-20 20:55:20 +02:00
dependabot[bot]
9e6f9310ea
build(deps): bump the crypto group with 1 update (#380)
Bumps the crypto group with 1 update: [rustls-pemfile](https://github.com/rustls/pemfile).


Updates `rustls-pemfile` from 2.0.0 to 2.1.0
- [Release notes](https://github.com/rustls/pemfile/releases)
- [Commits](https://github.com/rustls/pemfile/compare/v/2.0.0...v/2.1.0)

---
updated-dependencies:
- dependency-name: rustls-pemfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: crypto
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-19 22:16:04 -05:00
dependabot[bot]
bc2cdc0a10
build(deps): bump the patch group with 4 updates (#381)
Bumps the patch group with 4 updates: [png](https://github.com/image-rs/image-png), [anyhow](https://github.com/dtolnay/anyhow), [clap](https://github.com/clap-rs/clap) and [semver](https://github.com/dtolnay/semver).


Updates `png` from 0.17.11 to 0.17.13
- [Changelog](https://github.com/image-rs/image-png/blob/master/CHANGES.md)
- [Commits](https://github.com/image-rs/image-png/compare/v0.17.11...v0.17.13)

Updates `anyhow` from 1.0.79 to 1.0.80
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.79...1.0.80)

Updates `clap` from 4.5.0 to 4.5.1
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.0...clap_complete-v4.5.1)

Updates `semver` from 1.0.21 to 1.0.22
- [Release notes](https://github.com/dtolnay/semver/releases)
- [Commits](https://github.com/dtolnay/semver/compare/1.0.21...1.0.22)

---
updated-dependencies:
- dependency-name: png
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: semver
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-19 22:15:45 -05:00
Benoît Cortier
a7e163b813
docs(web): update README files (#377) 2024-02-16 08:16:44 -05:00
Vladyslav Nikonov
39ca17b9c3
feat(input): Added unicode input to ironrdp-web and ironrdp-input (#374) 2024-02-15 11:42:37 +02:00
Vladyslav Nikonov
d53a5321b2
feat(displaycontrol): dynamic resolution PDUs (#366) 2024-02-14 19:32:02 +02:00
Vladyslav Nikonov
da009da927
fix(web): set correct src for cursor proxy Image object (#372) 2024-02-13 15:55:55 +00:00
Benoît Cortier
3f36e9195e
refactor(async): use the "Local" terminology (#371)
For non-Send types, the common terminology is "Local", or "Thread Local"
instead of "Single Threaded".

This patch is renaming such types since this is shorter and standard.
2024-02-13 09:17:40 -05:00
Benoît Cortier
0cd612bd43
refactor(web): use gloo-net provided AsyncRead/Write impl (#370) 2024-02-13 16:06:11 +02:00
Benoît Cortier
abe90e40f4
fix(web): send the actual meta key scancode (#367) 2024-02-13 16:05:38 +02:00
dependabot[bot]
a1db20fef0
build(deps): bump the patch group with 9 updates (#368)
Bumps the patch group with 9 updates:

| Package | From | To |
| --- | --- | --- |
| [thiserror](https://github.com/dtolnay/thiserror) | `1.0.56` | `1.0.57` |
| [num-derive](https://github.com/rust-num/num-derive) | `0.4.1` | `0.4.2` |
| [num-traits](https://github.com/rust-num/num-traits) | `0.2.17` | `0.2.18` |
| [num-integer](https://github.com/rust-num/num-integer) | `0.1.45` | `0.1.46` |
| [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) | `0.2.90` | `0.2.91` |
| [wasm-bindgen-futures](https://github.com/rustwasm/wasm-bindgen) | `0.4.40` | `0.4.41` |
| [web-sys](https://github.com/rustwasm/wasm-bindgen) | `0.3.67` | `0.3.68` |
| [js-sys](https://github.com/rustwasm/wasm-bindgen) | `0.3.67` | `0.3.68` |
| [chrono](https://github.com/chronotope/chrono) | `0.4.33` | `0.4.34` |


Updates `thiserror` from 1.0.56 to 1.0.57
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.56...1.0.57)

Updates `num-derive` from 0.4.1 to 0.4.2
- [Changelog](https://github.com/rust-num/num-derive/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-derive/compare/num-derive-0.4.1...num-derive-0.4.2)

Updates `num-traits` from 0.2.17 to 0.2.18
- [Changelog](https://github.com/rust-num/num-traits/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-traits/compare/num-traits-0.2.17...num-traits-0.2.18)

Updates `num-integer` from 0.1.45 to 0.1.46
- [Changelog](https://github.com/rust-num/num-integer/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-integer/compare/num-integer-0.1.45...num-integer-0.1.46)

Updates `wasm-bindgen` from 0.2.90 to 0.2.91
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/compare/0.2.90...0.2.91)

Updates `wasm-bindgen-futures` from 0.4.40 to 0.4.41
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

Updates `web-sys` from 0.3.67 to 0.3.68
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

Updates `js-sys` from 0.3.67 to 0.3.68
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

Updates `chrono` from 0.4.33 to 0.4.34
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.33...v0.4.34)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: num-derive
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: num-traits
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: num-integer
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: wasm-bindgen
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: wasm-bindgen-futures
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: web-sys
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: js-sys
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: chrono
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 01:02:08 -05:00
dependabot[bot]
180975e416
build(deps): bump clap from 4.4.18 to 4.5.0 (#369)
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.18 to 4.5.0.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.18...clap_complete-v4.5.0)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 01:01:45 -05:00
Isaiah Becker-Mayer
c944674ecb enable windows key 2024-02-08 15:14:49 -05:00
Marc-André Lureau
60080191aa fix(server): make Debug for BitmapUpdate less verbose
Skip the data part, it's usually noisy.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-02-06 18:49:42 +09:00
Marc-André Lureau
42329b326d feat(connector): make DesktopSize Partial/Eq/Copy
The type is simple, let's avoid the need for explicit clone()

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-02-06 18:49:42 +09:00
Marc-André Lureau
3a52fed1fe fix(session): bitmap is in BgrX format
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-02-06 18:49:42 +09:00
Marc-André Lureau
896f4b32a1 fix(session): correct Exclusive->Inclusive rectangle
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-02-06 18:49:42 +09:00
Marc-André Lureau
df165129fa fix(server): flip bitmap data when necessary
Bitmaps are encoded from bottom to top...

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-02-06 18:49:42 +09:00
Marc-André Lureau
2ddb618e83 fix(client): scale cursor position to logical position
This helps with HiDPI clients. However, we should also have code to
handle HiDPI guests, where physical position will be required. Or when
the display is scaled/streched. Leave some FIXME notes for that.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-02-06 18:49:42 +09:00
Marc-André Lureau
aecc29b780 refactor(server): replace get_updates() with a receiver
When there are no clients connected, we are not processing display
updates. Let the server implementation react when client start and stop
listening for updates. For example, the initial update should be a
whole-display, and on disconnect, the server can stop processing updates
too.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-02-06 18:49:42 +09:00
Marc-André Lureau
f93c546252 fix(doc): warning: this URL is not a hyperlink
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-02-06 18:49:42 +09:00
dependabot[bot]
3d30d6bfc3
build(deps): bump the patch group with 2 updates (#362)
Bumps the patch group with 2 updates: [reqwest](https://github.com/seanmonstar/reqwest) and [time](https://github.com/time-rs/time).


Updates `reqwest` from 0.11.23 to 0.11.24
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.23...v0.11.24)

Updates `time` from 0.3.31 to 0.3.34
- [Release notes](https://github.com/time-rs/time/releases)
- [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md)
- [Commits](https://github.com/time-rs/time/compare/v0.3.31...v0.3.34)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: time
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 21:53:58 -05:00
dependabot[bot]
da82c4f717
build(deps): bump tokio from 1.35.1 to 1.36.0 (#363)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.35.1 to 1.36.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.35.1...tokio-1.36.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 21:53:36 -05:00
dependabot[bot]
37aa6426db
build(deps): bump the patch group with 1 update (#360)
Bumps the patch group with 1 update: [chrono](https://github.com/chronotope/chrono).


Updates `chrono` from 0.4.32 to 0.4.33
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.32...v0.4.33)

---
updated-dependencies:
- dependency-name: chrono
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 22:52:18 -05:00
dependabot[bot]
0f6a7068e1
build(deps): bump gloo-net from 0.4.0 to 0.5.0 (#361)
Bumps [gloo-net](https://github.com/rustwasm/gloo) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/rustwasm/gloo/releases)
- [Changelog](https://github.com/rustwasm/gloo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/gloo/compare/0.4.0...0.5.0)

---
updated-dependencies:
- dependency-name: gloo-net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 22:52:00 -05:00
dependabot[bot]
a96cacdab0
build(deps): bump the patch group with 4 updates (#354)
Bumps the patch group with 4 updates: [bitflags](https://github.com/bitflags/bitflags), [clap](https://github.com/clap-rs/clap), [resize](https://github.com/PistonDevelopers/resize) and [chrono](https://github.com/chronotope/chrono).


Updates `bitflags` from 2.4.1 to 2.4.2
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/2.4.1...2.4.2)

Updates `clap` from 4.4.17 to 4.4.18
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.17...v4.4.18)

Updates `resize` from 0.8.3 to 0.8.4
- [Commits](https://github.com/PistonDevelopers/resize/compare/v0.8.3...v0.8.4)

Updates `chrono` from 0.4.31 to 0.4.32
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.31...v0.4.32)

---
updated-dependencies:
- dependency-name: bitflags
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: resize
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: chrono
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-24 12:03:40 -05:00
Marc-André Lureau
f29f2c64c3 feat(server): add basic Display Control channel
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-25 01:01:59 +09:00
Marc-André Lureau
3c76c76705 feat(server): add ainput DVC channel
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-25 01:01:59 +09:00
Marc-André Lureau
203f39eb88 refactor(server): require RdpServerInputHandler to be Send
So we can share it with different tasks more easily.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-25 01:01:59 +09:00
Marc-André Lureau
33e61a6c19 refactor(server): wrap handler in Arc<Mutex<>>
The following commit will share the handler with a DVC channel.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-25 01:01:59 +09:00
Marc-André Lureau
d0aae30a86 refactor(server): drop need for async in input handler
Having async handlers is discouraged. Implementations should use event
signaling and separate threads instead. See also:
https://github.com/Devolutions/IronRDP/pull/350#pullrequestreview-1821815597

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-25 01:01:59 +09:00
Marc-André Lureau
df329ac888 feat(server): impl From<ainput::MousePdu> for MouseEvent
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-25 01:01:59 +09:00
Marc-André Lureau
fe27f60e1c feat(dvc): add FreeRDP::Advanced::Input PDUs
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-25 01:01:59 +09:00
Marc-André Lureau
d81e144832 feat(dvc): add server bits
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-25 01:01:59 +09:00
Marc-André Lureau
671b742bec feat(pdu): add PduEncode for Vec<u8>
This is a helper for legacy code.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-25 01:01:59 +09:00
Marc-André Lureau
869d0d31a2 feat(pdu): add a few new() API
Some new convenient API for the next commit.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-25 01:01:59 +09:00
Marc-André Lureau
14bb6d0c82 refactor(dvc): move client bits to client.rs
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-25 01:01:59 +09:00
Marc-André Lureau
0b45bada37 feat(dvc): introduce DvcClientProcessor
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-25 01:01:59 +09:00
Marc-André Lureau
7434f344f0 refactor(dvc): simplify Debug of DrdynvcClient
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-25 01:01:59 +09:00
Marc-André Lureau
50a708c5a0 refactor(dvc): rename Drdynvc to DrdynvcClient
Let's distinguish Client and Server traits to avoid unexpected mix.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-25 01:01:59 +09:00
Marc-André Lureau
494506f7bb refactor(dvc): DvcProcessor
Rename DynamicVirtualChannel to follow SvcProcessor naming.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-25 01:01:59 +09:00
Marc-André Lureau
4b5e2a2392 build(deps): cargo update
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-25 01:01:59 +09:00
dependabot[bot]
151a7e9839
build(deps): bump smallvec from 1.12.0 to 1.13.1 (#356)
Bumps [smallvec](https://github.com/servo/rust-smallvec) from 1.12.0 to 1.13.1.
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.12.0...v1.13.1)

---
updated-dependencies:
- dependency-name: smallvec
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-22 23:50:23 -05:00
Marc-André Lureau
bdb421d987 build(deps): update rustls-pemfile from 1.0.4 to 2.0.0
Co-authored-by: Benoît Cortier <bcortier@proton.me>
2024-01-18 19:03:34 +09:00
dependabot[bot]
294b1d7d6e
build(deps): bump the patch group with 1 update (#352)
Bumps the patch group with 1 update: [clap](https://github.com/clap-rs/clap).


Updates `clap` from 4.4.16 to 4.4.17
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.16...v4.4.17)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 21:32:32 -05:00
Vladyslav Nikonov
65932ac23e
fix(clipboard): fix windows clipboard client regression (#351) 2024-01-15 14:05:01 +00:00
Marc-André Lureau
c61b17ab70 feat(server): handle relative mouse events
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
03b0a2fde8 feat(pdu): add relative mouse pointer
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
94fa7b7f54 feat(ironrdp): add stub clipboard to server example
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
9a3f51edb9 feat(client): add --clipboard-type none/default/stub
Allow to specify a clipboard, or disable it.

The stub implementation allows to experiment with basic channel
handling.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
f8a982dbda feat(cliprdr-native): add stub
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
119b0d33c2 feat(server): add optional cliprdr channel
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
7c432fc553 feat(cliprdr): add server support
The channel role is quite symmetrical, only initialization sequence
differs.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
cf407e389b refactor(acceptor): take SvcServerProcessor
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
38d3c33aa9 feat(server): add basic static channels handling
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
7d57c068fc feat(svc): add StaticChannelSet::iter_mut()
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
6aa513ca6d feat(svc): add StaticVirtualChannel::start()
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
0d21e39272 refactor(session): use client_encode_svc_messages
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
d1ed78a4fa refactor(svc): add encode_svc_messages
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
7d8a30304b doc(svc): generalize SvcProcessor::process()
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
d7f9cd6256 feat(connector): require SvcClientProcessor
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
978096f8c8 Mark all static channels as SvcClient
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
f39141787a feat(svc): add Svc{Client,Server}Processor
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
090440a45d refactor: rename StaticVirtualChannelProcessor->SvcProcessor
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Marc-André Lureau
f52c1ca5d5 build(deps): cargo update
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2024-01-15 13:21:51 +09:00
Benoît Cortier
db9a7cc705
fix(connector): send join requests in a single batch (#347)
Issue: #112
2024-01-09 17:29:15 +00:00
dependabot[bot]
e0521f3906
build(deps): bump the patch group with 6 updates (#345)
Bumps the patch group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [thiserror](https://github.com/dtolnay/thiserror) | `1.0.53` | `1.0.55` |
| [clap](https://github.com/clap-rs/clap) | `4.4.12` | `4.4.14` |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.78` | `1.0.79` |
| [semver](https://github.com/dtolnay/semver) | `1.0.20` | `1.0.21` |
| [async-trait](https://github.com/dtolnay/async-trait) | `0.1.76` | `0.1.77` |
| [base64](https://github.com/marshallpierce/rust-base64) | `0.21.5` | `0.21.6` |


Updates `thiserror` from 1.0.53 to 1.0.55
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.53...1.0.55)

Updates `clap` from 4.4.12 to 4.4.14
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.12...v4.4.14)

Updates `anyhow` from 1.0.78 to 1.0.79
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.78...1.0.79)

Updates `semver` from 1.0.20 to 1.0.21
- [Release notes](https://github.com/dtolnay/semver/releases)
- [Commits](https://github.com/dtolnay/semver/compare/1.0.20...1.0.21)

Updates `async-trait` from 0.1.76 to 0.1.77
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/commits)

Updates `base64` from 0.21.5 to 0.21.6
- [Changelog](https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/marshallpierce/rust-base64/compare/v0.21.5...v0.21.6)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: semver
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: base64
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 21:29:05 -05:00
dependabot[bot]
33c66edfc6
build(deps): bump the patch group with 6 updates (#344)
Bumps the patch group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [thiserror](https://github.com/dtolnay/thiserror) | `1.0.51` | `1.0.53` |
| [clap](https://github.com/clap-rs/clap) | `4.4.11` | `4.4.12` |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.75` | `1.0.78` |
| [futures-util](https://github.com/rust-lang/futures-rs) | `0.3.29` | `0.3.30` |
| [async-trait](https://github.com/dtolnay/async-trait) | `0.1.74` | `0.1.76` |
| [time](https://github.com/time-rs/time) | `0.3.30` | `0.3.31` |


Updates `thiserror` from 1.0.51 to 1.0.53
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.51...1.0.53)

Updates `clap` from 4.4.11 to 4.4.12
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.11...v4.4.12)

Updates `anyhow` from 1.0.75 to 1.0.78
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.75...1.0.78)

Updates `futures-util` from 0.3.29 to 0.3.30
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.29...0.3.30)

Updates `async-trait` from 0.1.74 to 0.1.76
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.74...0.1.76)

Updates `time` from 0.3.30 to 0.3.31
- [Release notes](https://github.com/time-rs/time/releases)
- [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md)
- [Commits](https://github.com/time-rs/time/compare/v0.3.30...v0.3.31)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: futures-util
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: time
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-01 22:28:49 -05:00
Benoît CORTIER
7519d8cc83 chore(release): prepare iron-remote-gui for 0.11.3 2023-12-21 11:24:59 -05:00
Benoît CORTIER
fded9f6a7e fix(web): make sure to not init logger twice 2023-12-21 11:24:59 -05:00
Benoît Cortier
9aa0e15ed2
chore(release): prepare iron-remote-gui for 0.11.2 (#339)
Issue: DPS-9810
2023-12-20 02:41:35 +00:00
Benoît Cortier
a563b15048
fix(credssp): catch KDC proxy HTTP status error (#338)
Previous error:

```
[CredSSP] CredSSP Caused by: InvalidToken: ASN1 DER error: TruncatedData
```

New error:

```
[KdcProxy] reason: HTTP status error (500 Internal Server Error)
```
2023-12-19 20:56:44 -05:00
dependabot[bot]
74b45c7144
build(deps): bump the patch group with 3 updates (#337)
Bumps the patch group with 3 updates: [sspi](https://github.com/devolutions/sspi-rs), [thiserror](https://github.com/dtolnay/thiserror) and [reqwest](https://github.com/seanmonstar/reqwest).


Updates `sspi` from 0.11.0 to 0.11.1
- [Commits](https://github.com/devolutions/sspi-rs/commits)

Updates `thiserror` from 1.0.50 to 1.0.51
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.50...1.0.51)

Updates `reqwest` from 0.11.22 to 0.11.23
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.22...v0.11.23)

---
updated-dependencies:
- dependency-name: sspi
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 23:16:48 +00:00
Vladyslav Nikonov
056ec6a034
feat(session): graceful disconnection support (#336) 2023-12-14 15:36:20 +00:00
dependabot[bot]
d24000a2ae
build(deps): bump the patch group with 2 updates (#335)
Bumps the patch group with 2 updates: [tracing-web](https://github.com/WorldSEnder/tracing-web) and [resize](https://github.com/PistonDevelopers/resize).


Updates `tracing-web` from 0.1.2 to 0.1.3
- [Changelog](https://github.com/WorldSEnder/tracing-web/blob/master/CHANGELOG.md)
- [Commits](https://github.com/WorldSEnder/tracing-web/commits)

Updates `resize` from 0.8.2 to 0.8.3
- [Commits](https://github.com/PistonDevelopers/resize/compare/v0.8.2...v0.8.3)

---
updated-dependencies:
- dependency-name: tracing-web
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: resize
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 19:03:26 -05:00
Marc-Andre Lureau
49ea64d913
fix(server): advertize FASTPATH_OUTPUT (#332)
This helps FreeRDP, despite not being required by mstcs:
https://github.com/FreeRDP/FreeRDP/issues/9612

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-08 14:23:42 -05:00
Marc-Andre Lureau
3725455682
feat(session): handle surface with codec ID 0 (raw bitmap) (#331)
This fixes the compability with the server example.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-08 14:18:07 -05:00
Marc-Andre Lureau
e3669b8eb7
feat(client): log to stdout/err by default (#330)
Logging to a file by default isn't common.

Use a more verbose and developer-friendly format in this case.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-08 19:11:29 +00:00
Benoît Cortier
0954f42519
fix(connector): better security protocol selection (#328)
Replace the bitflag-based config API with a boolean-based one:

- `enable_tls`: set the PROTOCOL_SSL flag
- `enable_credssp`: set the PROTOCOL_HYBRID and PROTOCOL_HYBRID_EX flags

The `--security_protocol` argument was removed from the native client
CLI, and instead it’s possible to disable specific protocols with
`--no-tls` and `--no-credssp`. By default, both protocols are
enabled for maximum compatibility with most RDP servers. We may change
the defaults in the future.
2023-12-08 15:59:48 +00:00
Benoît Cortier
9801c4f560
fix(connector): better errors for MCS channel confirm (#329) 2023-12-08 13:07:16 +02:00
Benoît Cortier
bd8df6dd1c
refactor: clarify features exposed by ironrdp-tls (#325) 2023-12-05 22:24:33 -05:00
Benoît Cortier
d47f181783
chore(xtask): make clean action delete local cargo root (#323) 2023-12-05 20:15:44 -05:00
Benoît Cortier
4a54d78cf6
chore(xtask): use cargo-binstall if available (#324) 2023-12-05 19:56:53 -05:00
Benoît Cortier
4506e1b185
build: bump rust toolchain to 1.74.0 (#322) 2023-12-05 19:55:51 -05:00
Benoît Cortier
2384a70916
chore: add getting help section in README (#321) 2023-12-05 20:47:22 +00:00
Isaiah Becker-Mayer
6588f7cdc7
refactor: function! macro for error context (#317)
Deletes any now-superfluous `const NAME: &'static str = "name";` definitions.

This gives a path to the function that is being called, which is useful
for debugging errors. It replaces the necessity of implementing
`const NAME: &'static str = "name";` in every function that wants to
use these macros (or coming up with a custom context).
2023-12-05 18:44:17 +00:00
Benoît Cortier
9355ae5b3d
refactor: follow-up for PR #198 (#320) 2023-12-05 17:18:09 +00:00
Marc-André Lureau
b611cda9b2 feat: add simple server example
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
53967a6bb0 feat(server): use SetSurfaceBits if possible
mstsc does not display bitmap update, for some reason. Using
SetSurfaceBits instead solves it, and is required for more advanced codecs.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
06dbe8008b feat(server): pass the surface flags to the UpdateEncoder
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
58a5d396a3 feat(server): check the client supports fastpath
It's needed for bitmap & surface commands etc.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
00ba7ad94d feat(server): advertize support for all surface commands
Those flags are set both by client and server. Even if the server
doesn't send those commands yet, this should be supported by most
clients.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
8776ec97c7 feat(acceptor): add basic static channel handling
Only accept channels we have attached.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
5742852d60 feat(svc): add StaticChannelSet::get_by_channel_name()
Used by the next patch during server negotiation.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
fa85584658 fix(server): handle only IO data at this point
Support for other channel messages is currently lacking.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
ad20697051 feat(acceptor): add {io,user}_channel_id in AcceptorResult
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
1c516e0026 misc(session): simplify a bit the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
e6b923fabe misc(pdu): log pdu_type on error
To ease debugging scenario.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
fadb0f14d3 feat(pdu): teach ShareDataPdu about RefreshRectangle
Even when the server doesn't advertize its support, msctc sends this.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
084fcaf289 feat(pdu): teach ShareDataPdu about SuppressOutput
Even when the server doesn't advertize its support, msctc sends this.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
3e47129eef fix(server): use nz/u16 for bitmap update region
Use u16 since that's the limit for resolution and bitmap data.

Use non-zero types for width & height, to avoid various issues, such as
division by zero.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
5ca81cca85 fix(acceptor): use good default for FontPdu
Fixes some warnings produced by FreeRDP when receiving the message.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
6bf638e200 feat(pdu): impl Default for FontPdu
Follow the spec to set good default values.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
950e88688c fix(server): fix fragmentation
The fragment index is not correctly updated, staying at 0, making
invalid series of First/First/..Last.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
3467579ffe feat(server): handle ShareDataPdu::ShutdownRequest
That PDU is sent by mstsc on disconnect.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
1a91c455a5 feat(pdu): teach ShareDataPdu about ShutdownRequest
Then the server can handle it.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
b0e0dd456e fix(server): advertize smaller FPU
u32::MAX is too large for mstsc, it fails to connect.
See also: https://github.com/Devolutions/IronRDP/issues/318

16Mb is the value use by freerdp
sample/server (https://github.com/FreeRDP/FreeRDP/pull/1313) and it's
probably fine for now.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
b6b509588c fix(server): keep running after a connection error
Simply report an error!() instead of returning an Error back.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
732e995509 fix(pdu): fix connectPDU length
Wireshark is unhappy about it and throws a "Malformed packet" error.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
d14f96cd61 fix(pdu): display the unexpected value with the error
This provides more useful errors:

Error: [invalid payload] PDU error

Caused by:
    [SendDataRequest] invalid message type (8)

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
dca40613f6 feat(connector): warn on unexpected protocol version
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
d81ca56f31 feat(pdu): export PROTOCOL_VER
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
4d0246c9b1 fix(pdu): accept various protocol version
Since FreeRDP commit
7cef0cb8d6cfc2307af967a851f44ad6c376a24d ("Refactored capability
parsing"), protocol version default to 0. This is probably an issue
there, but IronRDP can also be tolerant here...

Add a new "protocol_version" field in the General capability structure,
so that client code can check the version and report an error if they
need to.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
1c220581de feat(pdu): impl Default for General capability sets
This allows to simplify some user code, and let us introduce a
"protocol_version" field in the next patch without having to explicitly
set its value but rely on the default instead.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
a513701173 fix(pdu): accept unknown capability flags
Be tolerant when parsing unknown flags.

Related to:
https://github.com/Devolutions/IronRDP/issues/217

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
Marc-André Lureau
74e40b88cb feat(pdu): add EarlyCapability SKIP_CHANNELJOIN flags
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-12-05 11:37:18 -05:00
dependabot[bot]
b87627b10e
build(deps): bump the patch group with 1 update (#319)
Bumps the patch group with 1 update: [clap](https://github.com/clap-rs/clap).

- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.10...v4.4.11)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-04 23:35:47 +00:00
Isaiah Becker-Mayer
f5f5e9c087
feat(rdpdr): LockControl handling (#313)
- Add a new `UnsupportedPdu` kind to `PduError`, enhancing error handling capabilities in ironrdp-pdu.
  This new error kind allows for more specific error messages when encountering unsupported PDUs.
- Implement a macro `unsupported_pdu_err` for conveniently creating `PduError` instances with the `UnsupportedPdu` kind.
- Implement handling for `ServerDriveLockControlRequest` in ironrdp-rdpdr, including the definition of
  the struct and its decoding method. This adds support for processing lock control requests in the RDPDR protocol.
- Update `Rdpdr`'s `process` method to utilize the new `unsupported_pdu_err` when encountering unknown packet IDs, replacing the previous handling of unimplemented PDUs.
- Remove the `Unimplemented` variant from `RdpdrPdu` and `Component` enums, replacing it with `EmptyResponse` in `RdpdrPdu`.
  This change streamlines the handling of unimplemented or unexpected PDUs.
- Enhance `FileInformationClass` and `FileInformationClassLevel` with `Display` implementations, providing better logging and error message capabilities.
2023-12-01 23:58:52 +00:00
Vladyslav Nikonov
d69ff6e6e7
ci: follow-up typos CI fixes (#316) 2023-12-01 14:24:34 -05:00
Vladyslav Nikonov
4258f06292
ci: check for typos using typos-cli (#315) 2023-12-01 17:27:36 +00:00
Isaiah Becker-Mayer
6aa3f3e63b
feat(rdpdr): SetInformation* handling (#312)
- Extend ReadCursor with methods to read and try reading i64 values in both little and big endian formats.
- Update RdpdrPdu to handle ClientDriveSetInformationResponse, reflecting the addition of ServerDriveSetInformationRequest handling in the RDPDR (Remote Desktop Protocol: Device Redirection) protocol.
- Implement decoding and encoding of various FileInformationClass types such as FileEndOfFileInformation, FileDispositionInformation, FileRenameInformation, and FileAllocationInformation.
- Refactor the existing code to improve the handling of different FileInformationClassLevels and FileSystemInformationClassLevels.
- Add handling for ServerDriveNotifyChangeDirectoryRequest in ServerDriveIoRequest, supporting the specific case when MinorFunction is set to IRP_MN_NOTIFY_CHANGE_DIRECTORY.
2023-11-29 17:38:01 +00:00
Vladyslav Nikonov
b554c6a6d1
fix(rfx): invalid rectangle intersection logic (#311)
This fixes the RFX visual artifacts.
2023-11-28 10:18:38 -05:00
dependabot[bot]
ed31ead9d3
build(deps): bump url from 2.4.1 to 2.5.0 (#309)
Bumps [url](https://github.com/servo/rust-url) from 2.4.1 to 2.5.0.
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/compare/v2.4.1...v2.5.0)

---
updated-dependencies:
- dependency-name: url
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-27 23:49:29 +00:00
dependabot[bot]
045956060e
build(deps): bump the patch group with 4 updates (#308)
Bumps the patch group with 4 updates: [clap](https://github.com/clap-rs/clap), [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen), [wasm-bindgen-futures](https://github.com/rustwasm/wasm-bindgen) and [base64](https://github.com/marshallpierce/rust-base64).


Updates `clap` from 4.4.8 to 4.4.9
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.8...v4.4.9)

Updates `wasm-bindgen` from 0.2.88 to 0.2.89
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/compare/0.2.88...0.2.89)

Updates `wasm-bindgen-futures` from 0.4.38 to 0.4.39
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

Updates `base64` from 0.21.4 to 0.21.5
- [Changelog](https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/marshallpierce/rust-base64/compare/v0.21.4...v0.21.5)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: wasm-bindgen
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: wasm-bindgen-futures
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: base64
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-27 23:49:12 +00:00
Benoît Cortier
96979ef81d
chore(release): also update public package.json (#305) 2023-11-27 17:03:10 +00:00
irvingouj @ Devolutions
e6518af4fd
chore(release): prepare iron-remote-gui for 0.11.1 (#304) 2023-11-27 16:52:03 +00:00
irvingouj @ Devolutions
a5747f3661
fix done,includes rxjs (#303) 2023-11-27 11:31:18 -05:00
Benoît Cortier
2b254c2869
chore(release): prepare web client for 0.11.0 (#302) 2023-11-24 21:10:24 +00:00
irvingouj@Devolutions
716f5c5e05
build(web-client): roll up and export correct TS types (#301) 2023-11-24 14:28:00 -05:00
Isaiah Becker-Mayer
078aa433c2
feat(rdpdr): add DeviceWrite{Request,Response} (#299)
- Added DeviceWriteRequest and DeviceWriteResponse support in RDPDR.
- Included handling for new write requests and responses in ServerDriveIoRequest.
- Updated RdpdrPdu to manage DeviceWriteResponse alongside read and other responses.
- Enhanced processing logic to handle unexpected packets for device write actions.
2023-11-22 23:30:48 -05:00
Isaiah Becker-Mayer
781cfcb18b
feat(rdpdr): support for DeviceRead{Request,Response} (#298)
- Implemented `DeviceReadRequest` and `DeviceReadResponse` PDUs in `ironrdp-rdpdr`.
- Updated `RdpdrPdu` enum to include `DeviceReadResponse`.
- Modified decoding logic in `ServerDriveIoRequest` to handle `MajorFunction::Read`.
- Streamlined error handling for unexpected packets in `StaticVirtualChannelProcessor`.
- Refactored `ensure_size` checks to `ensure_fixed_part_size` in `efs.rs` for improved consistency.
2023-11-22 19:55:19 -05:00
Isaiah Becker-Mayer
bca685f43b
feat(rdpdr): enhance DeviceControlResponse and add AnyIoCtlCode (#297)
- Introduced `AnyIoCtlCode` to handle cases with unknown or non-critical IoCtlCode.
- Updated `DeviceControlResponse` to accept an optional `output_buffer`, aligning with FreeRDP's implementation for empty buffer scenarios.
- Implemented decoding for `DeviceControlRequest<AnyIoCtlCode>` in `ServerDriveIoRequest`.
2023-11-23 00:14:17 +00:00
Isaiah Becker-Mayer
25f49133d3
feat(rdpdr): ServerDriveQueryVolumeInformation{Request,Response} (#296)
- Added support for ServerDriveQueryVolumeInformationRequest in the RDPDR EFS module.
- Implemented decoding logic for ServerDriveQueryVolumeInformationRequest with checks for valid FileSystemInformationClassLevels.
- Extended the RdpdrPdu enum to include ClientDriveQueryVolumeInformationResponse.
- Implemented ClientDriveQueryVolumeInformationResponse with appropriate encoding and sizing methods.
- Updated Rdpdr handling to accommodate the new ClientDriveQueryVolumeInformationResponse type.
2023-11-22 15:56:22 -05:00
Benoît Cortier
32910b3a11
chore(release): prepare web client for 0.10.0 (#294) 2023-11-21 16:35:19 -05:00
dependabot[bot]
6ad640d904
build(deps): bump rustix from 0.38.17 to 0.38.25 (#295)
Bumps [rustix](https://github.com/bytecodealliance/rustix) from 0.38.17 to 0.38.25.
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.38.17...v0.38.25)

---
updated-dependencies:
- dependency-name: rustix
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-21 21:29:40 +00:00
Vladyslav Nikonov
5b210f2fc7
feat(web): support for hardware cursor (#290)
Adds support for native cursors for `IronRDP-web`.

- Uses CSS `cursor` property to set the custom cursor (which we encode as Base64 data URL
- Software rendering is still in place and could be enabled via the config flag
- "Inverted colors" are implemented for native cursors as a "checkerboard pattern" (As in FreeRDP)
- Native cursors have size **limitation** - _Any remote cursor bigger than 32x32 will be scaled down._ This is due to browser limitations described [here](https://chromestatus.com/feature/5825971391299584) and [here](https://bugs.chromium.org/p/chromium/issues/detail?id=880863)

Closes #250
2023-11-21 17:20:03 +00:00
Isaiah Becker-Mayer
723a91a432
feat(rdpdr): QueryDirectory structures for directory control (#288)
- Added a new method write_i8 to WriteCursor in ironrdp-pdu/src/cursor.rs,
allowing for writing an 8-bit integer value to the cursor.

- Updated Rdpdr in ironrdp-rdpdr/src/lib.rs to handle an additional case
for ClientDriveQueryDirectoryResponse in the error handling logic.

- Introduced several new structures in ironrdp-rdpdr/src/pdu/efs.rs related
 to directory information, including FileBothDirectoryInformation,
 FileFullDirectoryInformation, FileNamesInformation, and
 FileDirectoryInformation.

- Added support for the ClientDriveQueryDirectoryResponse PDU in various
parts of ironrdp-rdpdr, including the RdpdrPdu enum and its implementation.

- Added a ServerDriveQueryDirectoryRequest structure to handle server
requests for querying directory information, along with the necessary
encoding logic.

- Added a ClientDriveQueryDirectoryResponse structure to handle client
responses for directory queries, along with the necessary encoding logic.

- Updated the RDPDR PDU encoding and size calculation methods to support
the new ClientDriveQueryDirectoryResponse structure.
2023-11-20 21:15:32 -05:00
dependabot[bot]
6153f68b8d
build(deps): bump the crypto group with 1 update (#291)
Bumps the crypto group with 1 update: [rustls](https://github.com/rustls/rustls).

- [Release notes](https://github.com/rustls/rustls/releases)
- [Commits](https://github.com/rustls/rustls/compare/v/0.21.8...v/0.21.9)

---
updated-dependencies:
- dependency-name: rustls
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: crypto
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-20 18:55:07 -05:00
dependabot[bot]
4feb51909a
build(deps): bump proptest from 1.3.1 to 1.4.0 (#292)
Bumps [proptest](https://github.com/proptest-rs/proptest) from 1.3.1 to 1.4.0.
- [Release notes](https://github.com/proptest-rs/proptest/releases)
- [Changelog](https://github.com/proptest-rs/proptest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/proptest-rs/proptest/compare/v1.3.1...v1.4.0)

---
updated-dependencies:
- dependency-name: proptest
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-20 18:54:33 -05:00
Benoît CORTIER
08d165d71f style(cliprdr): prefer < over > 2023-11-20 09:39:36 -05:00
Benoît CORTIER
82d7977672 style(cliprdr): clarify invariants 2023-11-20 09:39:36 -05:00
Benoît CORTIER
162695534f refactor(cliprdr): consolidate HTML conversions
- `cf_html_to_plain_html` does not allocate anymore
- `plain_html_to_cf_html` returns a `String` (CF_HTML payload is UTF-8)
2023-11-20 09:39:36 -05:00
Benoît CORTIER
130932acee refactor(cliprdr): clarify naming 2023-11-20 09:39:36 -05:00
Benoît CORTIER
d2ddbcc4ee style: local helper functions 2023-11-20 09:39:36 -05:00
Benoît CORTIER
d2ddf50a40 perf: enable inlining for a few functions
These are very small functions which are used multiple times in the same
loop inside cliprdr web backend.
2023-11-20 09:39:36 -05:00
Benoît CORTIER
ccb82f9583 refactor(web): explain cliprdr web backend quirks 2023-11-20 09:39:36 -05:00
Benoît CORTIER
c1ac51904f refactor(cliprdr): fix typos 2023-11-20 09:39:36 -05:00
Benoît Cortier
8fc213e699
refactor(credssp): follow up to #260 (#287) 2023-11-17 09:50:03 -05:00
irvingouj@Devolutions
5530550ef3
feat(credssp): support for Kerberos (#260)
Issue: ARC-139
Co-authored-by: Benoît Cortier <bcortier@proton.me>
2023-11-17 04:12:13 +00:00
Benoît Cortier
89f25b4335
feat(cliprdr): BI_BITFIELDS compression is supported (#286)
It turns out we partially support the BI_BITFIELDS compression (which
is not actually for compressed bitmaps).

Quote from [MS-WMF] section 2.1.1.7:

> The bitmap is not compressed, and the color table consists of three
> DWORD (defined in [MS-DTYP] section 2.2.9) color masks that specify the
> red, green, and blue components, respectively, of each pixel. This is
> valid when used with 16 and 32-bits per pixel bitmaps.
2023-11-16 17:51:53 +00:00
Benoît Cortier
c4fae49d45
chore(web-client): bump prettier to 3.1.0 (#285)
A bug in 3.0.x versions was causing the --plugin setting to be set in
order to let prettier know about svelte. This is now fixed.
2023-11-16 11:27:46 -05:00
irvingouj@Devolutions
2849d5830b
chore(web-client): ignore *.css in ESLint (#284) 2023-11-15 15:51:36 -05:00
mihneabuz
1902b79558 fix(acceptor): mstsc disconnect
mstsc sometimes sends a DisconnectUltimatum after receiving the ServerDemandActive
2023-11-14 16:28:33 -05:00
mihneabuz
ef57fba453 fix(server): improve logging 2023-11-14 16:28:33 -05:00
mihneabuz
8c41b7649e fix(acceptor): accumulate and batch process input packets
We accumulate input events during the accept connection step, and
batch process them before running the main session loop.
2023-11-14 16:28:33 -05:00
mihneabuz
da36bd66cc feat(pdu): added RdpHint for any RDP pdu 2023-11-14 16:28:33 -05:00
dependabot[bot]
1a47255e7e
build(deps): bump the crypto group with 1 update (#281)
Bumps the crypto group with 1 update: [getrandom](https://github.com/rust-random/getrandom).

- [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/getrandom/compare/v0.2.10...v0.2.11)

---
updated-dependencies:
- dependency-name: getrandom
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: crypto
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-13 20:09:42 -05:00
dependabot[bot]
179ca93dec
build(deps): bump the patch group with 3 updates (#282)
Bumps the patch group with 3 updates: [clap](https://github.com/clap-rs/clap), [tracing-subscriber](https://github.com/tokio-rs/tracing) and [smallvec](https://github.com/servo/rust-smallvec).


Updates `clap` from 4.4.7 to 4.4.8
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.7...v4.4.8)

Updates `tracing-subscriber` from 0.3.17 to 0.3.18
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.17...tracing-subscriber-0.3.18)

Updates `smallvec` from 1.11.1 to 1.11.2
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.11.1...v1.11.2)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: tracing-subscriber
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: smallvec
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-13 20:09:18 -05:00
dependabot[bot]
4979ed4b6c
build(deps): bump tokio from 1.33.0 to 1.34.0 (#283)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.33.0 to 1.34.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.33.0...tokio-1.34.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-13 20:08:41 -05:00
Benoît Cortier
73ea1ff05f
chore(release): prepare web client for 0.9.0 (#280) 2023-11-13 19:48:52 +00:00
Benoît Cortier
76e3a35df1
style: normalize casing of error messages (#274)
Error messages should start by a lowercase so it’s easier to compose
with other error messages and the final casing is left up to the "error
reporter" down the line. This is the convention adopted by the Rust
project:

- https://rust-lang.github.io/api-guidelines/interoperability.html
- https://doc.rust-lang.org/stable/std/error/trait.Error.html
2023-11-13 17:06:48 +02:00
Benoît Cortier
a5aa8a2ef4
chore: review FIXMEs and TODOs (#272)
This patch also adds issue IDs when appropriate:

```rust
// FIXME(#XX): the foo-bar problem
// TODO(#XX): add support for feature baz here
```

This way, it’s easier to lookup the places relevant for tackling a
specific issue of the tracker, and conversely, it’s easier to
know which problem are "properly" tracked and which are not.
2023-11-13 17:06:15 +02:00
Benoît Cortier
a5845f5c9f
docs: add STYLE.md document (#275)
Based on rust-analyzer document:
- 76633199f4/docs/dev/style.md
2023-11-13 17:05:49 +02:00
Benoît Cortier
f1fab22fe0
fix(connector): STATUS_VALID_CLIENT IS expected (#279) 2023-11-11 02:35:55 +00:00
Benoît Cortier
52d1accf89
fix(connector): always expect STATUS_VALID_CLIENT (#278)
Issue: ARC-180
2023-11-10 19:27:11 -05:00
Benoît Cortier
c025f494bc
fix(server): enable required tokio feature net (#273) 2023-11-09 21:53:46 -05:00
Benoît Cortier
d6e1daccb6
chore(web-client): fix prettier checks (#270) 2023-11-09 21:53:27 -05:00
Benoît Cortier
e10bf4fc38
ci: check for dirty lock files (#277)
Note that we can’t really use the --locked option of cargo, because to
run xtask, we need to compile it using cargo, and thus the lock files
are already "refreshed" as far as cargo is concerned. Instead, this new
task will check for modifications to the lock files using git-status
porcelain. The side benefit is that we can check for npm lock files too.
2023-11-09 21:53:08 -05:00
Vladyslav Nikonov
2b501496d9
feat: clipboard support for web client (#259) 2023-11-08 13:57:21 +02:00
dependabot[bot]
c666dc5f03
build(deps): bump the patch group with 2 updates (#267)
Bumps the patch group with 2 updates: [wasm-bindgen-futures](https://github.com/rustwasm/wasm-bindgen) and [web-sys](https://github.com/rustwasm/wasm-bindgen).


Updates `wasm-bindgen-futures` from 0.4.37 to 0.4.38
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

Updates `web-sys` from 0.3.64 to 0.3.65
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

---
updated-dependencies:
- dependency-name: wasm-bindgen-futures
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: web-sys
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-06 20:45:06 -05:00
dependabot[bot]
c24e893a5d
build(deps): bump the patch group with 3 updates (#265)
Bumps the patch group with 3 updates: [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen), [wasm-bindgen-futures](https://github.com/rustwasm/wasm-bindgen) and [web-sys](https://github.com/rustwasm/wasm-bindgen).


Updates `wasm-bindgen` from 0.2.87 to 0.2.88
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/compare/0.2.87...0.2.88)

Updates `wasm-bindgen-futures` from 0.4.37 to 0.4.38
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

Updates `web-sys` from 0.3.64 to 0.3.65
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

---
updated-dependencies:
- dependency-name: wasm-bindgen
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: wasm-bindgen-futures
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: web-sys
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-06 23:44:00 +00:00
dependabot[bot]
a9bc51a10c
build(deps): bump js-sys from 0.3.64 to 0.3.65 (#266)
Bumps [js-sys](https://github.com/rustwasm/wasm-bindgen) from 0.3.64 to 0.3.65.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

---
updated-dependencies:
- dependency-name: js-sys
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-06 23:42:50 +00:00
irvingouj@Devolutions
23777d6b8b
chore(web-client): relax eslint prettier end of line checks (#264) 2023-11-06 12:36:40 -05:00
Benoît CORTIER
1d0b5fa1ee chore: check web client for lints and formatting 2023-11-04 02:26:46 -04:00
Benoît CORTIER
d4ea558da0 style(web-client): run prettier 2023-11-04 02:26:46 -04:00
Benoît CORTIER
dff3f307eb build(web-client): update npm dependencies 2023-11-04 02:26:46 -04:00
Benoît CORTIER
ab16cce949 refactor(web-client): fix lints emitted by ESLint
Co-authored-by: irving ou <jou@devolutions.net>
2023-11-04 02:26:46 -04:00
Benoît CORTIER
7839acfa63 chore(web-client): configure ESLint
Co-authored-by: irving ou <jou@devolutions.net>
2023-11-04 02:26:46 -04:00
Benoît CORTIER
19b8c119a9 refactor(iron-remote-gui): enable strict mode 2023-11-04 02:26:46 -04:00
Benoît CORTIER
aa8f3754ad refactor(web-client): set noImplicitAny to true
Co-authored-by: irving ou <jou@devolutions.net>
2023-11-04 02:26:46 -04:00
Benoît CORTIER
f0e00515cd chore(web-client): set allowJs to false
Everything (except .svelte files) is already TypeScript.

Co-authored-by: irving ou <jou@devolutions.net>
2023-11-04 02:26:46 -04:00
Benoît CORTIER
a0e3cbccf6 refactor(svelte): remove dead code 2023-11-04 02:26:46 -04:00
Benoît CORTIER
3a412a331f refactor(web-client): enable strictNullChecks 2023-11-04 02:26:46 -04:00
Benoît CORTIER
7ea8d1d503 chore(web-client): configure prettier
Co-authored-by: irving ou <jou@devolutions.net>
2023-11-04 02:26:46 -04:00
Benoît CORTIER
500beb21fa chore(web-client): update .gitignore files
Co-authored-by: irving ou <jou@devolutions.net>
2023-11-04 02:26:46 -04:00
Vladyslav Nikonov
5a1999aae3
fix(session): add out of bounds cursor rendering check (#262) 2023-11-03 14:12:43 -04:00
Benoît CORTIER
baf63b203b chore(release): prepare for iron-remote-gui v0.8.1 2023-11-02 11:52:47 -04:00
Benoît CORTIER
28e2029790 revert(web): disable RDP6 bitmap codec temporarily
Issue: https://github.com/Devolutions/IronRDP/issues/249
2023-11-02 11:52:47 -04:00
Isaiah Becker-Mayer
c2f57f5bcb
feat(rdpdr): handling for DeviceCloseRequest and DeviceCloseResponse (#257) 2023-11-02 00:54:03 -04:00
Benoît CORTIER
cebfe8c6f3 build: remove softbuffer patch 2023-11-01 15:51:40 -04:00
Benoît CORTIER
fbe8921528 ci: adjust npm-publish workflow 2023-11-01 15:51:40 -04:00
Benoît Cortier
b8ff6fee25
ci: add a workflow to publish the npm package (#246) 2023-11-01 08:49:10 -04:00
Benoît Cortier
d60782adae
docs: uniformize links to specification (#247)
I ran `sd` commands to change most places, and modified using
multi-cursors the last few places it didn’t covered.

```
$ fd --extension=rs --exec sd "/// \[([\d\.]+)( .+)\]:" "/// [\$1]:"
$ fd --extension=rs --exec sd "/// \[([\d\.]+)( .+)\]" "/// [\$1]\$2"
```
2023-10-31 19:20:33 -07:00
Isaiah Becker-Mayer
497f53df96
feat(rdpdr): add ServerDriveQueryInformationRequest (#245)
Also changes `FilesystemRequest` to be named `ServerDriveIoRequest`.
2023-10-31 21:54:54 -04:00
Benoît Cortier
29bf964dd6
chore(release): prepare web client for 0.8.0 (#244) 2023-10-31 16:08:32 +00:00
dependabot[bot]
10ee653c04
build(deps): bump the patch group with 3 updates (#242)
Bumps the patch group with 3 updates: [clap](https://github.com/clap-rs/clap), [arbitrary](https://github.com/rust-fuzz/arbitrary) and [futures-util](https://github.com/rust-lang/futures-rs).


Updates `clap` from 4.4.6 to 4.4.7
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.6...v4.4.7)

Updates `arbitrary` from 1.3.1 to 1.3.2
- [Changelog](https://github.com/rust-fuzz/arbitrary/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-fuzz/arbitrary/compare/derive_arbitrary@1.3.1...v1.3.2)

Updates `futures-util` from 0.3.28 to 0.3.29
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.28...0.3.29)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: arbitrary
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: futures-util
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 22:03:34 -04:00
dependabot[bot]
fc9fef30a2
build(deps): bump the crypto group with 1 update (#241)
Bumps the crypto group with 1 update: [rustls](https://github.com/rustls/rustls).

- [Release notes](https://github.com/rustls/rustls/releases)
- [Commits](https://github.com/rustls/rustls/compare/v/0.21.7...v/0.21.8)

---
updated-dependencies:
- dependency-name: rustls
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: crypto
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 22:03:10 -04:00
Benoît CORTIER
a3116686f5 fix(pdu)!: remove unused DynamicChannels type alias
I don’t think this was ever used, but still a breaking change.
2023-10-30 18:24:00 -04:00
Benoît CORTIER
291dbe6bc5 fix(pdu): safer remaining methods for cursors
Returns an empty slice instead of panicking when the cursor position
is out of bounds.
2023-10-30 18:24:00 -04:00
Benoît CORTIER
39ecfb1368 style: run cargo +nightly fmt 2023-10-30 18:24:00 -04:00
Benoît CORTIER
40740169e0 feat: add new_from_leftover on Framed 2023-10-30 18:24:00 -04:00
Benoît CORTIER
f834305563 refactor(connector): use #[non_exhaustive] on type tokens 2023-10-30 18:24:00 -04:00
Benoît CORTIER
5513546d89 test: improve performance for property tests
Set the opt-level to 3 as suggested by the proptest book.

Reference:
- https://proptest-rs.github.io/proptest/proptest/tips-and-best-practices.html#setting-opt-level
2023-10-30 18:24:00 -04:00
Benoît Cortier
39d5ded239
docs: add "on bit flags" section (#237) 2023-10-30 22:09:18 +00:00
Isaiah Becker-Mayer
9859cb7518
feat(rdpdr): DeviceCreateRequest and DeviceCreateResponse (#235) 2023-10-30 18:15:25 +00:00
Benoît Cortier
8b359ff4a3
fix(pdu): bubble up X509 cert parsing error source (#236)
The source for the certificate parsing error was discarded. This
patch is modifying the ServerLicenseError::InvalidX509Certificate
variant so that it is properly bubbled up.

This will help us gather troubleshooting information from our
end users.
2023-10-30 16:46:07 +02:00
Benoît Cortier
9e287b6cf7
fix(cliprdr): ensure string padding is zeroed (#239) 2023-10-30 16:38:48 +02:00
Benoît Cortier
7d648ef56e
refactor: use u16 for pointer position (#240)
This removes some as casts that were never required.
2023-10-30 16:36:37 +02:00
Isaiah Becker-Mayer
2075833ff2
feat(rdpdr): drive announce (#234)
Initial work for drive redirection.

Co-authored-by: Benoît CORTIER <bcortier@proton.me>
2023-10-25 04:04:28 +00:00
dependabot[bot]
a2cb31a3d7
build(deps): bump the patch group with 2 updates (#232)
Bumps the patch group with 2 updates: [tracing](https://github.com/tokio-rs/tracing) and [thiserror](https://github.com/dtolnay/thiserror).


Updates `tracing` from 0.1.39 to 0.1.40
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.39...tracing-0.1.40)

Updates `thiserror` from 1.0.49 to 1.0.50
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.49...1.0.50)

---
updated-dependencies:
- dependency-name: tracing
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 20:41:22 -04:00
dependabot[bot]
a086e94d0b
build(deps-dev): bump postcss in /web-client/iron-svelte-client (#228)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.19 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.19...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 11:52:07 -04:00
dependabot[bot]
dec61fe690
build(deps-dev): bump vite in /web-client/iron-svelte-client (#229)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 3.2.4 to 3.2.7.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v3.2.7/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v3.2.7/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 11:49:33 -04:00
Isaiah Becker-Mayer
47bc49b50e
feat: smartcard pin, autologon, GetReaderIconCall and GetReaderIconReturn (#230)
Includes #231
2023-10-20 16:35:19 -04:00
Isaiah Becker-Mayer
a29ff47f49
feat(rdpdr): WriteCacheCall (#227) 2023-10-17 22:53:18 -04:00
Isaiah Becker-Mayer
ce0d7c1442
feat(rdpdr): ReadCacheCall and ReadCacheReturn (#226) 2023-10-17 22:04:12 -04:00
Isaiah Becker-Mayer
ca46230453
feat(rdpdr): GetDeviceTypeIdCall and GetDeviceTypeIdReturn (#225) 2023-10-17 19:22:29 -04:00
Isaiah Becker-Mayer
0799bbc73c
feat(rdpdr): add EndTransaction Disconnect Cancel and IsValidContext (#224) 2023-10-17 21:56:57 +00:00
dependabot[bot]
10612cda8d
build(deps): bump the patch group with 5 updates (#223)
Bumps the patch group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [tracing](https://github.com/tokio-rs/tracing) | `0.1.37` | `0.1.39` |
| [bitflags](https://github.com/bitflags/bitflags) | `2.4.0` | `2.4.1` |
| [arbitrary](https://github.com/rust-fuzz/arbitrary) | `1.3.0` | `1.3.1` |
| [async-trait](https://github.com/dtolnay/async-trait) | `0.1.73` | `0.1.74` |
| [time](https://github.com/time-rs/time) | `0.3.29` | `0.3.30` |


Updates `tracing` from 0.1.37 to 0.1.39
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.37...tracing-0.1.39)

Updates `bitflags` from 2.4.0 to 2.4.1
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/2.4.0...2.4.1)

Updates `arbitrary` from 1.3.0 to 1.3.1
- [Changelog](https://github.com/rust-fuzz/arbitrary/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-fuzz/arbitrary/compare/v1.3.0...derive_arbitrary@1.3.1)

Updates `async-trait` from 0.1.73 to 0.1.74
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.73...0.1.74)

Updates `time` from 0.3.29 to 0.3.30
- [Release notes](https://github.com/time-rs/time/releases)
- [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md)
- [Commits](https://github.com/time-rs/time/compare/v0.3.29...v0.3.30)

---
updated-dependencies:
- dependency-name: tracing
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: bitflags
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: arbitrary
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: time
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-16 23:43:53 +00:00
Isaiah Becker-Mayer
40e5581de3
feat: adds AsAny bound on CliprdrBackend (#219) 2023-10-16 19:40:20 +00:00
Benoît Cortier
a4fee87c49
ci: make sure the coverage workflow is disabled for forks (#222) 2023-10-16 11:31:40 -04:00
Isaiah Becker-Mayer
78caad219a
feat(rdpdr): ContextCall (#218) 2023-10-13 12:28:28 -04:00
Isaiah Becker-Mayer
8b5cdc914d
feat(rdpdr): StatusCall and StatusReturn (#216) 2023-10-11 23:26:25 +00:00
Isaiah Becker-Mayer
3c593970f2
feat(rdpdr): TransmitCall and TransmitReturn (#215) 2023-10-11 18:12:58 -04:00
Isaiah Becker-Mayer
94673ece73
feat(rdpdr): HCardAndDispositionCall (#214) 2023-10-11 02:50:05 +00:00
Isaiah Becker-Mayer
b130b2e374
feat(rdpdr): ConnectCall and ConnectReturn (#210)
This also refactors rpce and ndr into their own files.
2023-10-11 01:02:14 +00:00
Isaiah Becker-Mayer
b70908d321
refactor: remove Sync bound from Sequence and downstream traits (#209) 2023-10-10 11:05:37 -04:00
dependabot[bot]
aa6f9e286e
build(deps): bump byteorder from 1.4.3 to 1.5.0 (#213)
Bumps [byteorder](https://github.com/BurntSushi/byteorder) from 1.4.3 to 1.5.0.
- [Changelog](https://github.com/BurntSushi/byteorder/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/byteorder/compare/1.4.3...1.5.0)

---
updated-dependencies:
- dependency-name: byteorder
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-09 19:45:27 -04:00
dependabot[bot]
a5543e6419
build(deps): bump tokio from 1.32.0 to 1.33.0 (#212)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.32.0 to 1.33.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.32.0...tokio-1.33.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-09 19:45:12 -04:00
dependabot[bot]
1192f54bcc
build(deps): bump the patch group with 3 updates (#211)
Bumps the patch group with 3 updates: [semver](https://github.com/dtolnay/semver), [num-derive](https://github.com/rust-num/num-derive) and [num-traits](https://github.com/rust-num/num-traits).


Updates `semver` from 1.0.19 to 1.0.20
- [Release notes](https://github.com/dtolnay/semver/releases)
- [Commits](https://github.com/dtolnay/semver/compare/1.0.19...1.0.20)

Updates `num-derive` from 0.4.0 to 0.4.1
- [Changelog](https://github.com/rust-num/num-derive/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-derive/compare/num-derive-0.4.0...num-derive-0.4.1)

Updates `num-traits` from 0.2.16 to 0.2.17
- [Changelog](https://github.com/rust-num/num-traits/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-traits/compare/num-traits-0.2.16...num-traits-0.2.17)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: num-derive
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: num-traits
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-09 19:44:58 -04:00
Isaiah Becker-Mayer
bf05ef8749
feat(rdpdr): GetStatusChangeCall and GetStatusChangeReturn (#208) 2023-10-07 03:31:21 +00:00
Benoît CORTIER
774a8634da ci: enable dependabot pull requests 2023-10-05 10:58:16 -04:00
Benoît CORTIER
c1802b625e test(graphics): save snapshots for RLE test
Instead of directly depending on the unmaintained rdp-rs to decompress
RLE tiles dynamically, a few snapshots are saved to test against.

The benefit is to remove a lot of outdated dependencies from our
(developement) dependency graph.
2023-10-05 10:58:16 -04:00
Benoît CORTIER
b6407a688a build: update dependencies 2023-10-05 10:58:16 -04:00
Benoît CORTIER
622a0457bc build: relax some dependencies
In general we don’t need to specify as far as the patch version for
our dependencies.
2023-10-05 10:58:16 -04:00
Isaiah Becker-Mayer
7fa43a594e
feat(rdpdr): ListReadersCall and ListReadersReturn (#206) 2023-10-05 10:11:16 -04:00
Isaiah Becker-Mayer
cbc7b6afdc
feat(rdpdr): consolidate RdpdrBackend smartcard calls into a single function (#204) 2023-10-05 03:28:41 +00:00
Benoît Cortier
3d98cd1d94
refactor: enable elided_lifetimes_in_paths lint (#203)
Hiding the lifetimes is making the code less obvious.

That’s not a problem for types that I know about intimately such as
`ReadCursor` or `WriteCursor`, but I actually found myself surprised
more than once when reading code I didn’t authored, discovering  later
there was in fact a hidden lifetime parameter.
I expect this problem to be worse for someone not familiar with
our codebase.

I understand this lint is "allow" by default because in some cases
it leads to writing unergonomic ugly code when a type has many generic
lifetimes parameters:

```
TyCtxt<'_, '_, '_>
```

However we _never_ work with more than one generic lifetime parameter in
IronRDP codebase, so it seems to me that the tradeoff towards clarity is
worth it, in our case.
2023-10-04 09:14:36 -07:00
Benoît Cortier
c670ab4331
feat(error): add Source trait (#202) 2023-10-03 23:39:55 +00:00
Isaiah Becker-Mayer
25d88c7f81
feat: RDPDR next steps (#201)
Adds `DeviceIoRequest`, `DeviceControlResponse` and the
beginnings of `RdpdrBackend`.
2023-10-03 23:11:48 +00:00
Isaiah Becker-Mayer
d8c6efa3d1
feat(rdpdr): add decoding for ServerDeviceAnnounceResponse (#199) 2023-10-02 15:08:21 +00:00
Benoît Cortier
6283e37937
refactor: check for additional lints (#200) 2023-10-02 13:46:05 +03:00
Vladyslav Nikonov
6b660fa298
feat(cliprdr): Clipboard support for Windows (#194)
Includes refactoring changes by @ibeckermayer from #196 and #197

Co-authored-by: Isaiah Becker-Mayer <isaiah@goteleport.com>
2023-09-27 14:45:47 -04:00
Isaiah Becker-Mayer
fe1567c887
feat(rdpdr): DR_CORE_SERVER_CLIENTID_CONFIRM and DR_CORE_DEVICELIST_ANNOUNCE (#193)
Adds handling for `DR_CORE_SERVER_CLIENTID_CONFIRM` and `DR_CORE_DEVICELIST_ANNOUNCE`/`DR_CORE_DEVICELIST_ANNOUNCE_REQ`.
The next steps in the rdpdr initialization sequence.
2023-09-20 18:29:01 -04:00
Benoît Cortier
86b86ba343
doc: elaborate high-level documentation (#192) 2023-09-18 19:03:39 +00:00
Benoît Cortier
babbd68af5
feat(blocking): initial implementation (#158) 2023-09-11 15:47:26 +00:00
Benoît Cortier
9d33cad303
feat(web): use softbuffer to draw into the canvas (#191)
Instead of copying and sending image buffers to JavaScript, the WASM
module now draws into the canvas by itself. This removes some overhead
associated with the previous approach and open the door for further
optimizations.

In order to achieve good performance, the newest API of
`softbuffer@0.3.0` is used: the "owned buffer" that can be written into
by us with direct access and `present_with_damage` to apply partial
updates. The presentation itself is currently not yet "no-copy" in
the case of the web backend because the current API of `softbuffer` is
expecting a pixel buffer in the BGRX format while the underlying canvas
can only takes RGBA pixels. There is an open issue for this.

There was a bug with the `present_with_damage` implementation for the
web backend. I fixed the issue and opened a PR to upstream the patch.
The cargo dependency patch will be removed once the fix is published
on crates.io.

Issue: ARC-164
2023-09-09 00:16:27 +03:00
Benoît Cortier
783167f23d
feat: support for both Send and !Send Framed impls (#189)
Using GATs we achieve genericity over Send and Sync marker traits.
The `FramedRead` and `FramedWrite` traits can now be used in both
single-threaded and multi-threaded contexts.
2023-09-05 21:52:52 -04:00
Marc-Andre Lureau
37ac7052aa
fix(pdu): don't be strict about platform type (#184)
Allow values outside the range of known values. FreeRDP has been using
0xFFFF - N for custom values.
2023-09-05 15:53:27 -04:00
Vladyslav Nikonov
eb049f69db
fix: remove redundant eprintln! (#190) 2023-09-05 15:51:08 -04:00
Isaiah Becker-Mayer
d0eed25157
feat(rdpdr): Core Capability PDUs and refactoring ironrdp-rdpdr internals (#186)
Adds
- [2.2.2.7 Server Core Capability Request (DR_CORE_CAPABILITY_REQ)](702789c3-b924-4bc2-9280-3221bc7d6797)
- [2.2.2.8 Client Core Capability Response (DR_CORE_CAPABILITY_RSP)](f513bf87-cca0-488a-ac5c-18cf18f4a7e1)

This also refactors rdpdr code (inspired by @pacmancoder's design) such that all PDUs
are packed into an `RdpdrPdu` enum, and only that enum is `PduEncode`/`PduDecode`.
2023-08-29 14:09:43 -04:00
Benoît Cortier
d466861a89
build(client): update softbuffer dependency (#187) 2023-08-29 15:28:36 +00:00
Benoît Cortier
aeec4c0a67
ci: disable coverage jobs when running on forks (#185) 2023-08-28 17:50:24 +00:00
Vladyslav Nikonov
cd91756305
feat: CLIPRDR initialization sequence (#182) 2023-08-28 15:50:43 +00:00
Isaiah Becker-Mayer
d51fb6dacb feat: RDPDR static channel scaffolding 2023-08-26 04:37:13 -04:00
Benoît CORTIER
2938db416b feat: API for custom static virtual channels
Issue: ARC-147
Co-authored-by: Isaiah Becker-Mayer <isaiah@goteleport.com>
2023-08-26 04:37:13 -04:00
Benoît CORTIER
ea6eea96fa docs(pdu): documentation about library design 2023-08-26 04:37:13 -04:00
Mihnea Buzatu
74e95f692a
feat: initial server support (#167) 2023-08-25 09:35:00 -04:00
Benoît Cortier
1132126c2a
ci(cov): use GITHUB_TOKEN instead of DEVOLUTIONSBOT_TOKEN (#179) 2023-08-24 09:56:12 -04:00
Benoît Cortier
55176ff99b
refactor: write_padding! and read_padding! macros (#177)
Macros ensuring optimal handling of padding at compile time.
2023-08-15 21:36:53 +03:00
Vladyslav Nikonov
4c38be29c7
feat: PDUs for CLIPRDR channel (#170)
Issue: #107
2023-08-15 13:53:07 -04:00
Benoît Cortier
0b408200a8
ci: wrong comparison operator for coverage diff (#176) 2023-08-15 10:50:00 -05:00
Benoît Cortier
38b3b4887a
chore(deps): bump sspi to 0.10.1 (#175) 2023-08-14 20:45:32 +00:00
Benoît Cortier
e10e2f4d91
ci: adjust slightly coverage task (#174) 2023-08-13 19:45:21 -05:00
Benoît Cortier
dbbd168b97
fix(web): detect errors when opening WebSocket (#173)
Ideally, when the WebSocket can’t be opened, the call to
`WebSocket::open` should fail with details on why is that (e.g., the
proxy hostname could not be resolved, proxy service is not running),
but errors are neved bubbled up in practice, so instead we poll the
WebSocket state until we know its connected (i.e., the WebSocket
handshake is a success and user data can be exchanged).
2023-08-11 17:04:08 -05:00
Benoît Cortier
bcc4e35ecb
build(deps): update sspi-rs (#169) 2023-08-08 16:29:02 +00:00
Vladyslav Nikonov
1f401a1350
feat: pointer processing logic (#168)
FastPath pointer messages handling:

- Add pointer messages handling in `ironrdp-session`, `ironrdp-client` and `ironrdp-web`
  - Supported bpp's: 1, 16, 24, 32 (8bpp is not supported yet, palette messages handling
    should be added first)
  - Alpha blending is supported, and done via software-based compositing
  - Inverted cursor pixels are supported (FreeRDP do not support them correctly)
  - Large pointers are supported (FreeRDP crashes on them)
  - Cursor caching is supported
- Add new CLI argument to enable/disable pointer rendering (--no-server-pointer)
- `session`/`client` refactoring to facilitate multiple types of session output updates besides
  framebuffer update (e.g. request to hide/show system pointer on client)
- Minor changes in web client typescript code to hide/show cursor on canvas

Closes #108
2023-08-04 13:55:11 -04:00
Benoît Cortier
2cb0b476b5
feat: enable additional sspi features (KDC) (#154)
* feat: enable additional sspi features (KDC)

- Actually implement sspi-rs’s `NetworkClient` trait in `ironrdp-web`
- Enable the `dns_resolver` feature for `ironrdp-client` (the native client)

* Updated code to use new dev version of sspi-rs

* Updated sspi-rs to 0.9.0

---------

Co-authored-by: Vladyslav Nikonov <mail@pacmancoder.xyz>
2023-07-24 10:42:41 -04:00
Buzatu Mihnea
25bf6d4ca3
fix(pdu): ensure_enough before trying to parse tpkt header (#166) 2023-07-17 12:52:50 -04:00
Vladyslav Nikonov
20312a678e
feat(pdu): pointer messages PDUs parsing support
* Add cursor messages PDUs parsing support
* Refactor the `basic_output` module to use new error-handling logic
2023-07-17 17:59:53 +03:00
Buzatu Mihnea
7e4c216249
fix(connector): client should also join user channel id (#164) 2023-07-14 06:51:07 -04:00
Buzatu Mihnea
c19f559e3c
fix(pdu): buffer_length for bitmap updates (#162) 2023-07-12 16:48:11 +00:00
Benoît Cortier
709f79558a
build(fuzz): fix build (#163)
Breakage was caused by some bad interaction with a
transitive dependency (proc-macro2).

This is simply updating the Cargo.lock by running `cargo update`.
2023-07-12 15:16:44 +00:00
Benoît Cortier
687a5c3311
test(pdu): PCB sample from actual payload (#157) 2023-06-26 20:30:33 -04:00
Nicolas Girot
feee4f68d6
fix(web-client): scroll and shutdown (#156)
Issue: DPS-8268
2023-06-20 16:35:54 -04:00
Marc-Andre Lureau
e7be7a54e1
fix(pdu): NegoRequestData is optional (#151)
The fields routingToken and cookie are optional.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2023-06-16 14:08:43 +00:00
Benoît CORTIER
f209c9a77b ci(fuzz): minimize corpus only for current target 2023-06-15 15:08:05 -04:00
Benoît CORTIER
a4a26d315e fix(pdu): panics in nego module found by fuzzing
Fixes #152
2023-06-15 15:08:05 -04:00
Benoît Cortier
b98443b407
chore: fix xtask for windows (#150) 2023-06-14 22:16:24 +00:00
Nicolas Girot
56e10e1497
feat(web-client): support new builder parameters (#149) 2023-06-12 16:19:14 -04:00
Benoît Cortier
1655955c1f
feat(web): clarify error on proxy connection (#148)
WebSocket state is checked after `WebSocket::open` is called.
2023-06-08 18:26:31 -04:00
Nicolas Girot
93d5ca739a
chore(iron-remote-gui): bump version (#147) 2023-06-08 15:56:34 +00:00
Vladyslav Nikonov
4844e77b7f
fix(graphics): invalid YCoCg color conversion (#146)
Closes #141
2023-05-23 11:22:30 -04:00
Benoît CORTIER
9aaf05ef73 ci: coverage reporting in PRs
Adds a new workflow reporting code coverage when opening a pull request.

The idea is to help find blind spots in our test suites and inform where testing
efforts should go, but there is no hard requirement related to the numbers.
We are allowed to merge pull requests regardless of the coverage percentage,
and even if the coverage workflow fails.

Past coverage data is stored in a separate branch (cov-data).
It gets updated when the workflow run against master.
2023-05-23 08:31:19 -04:00
Benoît CORTIER
5d88531259 ci: optimize speed and cache 2023-05-23 08:31:19 -04:00
Benoît CORTIER
493e8f13a4 ci: add web client check job 2023-05-23 08:31:19 -04:00
Isaiah Becker-Mayer
124c3aa251
feat: default feature in ironrdp-tls (#144) 2023-05-19 17:32:06 -04:00
Isaiah Becker-Mayer
9b7bad65e5
fix: change read_pdu to return BytesMut (#143) 2023-05-19 09:44:21 -04:00
Benoît Cortier
310df4c319
feat(web): configurable clientName and default to 16bpp (#142)
Changes IronRDP web module:

- `clientName` field in Client Core Data is now configurable (but
  defaults to "ironrdp-web")
- Set supported bitmap color depth to 16bpp
2023-05-18 15:40:07 +00:00
Benoît Cortier
c0f5fd4c50
feat(connector): support for strong asymmetric keys (#138)
This flag is used to indicate that the client supports
asymmetric keys larger than 512 bits for use with the
Server Certificate (section 2.2.1.4.3.1) sent in the Server Security
Data block (section 2.2.1.4.3).

This is supported by IronRDP.
2023-05-16 19:48:27 -04:00
Benoît Cortier
f5a6ac7751
fix(pdu): panics in mcs module found by fuzzing (#137) 2023-05-16 23:40:07 +00:00
Benoît Cortier
0125089bc6
fix: proprietary certificate public key extraction (#134)
Also commented out some properties advertising not yet supported
features in GCC blocks.
2023-05-16 18:54:48 -04:00
Benoît Cortier
0b20b56068
fix(pdu): panic in mcs module found by fuzzing (#133) 2023-05-16 16:19:22 +00:00
Marc-André Moreau
9c3ec78588 fix: GCC client, 16/32bpp, RemoteFX rectangles
Co-authored-by: Benoît CORTIER <bcortier@proton.me>
2023-05-15 23:45:22 -04:00
Benoît CORTIER
cf2287739d refactor: error handling
Base all library errors on `ironrdp_error::Error`, a lightweight and
`no_std`-compatible generic `Error` type.
A custom consumer-defined type (such as `PduErrorKind`) for
domain-specific details is wrapped by this type.
2023-05-15 23:45:22 -04:00
Benoît CORTIER
8857c3c25e docs: comment on string_len in client_info module
Comment says it all. This code should be fixed at some point.
2023-05-11 19:41:11 -04:00
Benoît CORTIER
845e394841 fix(ironrdp-pdu): out of bounds read in mcs module 2023-05-11 19:41:11 -04:00
Benoît CORTIER
04924757cb test: extract fuzzing oracles into another crate 2023-05-11 19:41:11 -04:00
Benoît CORTIER
60bc72f873 test: organize integration tests into single crate
Put all integration tests in a single crate, and organized in modules.

This is similar to what was done in `cargo` repository:
https://github.com/rust-lang/cargo/pull/5022#issuecomment-364691154

```
$ rustup show
stable-x86_64-unknown-linux-gnu (default)
rustc 1.69.0 (84c898d65 2023-04-16)
```

Run on a recent high-end laptop:
12th Gen Intel(R) Core(TM) i9-12900HK

Original (multiple integration binaries):

- `cargo clean && cargo test --no-run`: 62.893s
- `cargo clean && cargo build --lib`: 54.959s
- `cargo clean && cargo build --bins`: 55.933s
- `cargo test --no-run` (after `cargo build`): 14.472s
- `cargo test` (after `cargo test --no-run`): 1.786s
- `du -hs target/`: 4.2G

After (ironrdp-testsuite):

- `cargo clean && cargo test --no-run`: 41.157s (crates with no tests are ignored)
- `cargo clean && cargo build --lib`: 53.983s
- `cargo clean && cargo build --bins`: 54.482s
- `cargo test --no-run` (after `cargo build`): 12.915s
- `cargo test` (after `cargo test --no-run`): 0.240s
- `du -hs target/`: 3.4G

Absolute diff:
- `cargo clean && cargo test --no-run`: -21.736s
- `cargo clean && cargo build --lib`: -0.976s
- `cargo clean && cargo build --bins`: -1.451s
- `cargo test --no-run` (after `cargo build`): -1.557s
- `cargo test` (after `cargo test --no-run`): -1.546s
- `du -hs target/`: -0.8G

Relative diff (%):
- `cargo clean && cargo test --no-run`: -34.5%
- `cargo clean && cargo build --lib`: -1.77%
- `cargo clean && cargo build --bins`: -2.59%
- `cargo test --no-run` (after `cargo build`): -10.7%
- `cargo test` (after `cargo test --no-run`): -86.5%
- `du -hs target/`: -19.0%
2023-05-11 19:41:11 -04:00
Isaiah Becker-Mayer
36a210aae9
fix(web-client): paths to web crate and unsupported call (#128)
Co-authored-by: Benoît CORTIER <bcortier@proton.me>
2023-05-11 16:00:04 +00:00
Benoît CORTIER
20e6025c6c ci: run fuzz jobs longer and in parallel 2023-05-10 13:58:53 -04:00
Benoît CORTIER
f3d17d3d6c docs: update list of supported codecs 2023-05-10 13:58:53 -04:00
Benoît CORTIER
368582ec5a chore(xtask): extend CLI options
- It is now possible to pass a custom fuzzing duration and target
- Adds a `coverage install` action
2023-05-10 13:58:53 -04:00
Benoît CORTIER
fce63eaf57 ci: fix fuzz workflow 2023-05-10 13:58:53 -04:00
Benoît Cortier
c81f0f0c0d
ci: separate workflow for fuzzing using corpus (#124)
Fetching the corpus in the main CI worflow is a problem because
it will fail when the azure storage is modified during the download:

> ERROR: The specified blob does not exist.
> RequestId:76c40723-901e-0098-38b9-8215c9000000
> Time:2023-05-09T21:03:10.8992962Z
> ErrorCode:BlobNotFound

This occurs when there are multiple runs at the same time. This
happens for example when two pull requests are opened.
This will cause CI to go red for no good reason.

With this patch, main CI will simply run the fuzz targets without
using the corpus, as a sanity check. Then, another workflow will
run at 03:12 AM UTC on Sunday, Monday, and Saturday using the corpus
from azure storage.

Future work: make the scheduled run fuzz longer and in parallel.
2023-05-10 09:25:41 -04:00
Benoît Cortier
55d11a5000
refactor: clarify project architecture (#123)
> Make the root of the workspace a virtual manifest. It might
> be tempting to put the main crate into the root, but that
> pollutes the root with src/, requires passing --workspace to
> every Cargo command, and adds an exception to an otherwise
> consistent structure.

> Don’t succumb to the temptation to strip common prefix
> from folder names. If each crate is named exactly as the
> folder it lives in, navigation and renames become easier.
> Cargo.tomls of reverse dependencies mention both the folder
> and the crate name, it’s useful when they are exactly the
> same.

Source:
https://matklad.github.io/2021/08/22/large-rust-workspaces.html#Smaller-Tips
2023-05-09 21:00:07 +00:00
Benoît Cortier
a2c6a5c124
feat(web): configurable initial desktop size (#122)
Exposes a new method from the SessionBuilder: `desktop_size`
This function takes a `DesktopSize` object that will be used during
the handshake.

Issue: ARC-136
2023-05-09 15:42:41 +00:00
Vladyslav Nikonov
71d7c0c651
fix: run typos check (#120)
- Fixes a bunch of typos
- Moves gigantic HEX-string represented binaries to separate `*.bin` files in crates\graphics\src\zgfx\mod.rs (it was messing with typos check)
2023-05-09 15:18:51 +00:00
Benoît Cortier
e99e6c7b55
fix: sanitize server name before sspi (#121) 2023-05-09 12:28:03 +03:00
Vladyslav Nikonov
137556013f
Add support for RDP6 32bpp bitmaps (#118)
This adds support for RDP6 32bpp bitmaps.

- RDP6 RLE implementation
- `RDP6_BITMAP_STREAM` pdu parsing
- New CLI arguments to select color depth and color reduction
- Unit tests and fuzzing for implemented functionality

Test bpp32 mode without lossy compression (RGB  colorspace)
```
cargo run -p ironrdp-client -- -u vmbox -p password --color-depth=32 <IP_ADDRESS>
```

Test bpp32 mode with lossy compression (YCoCg colorspace, subsampling, color loss)
```
cargo run -p ironrdp-client -- -u vmbox -p password --color-depth=32 --lossy-bitmap-compression <IP_ADDRESS>
```

Issue: ARC-149
2023-05-08 09:43:53 -04:00
Benoît Cortier
f11131b18a
fix: remove forgotten dbg! invocations (#117) 2023-05-05 11:10:32 -04:00
Benoît CORTIER
95faf8bbc6 feat!: implement new traits on PreconnectionBlob 2023-05-04 21:27:44 -04:00
Benoît CORTIER
5e9990ce38 refactor: use RustCrypto’s x509-cert crate
This replaces `x509-parser` crate with `x509-cert` crate:
- `x509-cert` crate has less dependencies, and
- we have common dependencies with it (`der` crate).
2023-05-04 21:27:44 -04:00
Benoît CORTIER
a8b9cfe99c docs: update ironrdp-pdu README.md 2023-05-04 21:27:44 -04:00
Benoît CORTIER
dd7413958d feat(web): shutdown placeholder method
This adds the `shutdown` method to `Session`.
The body is not yet implemented, but frontend code can be modified
to call it as of now.
Once the implementation is complete, no change on frontend code
will be required.
2023-05-04 21:27:44 -04:00
Benoît CORTIER
ccdd3825b0 refactor: connection sequence as state machine
- Connection sequence is now implemented as an instrumentable state
  machine

- Improved errors

Issue: ARC-146
Issue: ARC-133
2023-04-28 18:00:10 -04:00
Benoît CORTIER
440eb655ef build: update dependencies 2023-04-28 18:00:10 -04:00
Benoît CORTIER
4516521412 chore: use cargo inheritance 2023-04-28 18:00:10 -04:00
Benoît CORTIER
c7ddcb2a2b feat: use tracing instead of log 2023-04-28 18:00:10 -04:00
Benoît CORTIER
01a10939bd ci: use cargo xtask commands 2023-04-28 18:00:10 -04:00
Nicolas Girot
f3c06b96dc
refactor(web-client): remove Tauri abstraction (#102)
This patch:

- Removes references to Tauri and (now unused) abstraction
- Remove Tauri references from package.json
2023-04-05 20:13:30 +00:00
Benoît Cortier
710a51f24a
refactor: re-organize workspace (#101)
- Remove Tauri client.

  It was useful when initially prototyping the Web Component, but now
  it’s more of a maintenance burden. It’s notably not convenient to
  check in CI. Since we now have another native alternative that does
  not require any GPU (`ironrdp-client`), it’s probably a good time to
  part ways.

- Move wasm package into the workspace.

- Move Rust crates into a `crates` subfolder.

- Introduce `xtask` crate for free-form automation.
2023-03-29 19:09:15 -04:00
Alex Yusiuk
4ad13df499
feat(ironrdp-client): add ability to collect sspi-rs logs (#100) 2023-03-29 22:34:07 +00:00
Benoît Cortier
eba943d812
feat(client): support for PixelDelta window event (#99) 2023-03-22 19:13:28 +00:00
Benoît CORTIER
0bbb518c69 fix(core): LogonErrorsInfo parsing
The "ErrorNotificationData" may contain the session identifier instead
of an error code.
2023-03-21 19:40:41 -04:00
Benoît CORTIER
93bcea67ca fix(client): graceful exit when RDP thread terminates 2023-03-21 19:40:41 -04:00
Benoît CORTIER
843342b0a7 refactor(rdcleanpath): clarify RDCleanPath code
- Renames ironrdp-devolutions-gateway -> ironrdp-rdcleanpath
- Adds a RDCleanPath helper enum to simplify user code
2023-03-21 19:40:41 -04:00
Benoît CORTIER
ef1c191fc8 docs(ironrdp-devolutions-gateway): rustdoc for RDCleanPath structure 2023-03-21 19:40:41 -04:00
Benoît CORTIER
46c8602563 fix(wasm): treat empty domain name as None value 2023-03-21 19:40:41 -04:00
Nicolas Girot
39ecb06da0
Use the new api for hostname and domain (#97)
* Modify calls to use the new api

* Bump version
2023-03-21 08:55:15 -04:00
Benoît Cortier
2a842540b6
feat(wasm): API to provide target hostname and domain (#96) 2023-03-20 18:44:20 +00:00
Benoît Cortier
0953227700
fix(client): better address / hostname handling (#95) 2023-03-17 14:13:35 -04:00
Benoît CORTIER
db78d7e5d7 feat: softbuffer-based frontend
Issue: ARC-140
2023-03-17 11:38:51 -04:00
Benoît CORTIER
89b4f736fd refactor: rename ironrdp-gui-client to ironrdp-client-glutin 2023-03-17 11:38:51 -04:00
Nicolas Girot
33b7fe72d0 Bump iron-remote-gui version 2023-03-10 16:23:10 -05:00
Benoît Cortier
407cb24121
ci: fix cargo-fuzz installation (#92) 2023-03-10 15:38:45 -05:00
Benoît CORTIER
85b07450ae ci: use azure account storage to store fuzzing corpus 2023-03-10 14:20:30 -05:00
Benoît CORTIER
7d37967106 build: update Rust toolchain to 1.68.0 2023-03-10 14:20:30 -05:00
Benoît Cortier
89e791c727
fix(core): remove info log forgotten previously (#90) 2023-03-10 16:19:05 +00:00
Benoît CORTIER
0f3849999f chore(iron-remote-gui): bump version (0.4.5) 2023-03-10 10:47:42 -05:00
Benoît CORTIER
6d376a115f fix(core): bad compression flags and type decoding 2023-03-10 10:47:42 -05:00
Alex Yusiuk
4471253e6a
build: bump sspi dependency to 0.7 (#88) 2023-03-10 09:46:24 -05:00
Nicolas Girot
1f6ad789de
Bump version 0.4.4 (#87) 2023-03-08 08:37:12 -05:00
Benoît Cortier
c949f5b46c
fix(graphics): harden RLE implementation (#86)
Mostly added bound checks in case an invalid RLE-compressed bitmap is
received. Implementation have been fuzzed for a few hours in order to
find blind spots.
2023-03-07 07:01:22 -05:00
Nicolas Girot
68ebc674a5
fix(iron-remote-gui): border on hover and fit scale function (#85) 2023-03-06 16:02:00 +00:00
Zacharia Ellaham
9819b9a976
chore(iron-remote-gui): bump version (0.4.3) (#84) 2023-02-28 23:01:25 +00:00
Benoît Cortier
631bf82fec
fix: more resilient fast-path update handling (#83) 2023-02-28 21:45:36 +00:00
Benoît Cortier
5dd0382e43
fix: do not returns an error when compression is used (#82)
Sometimes the FastPathUpdatePdu header has the
COMPRESSION_USED bit flipped and it returns an error.
It should not.
2023-02-28 19:27:13 +00:00
Benoît Cortier
5b36fc42b5
docs: update section about supported codecs (#81) 2023-02-28 15:56:08 +00:00
Nicolas Girot
744445da68
chore(iron-remote-gui): bump version (#79) 2023-02-28 09:51:43 -05:00
Benoît Cortier
a7e30ca617
fix: bad width and height for Rectangle (#80)
Issue: ARC-135
2023-02-28 14:48:03 +00:00
Benoît Cortier
137c978997
feat: compressed RLE bitmap codec (#78)
Issue: ARC-112
2023-02-27 21:23:59 +00:00
Alex Yusiuk
5d987265f1
fix: build with native-tls feature (#77)
Building with native-tls is essential cause rustls can’t connect
to Windows 7 and 8.1 using enhanced SSL cipher suites.
2023-02-24 09:25:58 -05:00
Nicolas Girot
e2ee180f7e
chore(iron-remote-gui): bump version to 0.3.0 (#75) 2023-02-14 20:11:18 +00:00
Nicolas Girot
1857368053
feat(web): better error handling and various improvements (#74) 2023-02-14 19:48:06 +00:00
Benoît Cortier
2c2979d618
feat(wasm): improved error reporting stopgap (#73) 2023-02-13 22:14:38 +00:00
Nicolas Girot
236f42ec5c
feat(web): mouse wheel support (#72) 2023-02-13 16:50:03 -05:00
Benoît Cortier
d9e2de84e6
docs: update svelte README (#69) 2023-02-13 16:17:08 -05:00
Benoît Cortier
053bec42ef
fix(input): MousePdu wheel rotation units encoding (#71) 2023-02-13 21:12:17 +00:00
Nicolas Girot
145c905fc2
feat(web): add Kana modifier and use the sync lock key function (#70) 2023-02-13 17:05:05 +00:00
Benoît Cortier
42ce3f88b4
feat(input): horizontal and vertical mouse wheel rotations (#68)
Issue: ARC-131
2023-02-13 16:25:11 +00:00
Benoît Cortier
c270b1a239
docs: elaborate web client setup steps (gateway, token…) (#67) 2023-02-10 21:56:12 +00:00
Nicolas Girot
328879d5bd
feat(web): keyboard improvements (#66)
* Complete ScanCode and fix keyboard keypress/keydown
* Manage modifier key repetition
* Add ctrl-alt-del and meta
* Release all inputs on mouseout
* Use CTRL+ESC to display the start menu
* Implement some new modifier keys and lock keys

Co-authored-by: Benoît CORTIER <bcortier@proton.me>
2023-02-10 13:05:25 -05:00
Benoît Cortier
51016a872b
feat(input): synchronize event support (#65)
Issue: ARC-130
2023-02-10 16:20:44 +00:00
Benoît Cortier
d58a48f86c
feat(input): release all keys and buttons API (#64)
It is intented to be used when focus is lost.

Issue: ARC-132
2023-02-07 20:16:29 -05:00
Benoît CORTIER
5908f57bcf ci: update ubuntu runner 2023-02-07 15:52:42 -05:00
Benoît CORTIER
9b5afa5382 feat(input): support for extended keys
Issue: ARC-129
2023-02-07 15:52:42 -05:00
Nicolas Girot
2f78175c67 feat: IronRDP GUI WebComponent
Issue: DPS-7619
2023-02-06 19:41:26 -05:00
Benoît CORTIER
9d57b63a97 feat: basic input system (ASCII)
Issue: ARC-111
2023-02-06 19:41:26 -05:00
Benoît CORTIER
80f7b6d032 fix(wasm): do not send partial images to frontend
Slower, but workaround the issue we have when finding the largest
region changed on a new frame. This should be fixed later.
2023-02-06 19:41:26 -05:00
Benoît CORTIER
898fb8d40a refactor: fix clippy warnings 2023-01-26 22:20:43 -05:00
Benoît CORTIER
e01d3263d9 ci: check compilation for wasm module and Tauri app 2023-01-26 22:20:43 -05:00
Benoît CORTIER
7a744a217e feat: initial WASM module 2023-01-26 22:20:43 -05:00
Benoît CORTIER
3b1f969afa feat: Devolutions Gateway RDP extension
Issue: ARC-109
2023-01-26 22:20:43 -05:00
Benoît CORTIER
ea0e6a4fff refactor: architecture rework 2023-01-26 22:20:43 -05:00
Benoît CORTIER
d164087d1a build: update Rust toolchain to 1.67.0 2023-01-26 22:20:43 -05:00
Benoît Cortier
98c6a1e97e
build: fix tauri compilation (#59) 2023-01-16 21:37:00 +00:00
Benoît Cortier
aa85fe4869
refactor: split into multiple crates (#57) 2022-12-19 11:56:58 -05:00
Sandeep Bansal
28eb35f69b
feat: glutin-based GUI client (#55)
Co-authored-by: Sandeep Bansal <sandeep.bansal85@gmail.com>
2022-12-16 09:55:59 -05:00
Nicolas Girot
440f5de449
feat: change Angular client by Svelte Client (#56) 2022-12-15 09:03:50 -05:00
dependabot[bot]
d34870902e
build(deps): bump engine.io from 6.2.0 to 6.2.1 in /iron-web-client (#53)
Bumps [engine.io](https://github.com/socketio/engine.io) from 6.2.0 to 6.2.1.
- [Release notes](https://github.com/socketio/engine.io/releases)
- [Changelog](https://github.com/socketio/engine.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/engine.io/compare/6.2.0...6.2.1)

---
updated-dependencies:
- dependency-name: engine.io
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-28 11:14:30 -05:00
Nicolas Girot
217b763b18
Clean Tauri client (#54) 2022-11-23 15:56:25 +00:00
Nicolas Girot
19b513fee3 Initial web-based frontend
Co-authored-by: Zacharia Ellaham <zellaham@devolutions.net>
2022-11-15 19:13:34 -05:00
Benoît CORTIER
1dd5fab929 Initial wasm-based npm package 2022-11-15 19:13:34 -05:00
Benoît CORTIER
9ce61b4d47 Initial tauri backend for native GUI client
Co-authored-by: Nicolas Girot <ngirot@devolutions.net>
2022-11-15 19:13:34 -05:00
Benoît CORTIER
bc869fc3dc Refactor and break down ironrdp_client crate
`ironrdp_client` is broken down into two smaller crates:
- `ironrdp-cli`
- `ironrdp-session`

`ironrdp-session` is meant to be reused by the actual client implementations.
It’s purpose is to be a portable component providing higher-level
abstraction to the RDP protocol (async state machine, etc).
2022-11-15 19:13:34 -05:00
Alexandr Yusuk
5110d9bfae chore(ironrdp_client): update sspi-rs to 0.5.1 2022-11-14 12:05:21 -05:00
Alexandr Yusuk
f991685ffb chore(ironrdp_client): update sspi-rs version 2022-11-14 12:05:21 -05:00
Sandeep Bansal
3bd8ca28ec
Adding support for async RDP client (#48)
Co-authored-by: Sandeep Bansal <sandeep.bansal85@gmail.com>
2022-11-09 13:51:12 -05:00
Benoît Cortier
ad9ae95480
build: update rust-toolchain to 1.65 (#50) 2022-11-09 00:53:28 +00:00
devolutionsinfra
5dcde3745c chore: [skip ci] updating .github/CODEOWNERS (managed by devops) 2022-10-28 08:52:23 -04:00
Sandeep Bansal
659b3425bd Adding some missing message parser 2022-10-26 12:16:30 -04:00
Sandeep Bansal
6fda0a0d52
Improvements for dynamic channel handling (#46)
* Enable graphics pipeline

* Cargo fmt

Co-authored-by: Sandeep Bansal <sandeep.bansal85@gmail.com>
2022-10-07 23:48:28 -04:00
Sandeep Bansal
91e353d32b
build: update clap to 4.0 (#44) 2022-10-06 13:06:01 +00:00
Alex Yusiuk
67c6d28ffb
Replace Kerberos with the Negotiation module for CredSspClient (#43) 2022-06-17 15:21:41 -04:00
Alex Yusiuk
de839b4dfb
ironrdp_client: pass SPN to sspi (#42) 2022-05-31 11:54:44 -04:00
Alex Yusuk
b333b022c0
feat(ironrdp_client): Kerberos support (#41)
* Add support for Kerberos using `sspi-rs`
* Change the `CredSspClient` creation according to changes in the sspi-rs
* Add optional support for `native-tls`
2022-05-20 18:29:31 +00:00
Benoît CORTIER
2f089c9a8f ci: fix clippy job
`-D warnings` was missing.
2022-04-20 15:55:16 -04:00
Benoît CORTIER
0ae846b6c3 refactor: use new type for RDP version instead of enum
We don't actually need strictness of enums for this.
In fact, in this case, a new type pattern is more robust
because we are trying to allow as much as we can.

This prevents issues such as the one described in this PR:
https://github.com/Devolutions/IronRDP/pull/30
2022-04-15 15:12:18 -04:00
Benoît CORTIER
fd97d7eaf3 ci: use checkout@v3 2022-04-15 13:39:34 -04:00
Benoît CORTIER
e19b0af527 style: fix typo 2022-04-15 13:39:34 -04:00
Benoît CORTIER
2837d104bf ci: add rustfmt component if necessary 2022-04-15 13:39:34 -04:00
Benoît CORTIER
261630f03c build: add rust-toolchain.toml 2022-04-15 13:39:34 -04:00
Benoît CORTIER
d8f3c5dfb2 build: update to latest Rust edition (2021) 2022-04-15 13:39:34 -04:00
Benoît CORTIER
816a665e93 ci: add dependabot config
It is enabled only for security updates.
2022-04-15 13:39:34 -04:00
Benoît CORTIER
142cdc07ae ci: migrate to GitHub Actions 2022-04-15 13:39:34 -04:00
Benoît CORTIER
ad0112b47e style: run cargo fmt 2022-04-15 13:39:34 -04:00
Benoît CORTIER
7264c01712 refactor: fix clippy warnings
Issuing `cargo clippy --fix` did most of the job.
2022-04-15 13:39:34 -04:00
Benoît CORTIER
32311d2fc9 build: fix code using bitvec crate 2022-04-14 17:19:24 -04:00
Benoît CORTIER
c71e553537 build: update all dependencies using cargo-edit 2022-04-14 17:19:24 -04:00
Benoît CORTIER
2e8165d7ef test: update md-5 crate usage
Test wasn't updated and compilation was broken.
2022-04-14 17:19:24 -04:00
Daniel Heater
ad4099ae12
Update dependency versions and other minor cleanups (#36) 2022-04-08 14:58:06 -04:00
devolutionsinfra
8b69d46f98 chore: [skip ci] updating .github/CODEOWNERS (managed by devops) 2022-03-10 10:47:23 -05:00
Jean-Francois Theroux
8e9d8dc777 chore: [skip ci] updating .github/CODEOWNERS (managed by devops) 2022-03-10 10:11:12 -05:00
Jean-Francois Theroux
e44d5cd14e chore: [skip ci] updating .github/CODEOWNERS (managed by devops) 2022-03-03 16:15:50 -05:00
Randy Bobandy
d81760753e ci: updating .github/CODEOWNERS 2022-02-24 08:33:58 -05:00
Daniel Heater
a8b68aab8a
Slk 11764 (#35)
* Ignore IDE/editor generated files and log from command line execution

* Swallow non-zero first row size not zero error for bitmaps and make a best effort to continue running

* Don't use println.  Propagate bitmap error up to client and add handling there

* Use map_err to remap to a BitmapError to FastPathError
2022-01-11 20:30:59 -05:00
Daniel Heater
2b97e9c0c8
Fix spelling errors in Bitmap error codes (#33) 2022-01-10 10:42:12 -05:00
Marc-André Moreau
04711ff0bc ironrdp: bump to 0.4.2 2020-10-27 18:43:21 -04:00
Vladislav Nikonov
ca53209c84
Jet-related IronRDP bugfix (#32)
* Fixed parsing of Fast-Path packet with forced long length field

* cargo fmt, fix, clippy

* Implemented bitmap fast path packet parsing to fix Win Server 2016 TS connections

* cargo fmt/clippy

* Performed minor code refactoring

* Fixed typos; Formatted comments

* connection request parsing fix

* Fixed failing broken tests

* Fixed formatting
2020-10-19 13:17:53 -04:00
Marc-André Moreau
ecfdf9dd11
Merge pull request #31 from Devolutions/fix_invalid_date_decoding
Fix decoding for invalid SystemDate
2020-09-11 14:33:00 -04:00
Benoît CORTIER
23edaf8b64 Fix decoding for invalid SystemDate
As specified in [RDP protocol
doc](526ed635-d7a9-4d3c-bbe1-4e3fb17585f4)
we should be able to ignore invalid date encoding.
2020-09-11 14:27:55 -04:00
Marc-André Moreau
a5ae271a4e
Merge pull request #30 from Devolutions/2004-fix
Fix Windows 10 2004 update connectivity (RDP client or server)
2020-09-03 15:51:44 -04:00
Marc-André Moreau
6aaf18c971 ironrdp: correctly handle new or unknown RDP version values in GCC core data block 2020-09-03 15:48:47 -04:00
Marc-André Moreau
f47e7fb75d
Merge pull request #29 from Devolutions/preconnection-pdu-improvements
Preconnection PDU parsing improvements
2020-09-02 15:06:37 -04:00
Benoît CORTIER
67ca9a8818 ironrdp: bump version (0.4.1) 2020-09-02 14:31:24 -04:00
Marc-André Moreau
d8ac8b3849 ironrdp: fix handling of extra null terminators in preconnection pdu 2020-09-02 14:29:37 -04:00
Vladislav Nikonov
c1e26ea457 Changed Preconnection Pdu parsing logic 2020-09-02 14:17:05 -04:00
Marc-André Moreau
4e3a52b023
Merge pull request #27 from Devolutions/input-events-parsing
Implemented parsing of Input Events
2020-03-31 16:23:39 -04:00
Vladyslav Hordiienko
7f2b55d304 ironrdp: implement parsing of Input Events 2020-03-31 16:55:10 +03:00
Marc-André Moreau
25947f21a5
Merge pull request #26 from Devolutions/server-error-info
Server Set Error Info PDU
2020-03-30 10:32:39 -04:00
Vladyslav Hordiienko
72ed496113 ironrdp_client: integrate Server Set Error Info PDU 2020-03-30 17:01:01 +03:00
Vladyslav Hordiienko
c41bc76b8f ironrdp: implement Server Set Error Info PDU with detailed description 2020-03-30 17:00:44 +03:00
Sébastien Duquette
e33e499cd0
Merge pull request #25 from Devolutions/fuzz-preconnection-pdu
Fuzz preconnection pdu
2020-03-30 09:38:24 -04:00
Vladyslav Hordiienko
509a81b4fe ironrdp: fix Preconnection PDU to check the payload size 2020-03-30 14:51:23 +03:00
Sébastien Duquette
e2bc1eedc0 Fuzz PreconnectionPdu parsing 2020-03-27 15:54:26 -04:00
Marc-André Moreau
a5ba53328e
Merge pull request #23 from Devolutions/preconnection-pdu
Implement parsing of the Preconnection PDU
2020-03-27 14:23:34 -04:00
Marc-André Moreau
783c3bf1af
Merge pull request #22 from Devolutions/make-ironrdp-client-independent
Update IronRDP client to be able to be reused in web-based client
2020-03-27 11:02:52 -04:00
Vladyslav Hordiienko
5a984856fb ironrdp: implement parsing of the Preconnection PDU 2020-03-27 13:37:06 +02:00
Vladyslav Hordiienko
f9b5985dea ironrdp_client: move connection sequence processing to lib 2020-03-26 17:05:16 +02:00
Vladyslav Hordiienko
34fe59191b ironrdp_client: remove dependence on Rustls for connection sequence 2020-03-26 17:05:16 +02:00
Vladyslav Hordiienko
5247a3a198 ironrdp_client: move all independent from platform components to lib.rs file 2020-03-26 17:05:14 +02:00
Vladyslav Hordiienko
c070793fc4 fixup! ironrdp: make frame ID optional field for FrameMarker PDU 2020-03-26 14:08:52 +02:00
Marc-André Moreau
3e584e07e1
Merge pull request #21 from Devolutions/err-on-server-termination
Fix bug with RFX when IronRDP client fails on unexpected end of stream
2020-03-25 12:10:19 -04:00
Vladyslav Hordiienko
3372d1a17f ironrdp_client: integrate FrameMarker updates related to optional field of FrameMarker 2020-03-25 14:40:17 +02:00
Vladyslav Hordiienko
a465bf1262 ironrdp: make frame ID optional field for FrameMarker PDU 2020-03-25 14:40:17 +02:00
Vladyslav Hordiienko
ba6826c2d7 ironrdp_client: update Fast-Path processing to raise error on inconsistent update 2020-03-25 14:40:17 +02:00
Vladyslav Hordiienko
d4689087d0 ironrdp_client: add a print of clear error message on the server session termination 2020-03-25 14:40:17 +02:00
Marc-André Moreau
7a0c7880d2
Merge pull request #20 from Devolutions/rfx-fast-path-pdus-parsing
RFX & Fast-Path integration into IronRDP client
2020-03-19 10:52:51 -04:00
Vladyslav Hordiienko
8a90aaa7fc ironrdp_client: update README 2020-03-19 14:59:44 +02:00
Vladyslav Hordiienko
d1d6c6b04a ironrdp_client: remove save of image 2020-03-19 11:55:41 +02:00
Vladyslav Hordiienko
fb96b060b9 ironrdp_client: integrate RFX with Fast-Path, refactor x224 2020-03-19 11:45:11 +02:00
Vladyslav Hordiienko
f4532612af fuzz: add fuzzers for rfx, fast-path, surface commands 2020-03-19 11:45:11 +02:00
Vladyslav Hordiienko
750c4059df ironrdp: refactoring 2020-03-19 11:45:11 +02:00
Vladyslav Hordiienko
478ab33b4b ironrdp: implement Fast-Path with Surface Commands Update 2020-03-19 11:45:11 +02:00
Vladyslav Hordiienko
4fe236f698 ironrdp: add RFX HeaderMessage, FrameAcknowledge, refactor RFX decoding 2020-03-19 11:45:11 +02:00
Marc-André Moreau
7da7482c98
Merge pull request #19 from Devolutions/rfx-master
WIP: RFX implementation
2020-02-19 11:29:31 -05:00
Vladyslav Hordiienko
8c2984cc4c ironrdp: implement RFX decoding with bands reconstruction 2020-02-14 16:58:31 +02:00
Vladyslav Hordiienko
689364d3c2 ironrdp: update PduBufferParsing trait to use mutable reference on the slices 2020-02-14 15:52:14 +02:00
Vladyslav Hordiienko
8aee045218 ironrdp: implement parsing of MultifragmentUpdate, LargePointer capability sets 2020-02-03 12:39:37 +02:00
Vladyslav Hordiienko
3401593bc7 ironrdp: implement parsing of GFX PDUs 2020-02-03 12:39:37 +02:00
Vladyslav Hordiienko
57cd268d8d ironrdp: Increase the maximum certificate length 2020-01-22 13:49:25 +02:00
Sébastien Duquette
d0ca82fb73
Merge pull request #17 from Devolutions/fuzzer-fixes
Fix unbounded allocations discovered by fuzzing
2020-01-17 13:49:40 -05:00
Vladyslav Hordiienko
06f35ea957 ironrdp: add size limits in X509CertificateChain 2020-01-17 11:56:13 +02:00
Vladyslav Hordiienko
26896cd481 fixup! ironrdp: add allocations bounds according to the fuzzers results 2020-01-17 11:53:16 +02:00
Sébastien Duquette
1a3d907ac9 Add size limits in LogonInfoVersion2 2020-01-16 16:01:23 -05:00
Vladyslav Hordiienko
48e67c8e04 ironrdp: add allocations bounds according to the fuzzers results 2020-01-16 15:49:44 +02:00
Marc-André Moreau
75a4e33e5e
Merge pull request #16 from Devolutions/update-bitvec-crate-version
Updated bitvec crate the latest version
2020-01-15 09:20:52 -05:00
Vladyslav Hordiienko
7f9775480d ironrdp: update bitvec crate to 0.17.1, optimized ZGFX decompression up to 28% 2020-01-15 09:52:08 +02:00
Sébastien Duquette
b3864405cf
Merge pull request #15 from Devolutions/update-fuzzer
Update the fuzzer
2020-01-14 15:31:06 -05:00
Sébastien Duquette
62700600db Update the fuzzer 2020-01-14 15:15:11 -05:00
Marc-André Moreau
4665307f4c ironrdp: bump version to 0.4.0 2020-01-14 13:37:52 -05:00
Marc-André Moreau
1a3265ec0f
Merge pull request #13 from Devolutions/dvc-with-gfx-implementation
DVC with GFX implementation
2020-01-14 13:35:01 -05:00
Vladyslav Hordiienko
b85838f187 ironrdp_client: fix bug when DVC Data First is complete Data 2020-01-14 14:33:44 +02:00
Vladyslav Hordiienko
e5c6bf36a6 ironrdp_client: integrate refactoring of ironrdp crate 2020-01-13 09:42:26 +02:00
Vladyslav Hordiienko
58b5bb5297 ironrdp: refactoring: remove new methods where the fields are public and remove save of buffer instead of the buffer length 2020-01-13 09:42:26 +02:00
Vladyslav Hordiienko
b7773ab233 ironrdp: fix bug with DVC Data First PDU invalid DVC total message size error 2020-01-13 09:42:26 +02:00
Vladyslav Hordiienko
d0a9d0aa4c ironrdp_client: update crates versions 2020-01-13 09:42:26 +02:00
Vladyslav Hordiienko
04e7a12f74 ironrdp: update crates versions 2020-01-13 09:42:26 +02:00
Vladyslav Hordiienko
cd6ed50c0c ironrdp_client: implement processing of GFX PDUs 2020-01-13 09:42:26 +02:00
Vladyslav Hordiienko
9669f97c9b ironrdp_client: fix bug with supported color depths for GFX 2020-01-13 09:42:26 +02:00
Vladyslav Hordiienko
de2fffdc7a ironrdp: implement ZGFX 2020-01-13 09:42:26 +02:00
Vladyslav Hordiienko
ff4afe998e ironrdp: implement GFX PDUs parsing 2020-01-13 09:42:26 +02:00
Vladyslav Hordiienko
a9b88b30c8 ironrdp_client: integrate save session info parsing 2020-01-13 09:42:26 +02:00
Vladyslav Hordiienko
5e7ef6c78b ironrdp: implement parsing of session info PDU 2020-01-13 09:42:26 +02:00
Vladyslav Hordiienko
a866bbdc11 ironrdp_client: implement exchange of DVC messages 2020-01-13 09:42:26 +02:00
Vladyslav Hordiienko
c73f9b548d ironrdp_client: add transport for static and dynamic channels 2020-01-13 09:42:26 +02:00
Vladyslav Hordiienko
4aad704d30 ironrdp: add parsing of DVC PDUs 2020-01-13 09:42:11 +02:00
Vladyslav Hordiienko
7faedd2687 ironrdp: implement parsing of channel header and static virtual channel PDUs 2020-01-13 09:38:08 +02:00
1188 changed files with 147291 additions and 19786 deletions

5
.cargo/config.toml Normal file
View file

@ -0,0 +1,5 @@
[alias]
xtask = "run --package xtask --"
[target.wasm32-unknown-unknown]
rustflags = ['--cfg', 'getrandom_backend="wasm_js"']

8
.gitattribute Normal file
View file

@ -0,0 +1,8 @@
*.rs text eol=lf
*.toml text eol=lf
*.cs text eol=lf
*.js text eol=lf
*.ps1 text eol=lf
*.sln text eol=crlf
ffi/dotnet/Devolutions.IronRdp/Generated/** linguist-generated merge=binary

3
.github/CODEOWNERS vendored Normal file
View file

@ -0,0 +1,3 @@
# File auto-generated and managed by Devops
/.github/ @devolutions/devops @devolutions/architecture-maintainers
/.github/dependabot.yml @devolutions/security-managers

28
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,28 @@
version: 2
updates:
- package-ecosystem: "cargo"
directories:
- "/"
- "/fuzz/"
schedule:
interval: "weekly"
assignees:
- "CBenoit"
open-pull-requests-limit: 3
groups:
crypto:
patterns:
- "md-5"
- "md5"
- "sha1"
- "pkcs1"
- "x509-cert"
- "der"
- "*tls*"
- "*rand*"
patch:
dependency-type: "production"
update-types:
- "patch"
dev:
dependency-type: "development"

215
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,215 @@
name: CI
on:
push:
branches:
- master
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
env:
# Disable incremental compilation. CI builds are often closer to from-scratch builds, as changes
# are typically bigger than from a local edit-compile cycle.
# Incremental compilation also significantly increases the amount of IO and the size of ./target
# folder, which makes caching less effective.
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
RUSTUP_MAX_RETRIES: 10
RUST_BACKTRACE: short
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
# Cache should never takes more than a few seconds to get downloaded.
# If it does, lets just rebuild from scratch instead of hanging "forever".
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
# Disabling debug info so compilation is faster and ./target folder is smaller.
CARGO_PROFILE_DEV_DEBUG: 0
jobs:
formatting:
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check formatting
run: cargo xtask check fmt -v
typos:
name: Check typos
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Binary cache
uses: actions/cache@v4
with:
path: ./.cargo/local_root/bin
key: ${{ runner.os }}-bin-${{ github.job }}-${{ hashFiles('xtask/src/bin_version.rs') }}
- name: typos (prepare)
run: cargo xtask check install -v
- name: typos (check)
run: cargo xtask check typos -v
checks:
name: Checks [${{ matrix.os }}]
needs: [formatting]
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
os: [windows, linux, macos]
include:
- os: windows
runner: windows-latest
- os: linux
runner: ubuntu-latest
- os: macos
runner: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install devel packages
if: ${{ runner.os == 'Linux' }}
run: |
sudo apt-get -y install libasound2-dev
- name: Install NASM
if: ${{ runner.os == 'Windows' }}
run: |
choco install nasm
$Env:PATH += ";$Env:ProgramFiles\NASM"
echo "PATH=$Env:PATH" >> $Env:GITHUB_ENV
shell: pwsh
- name: Rust cache
uses: Swatinem/rust-cache@v2.7.3
- name: Binary cache
uses: actions/cache@v4
with:
path: ./.cargo/local_root/bin
key: ${{ runner.os }}-bin-${{ github.job }}-${{ hashFiles('xtask/src/bin_version.rs') }}
# Compilation is separated from execution so we know exactly the time for each step.
- name: Tests (compile)
run: cargo xtask check tests --no-run -v
- name: Tests (run)
run: cargo xtask check tests -v
- name: Lints
run: cargo xtask check lints -v
- name: WASM (prepare)
run: cargo xtask wasm install -v
- name: WASM (check)
run: cargo xtask wasm check -v
- name: Lock files
run: cargo xtask check locks -v
fuzz:
name: Fuzzing
needs: [formatting]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Rust cache
uses: Swatinem/rust-cache@v2.7.3
with:
workspaces: fuzz -> target
- name: Binary cache
uses: actions/cache@v4
with:
path: ./.cargo/local_root/bin
key: ${{ runner.os }}-bin-${{ github.job }}-${{ hashFiles('xtask/src/bin_version.rs') }}
- name: Prepare
run: cargo xtask fuzz install -v
# Simply run all fuzz targets for a few seconds, just checking there is nothing obviously wrong at a quick glance
- name: Fuzz
run: cargo xtask fuzz run -v
- name: Lock files
run: cargo xtask check locks -v
web:
name: Web Client
needs: [formatting]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Rust cache
uses: Swatinem/rust-cache@v2.7.3
- name: Binary cache
uses: actions/cache@v4
with:
path: ./.cargo/local_root/bin
key: ${{ runner.os }}-bin-${{ github.job }}-${{ hashFiles('xtask/src/bin_version.rs') }}
- name: Prepare
run: cargo xtask web install -v
- name: Check
run: cargo xtask web check -v
- name: Lock files
run: cargo xtask check locks -v
ffi:
name: FFI
needs: [formatting]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Rust cache
uses: Swatinem/rust-cache@v2.7.3
- name: Binary cache
uses: actions/cache@v4
with:
path: ./.cargo/local_root/bin
key: ${{ runner.os }}-bin-${{ github.job }}-${{ hashFiles('xtask/src/bin_version.rs') }}
- name: Prepare runner
run: cargo xtask ffi install -v
- name: Build native library
run: cargo xtask ffi build -v
- name: Generate bindings
run: cargo xtask ffi bindings -v
- name: Build .NET projects
run: cd ./ffi/dotnet && dotnet build
success:
name: Success
if: ${{ always() }}
needs: [formatting, typos, checks, fuzz, web, ffi]
runs-on: ubuntu-latest
steps:
- name: Check success
run: |
$results = '${{ toJSON(needs.*.result) }}' | ConvertFrom-Json
$succeeded = $($results | Where { $_ -Ne "success" }).Count -Eq 0
exit $(if ($succeeded) { 0 } else { 1 })
shell: pwsh

50
.github/workflows/coverage.yml vendored Normal file
View file

@ -0,0 +1,50 @@
name: Coverage
on:
push:
branches:
- master
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
env:
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
jobs:
coverage:
name: Coverage Report
runs-on: ubuntu-latest
# Running the coverage job is only supported on the official repo itself, not on forks
# (because $GITHUB_TOKEN only have read permissions when run on a fork)
# We would need something like Codecov integration to handle forks properly
# https://github.com/taiki-e/cargo-llvm-cov#continuous-integration
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v4
- name: Rust cache
uses: Swatinem/rust-cache@v2.7.3
- name: Prepare runner
run: cargo xtask cov install -v
- name: Generate PR report
if: ${{ github.event.number != '' }}
run: cargo xtask cov report-gh --repo "${{ github.repository }}" --pr "${{ github.event.number }}" -v
env:
GH_TOKEN: ${{ github.token }}
- name: Configure Git Identity
if: ${{ github.ref == 'refs/heads/master' }}
run: |
git config --local user.name "github-actions[bot]"
git config --local user.email "github-actions[bot]@users.noreply.github.com"
- name: Update coverage data
if: ${{ github.ref == 'refs/heads/master' }}
run: cargo xtask cov update -v
env:
GH_TOKEN: ${{ secrets.DEVOLUTIONSBOT_TOKEN }}

182
.github/workflows/fuzz.yml vendored Normal file
View file

@ -0,0 +1,182 @@
name: Fuzz
on:
workflow_dispatch:
schedule:
- cron: '12 3 * * 0' # At 03:12 AM UTC on Sunday.
env:
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
RUSTUP_MAX_RETRIES: 10
RUST_BACKTRACE: short
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
jobs:
corpus-download:
name: Download corpus
runs-on: ubuntu-latest
env:
AZURE_STORAGE_KEY: ${{ secrets.CORPUS_AZURE_STORAGE_KEY }}
steps:
- uses: actions/checkout@v4
- name: Download fuzzing corpus
run: cargo xtask fuzz corpus-fetch -v
- name: Save corpus
uses: actions/cache/save@v4
with:
path: |
./fuzz/corpus
./fuzz/artifacts
key: fuzz-corpus-${{ github.run_id }}
fuzz:
name: Fuzzing ${{ matrix.target }}
needs: [corpus-download]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [pdu_decoding, rle_decompression, bitmap_stream, cliprdr_format, channel_processing]
steps:
- uses: actions/checkout@v4
- name: Download corpus
uses: actions/cache/restore@v4
with:
fail-on-cache-miss: true
path: |
./fuzz/corpus
./fuzz/artifacts
key: fuzz-corpus-${{ github.run_id }}
- name: Print corpus
run: |
tree ./fuzz/corpus
tree ./fuzz/artifacts
- name: Rust cache
uses: Swatinem/rust-cache@v2.7.3
with:
workspaces: fuzz -> target
- name: Binary cache
uses: actions/cache@v4
with:
path: ./.cargo/local_root/bin
key: ${{ runner.os }}-bin-${{ github.job }}-${{ hashFiles('xtask/src/bin_version.rs') }}
- name: Prepare runner
run: cargo xtask fuzz install -v
- name: Fuzz
run: cargo xtask fuzz run --duration 1000 --target ${{ matrix.target }} -v
- name: Minify fuzzing corpus
if: ${{ always() && !cancelled() }}
run: cargo xtask fuzz corpus-min --target ${{ matrix.target }} -v
# Use GitHub artifacts instead of cache for the updated corpus
# because same cache cant be used by multiple jobs at the same time.
# Also, we cant dynamically create a unique cache keys for all
# the targets, because then we cant easily retrieve this cache
# without hardcoding a step for each one. Its not good for maintenance.
- name: Prepare minified corpus upload
# We want to upload artifacts even if fuzzing "fails" (so we can retrieve the artifact causing the crash)
if: ${{ always() && !cancelled() }}
run: |
mkdir ${{ runner.temp }}/corpus/
cp -r ./fuzz/corpus/${{ matrix.target }} ${{ runner.temp }}/corpus
mkdir ${{ runner.temp }}/artifacts/
cp -r ./fuzz/artifacts/${{ matrix.target }} ${{ runner.temp }}/artifacts
- name: Upload minified corpus
if: ${{ always() && !cancelled() }}
uses: actions/upload-artifact@v4
with:
retention-days: 7
name: minified-corpus-${{ matrix.target }}
path: |
${{ runner.temp }}/corpus
${{ runner.temp }}/artifacts
corpus-merge:
name: Corpus merge artifacts
if: ${{ always() && !cancelled() }}
needs: [fuzz]
runs-on: ubuntu-latest
steps:
- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
with:
name: minified-corpus
pattern: minified-corpus-*
delete-merged: true
corpus-upload:
name: Upload corpus
if: ${{ always() && !cancelled() }}
needs: [corpus-merge]
runs-on: ubuntu-latest
env:
AZURE_STORAGE_KEY: ${{ secrets.CORPUS_AZURE_STORAGE_KEY }}
steps:
- uses: actions/checkout@v4
- name: Download updated corpus
uses: actions/download-artifact@v4
with:
name: minified-corpus
path: ./fuzz/
- name: Print corpus
run: |
tree ./fuzz/corpus
tree ./fuzz/artifacts
- name: Upload fuzzing corpus
run: cargo xtask fuzz corpus-push -v
- name: Clean corpus cache
run: |
curl -L \
-X DELETE \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ github.token }}"\
-H "X-GitHub-Api-Version: 2022-11-28" \
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/actions/caches?key=fuzz-corpus-${{ github.run_id }}"
notify:
name: Notify failure
if: ${{ always() && contains(needs.*.result, 'failure') && github.event_name == 'schedule' }}
needs: [fuzz]
runs-on: ubuntu-latest
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_ARCHITECTURE }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
steps:
- name: Send slack notification
id: slack
uses: slackapi/slack-github-action@v1.26.0
with:
payload: |
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*${{ github.repository }}* :warning: \n Fuzz workflow for *${{ github.repository }}* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|found a bug>"
}
}
]
}

234
.github/workflows/npm-publish.yml vendored Normal file
View file

@ -0,0 +1,234 @@
name: Publish npm package
on:
workflow_dispatch:
inputs:
dry-run:
description: 'Dry run'
required: true
type: boolean
default: true
schedule:
- cron: '48 3 * * 1' # 3:48 AM UTC every Monday
jobs:
preflight:
name: Preflight
runs-on: ubuntu-latest
outputs:
dry-run: ${{ steps.get-dry-run.outputs.dry-run }}
steps:
- name: Get dry run
id: get-dry-run
run: |
$IsDryRun = '${{ github.event.inputs.dry-run }}' -Eq 'true' -Or '${{ github.event_name }}' -Eq 'schedule'
if ($IsDryRun) {
echo "dry-run=true" >> $Env:GITHUB_OUTPUT
} else {
echo "dry-run=false" >> $Env:GITHUB_OUTPUT
}
shell: pwsh
build:
name: Build package [${{matrix.library}}]
needs: [preflight]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
library:
- iron-remote-desktop
- iron-remote-desktop-rdp
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup wasm-pack
run: |
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
shell: bash
- name: Install dependencies
run: |
Set-Location -Path "./web-client/${{matrix.library}}/"
npm install
shell: pwsh
- name: Build package
run: |
Set-PSDebug -Trace 1
Set-Location -Path "./web-client/${{matrix.library}}/"
npm run build
Set-Location -Path ./dist
npm pack
shell: pwsh
- name: Harvest package
run: |
Set-PSDebug -Trace 1
New-Item -ItemType "directory" -Path . -Name "npm-packages"
Get-ChildItem -Path ./web-client/ -Recurse *.tgz | ForEach { Copy-Item $_ "./npm-packages" }
shell: pwsh
- name: Upload package artifact
uses: actions/upload-artifact@v4
with:
name: npm-${{matrix.library}}
path: npm-packages/*.tgz
npm-merge:
name: Merge artifacts
needs: [build]
runs-on: ubuntu-latest
steps:
- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
with:
name: npm
pattern: npm-*
delete-merged: true
publish:
name: Publish package
environment: publish
if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [preflight, npm-merge]
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download NPM packages artifact
uses: actions/download-artifact@v4
with:
name: npm
path: npm-packages
- name: Publish
run: |
Set-PSDebug -Trace 1
$isDryRun = '${{ needs.preflight.outputs.dry-run }}' -Eq 'true'
$files = Get-ChildItem -Recurse npm-packages/*.tgz
foreach ($file in $files) {
Write-Host "Processing $($file.Name)..."
$match = [regex]::Match($file.Name, '^(?<name>.+)-(?<version>\d+\.\d+\.\d+)\.tgz$')
if (-not $match.Success) {
Write-Host "Unable to parse package name/version from $($file.Name), skipping."
continue
}
$pkgName = $match.Groups['name'].Value
# Normalize scope for npm lookups: "devolutions-foo" => "@devolutions/foo"
if ($pkgName -like 'devolutions-*') {
$scopedName = "@devolutions/$($pkgName.Substring(12))"
} else {
$scopedName = $pkgName
}
$pkgVersion = $match.Groups['version'].Value
# Check if this version exists on npm; exit code 0 means it does.
npm view "$scopedName@$pkgVersion" | Out-Null
if ($LASTEXITCODE -eq 0) {
Write-Host "$scopedName@$pkgVersion already exists on npm; skipping publish."
continue
}
$publishCmd = @('npm','publish',"$file",'--access=public')
if ($isDryRun) {
$publishCmd += '--dry-run'
}
$publishCmd = $publishCmd -Join ' '
Invoke-Expression $publishCmd
}
shell: pwsh
- name: Create version tags
if: ${{ needs.preflight.outputs.dry-run == 'false' }}
run: |
set -e
git fetch --tags
for file in npm-packages/*.tgz; do
base=$(basename "$file" .tgz)
# Split base at the last hyphen to separate name and version
pkg=${base%-*}
# Strip the unscoped prefix introduced by `npm pack` for @devolutions/<pkg>.
pkg=${pkg#devolutions-}
version=${base##*-}
tag="npm-${pkg}-v${version}"
if git rev-parse "$tag" >/dev/null 2>&1; then
echo "Tag $tag already exists; skipping."
continue
fi
git tag "$tag" "$GITHUB_SHA"
git push origin "$tag"
done
shell: bash
env:
GIT_AUTHOR_NAME: github-actions
GIT_AUTHOR_EMAIL: github-actions@github.com
GIT_COMMITTER_NAME: github-actions
GIT_COMMITTER_EMAIL: github-actions@github.com
- name: Update Artifactory Cache
if: ${{ needs.preflight.outputs.dry-run == 'false' }}
run: |
gh workflow run update-artifactory-cache.yml --repo Devolutions/scheduled-tasks --field package_name="iron-remote-desktop"
gh workflow run update-artifactory-cache.yml --repo Devolutions/scheduled-tasks --field package_name="iron-remote-desktop-rdp"
env:
GH_TOKEN: ${{ secrets.DEVOLUTIONSBOT_WRITE_TOKEN }}
notify:
name: Notify failure
if: ${{ always() && contains(needs.*.result, 'failure') && github.event_name == 'schedule' }}
needs: [preflight, build]
runs-on: ubuntu-latest
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_ARCHITECTURE }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
steps:
- name: Send slack notification
id: slack
uses: slackapi/slack-github-action@v1.26.0
with:
payload: |
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*${{ github.repository }}* :fire::fire::fire::fire::fire: \n The scheduled build for *${{ github.repository }}* is <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|broken>"
}
}
]
}

420
.github/workflows/nuget-publish.yml vendored Normal file
View file

@ -0,0 +1,420 @@
name: Publish NuGet package
on:
workflow_dispatch:
inputs:
dry-run:
description: 'Dry run'
required: true
type: boolean
default: true
schedule:
- cron: '21 3 * * 1' # 3:21 AM UTC every Monday
jobs:
preflight:
name: Preflight
runs-on: ubuntu-latest
outputs:
dry-run: ${{ steps.get-dry-run.outputs.dry-run }}
project-version: ${{ steps.get-version.outputs.project-version }}
package-version: ${{ steps.get-version.outputs.package-version }}
steps:
- name: Checkout ${{ github.repository }}
uses: actions/checkout@v4
- name: Get dry run
id: get-dry-run
run: |
$IsDryRun = '${{ github.event.inputs.dry-run }}' -Eq 'true' -Or '${{ github.event_name }}' -Eq 'schedule'
if ($IsDryRun) {
echo "dry-run=true" >> $Env:GITHUB_OUTPUT
} else {
echo "dry-run=false" >> $Env:GITHUB_OUTPUT
}
shell: pwsh
- name: Get version
id: get-version
run: |
$CsprojXml = [Xml] (Get-Content .\ffi\dotnet\Devolutions.IronRdp\Devolutions.IronRdp.csproj)
$ProjectVersion = $CsprojXml.Project.PropertyGroup.Version | Select-Object -First 1
$PackageVersion = $ProjectVersion -Replace "^(\d+)\.(\d+)\.(\d+).(\d+)$", "`$1.`$2.`$3"
echo "project-version=$ProjectVersion" >> $Env:GITHUB_OUTPUT
echo "package-version=$PackageVersion" >> $Env:GITHUB_OUTPUT
shell: pwsh
build-native:
name: Native build
needs: [preflight]
runs-on: ${{matrix.runner}}
strategy:
fail-fast: false
matrix:
os: [win, osx, linux, ios, android]
arch: [x86, x64, arm, arm64]
include:
- os: win
runner: windows-2022
- os: osx
runner: macos-14
- os: linux
runner: ubuntu-22.04
- os: ios
runner: macos-14
- os: android
runner: ubuntu-22.04
exclude:
- arch: arm
os: win
- arch: arm
os: osx
- arch: arm
os: linux
- arch: arm
os: ios
- arch: x86
os: win
- arch: x86
os: osx
- arch: x86
os: linux
- arch: x86
os: ios
steps:
- name: Checkout ${{ github.repository }}
uses: actions/checkout@v4
- name: Configure Android NDK
if: ${{ matrix.os == 'android' }}
uses: Devolutions/actions-public/cargo-android-ndk@v1
with:
android_api_level: "21"
- name: Configure macOS deployement target
if: ${{ matrix.os == 'osx' }}
run: Write-Output "MACOSX_DEPLOYMENT_TARGET=10.10" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
shell: pwsh
- name: Configure iOS deployement target
if: ${{ matrix.os == 'ios' }}
run: Write-Output "IPHONEOS_DEPLOYMENT_TARGET=12.1" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
shell: pwsh
- name: Update runner
if: ${{ matrix.os == 'linux' }}
run: sudo apt update
- name: Install dependencies for rustls
if: ${{ runner.os == 'Windows' }}
run: |
choco install ninja nasm
# We need to add the NASM binary folder to the PATH manually.
# We don't need to do that for ninja.
Write-Output "PATH=$Env:PATH;$Env:ProgramFiles\NASM" >> $Env:GITHUB_ENV
# libclang / LLVM is a requirement for AWS LC.
# https://aws.github.io/aws-lc-rs/requirements/windows.html#libclang--llvm
$VSINSTALLDIR = $(vswhere.exe -latest -requires Microsoft.VisualStudio.Component.VC.Llvm.Clang -property installationPath)
Write-Output "LIBCLANG_PATH=$VSINSTALLDIR\VC\Tools\Llvm\x64\bin" >> $Env:GITHUB_ENV
# Install Visual Studio Developer PowerShell Module for cmdlets such as Enter-VsDevShell
Install-Module VsDevShell -Force
shell: pwsh
# No pre-generated bindings for Android and iOS.
# https://aws.github.io/aws-lc-rs/platform_support.html#pre-generated-bindings
- name: Install bindgen-cli for aws-lc-sys
if: ${{ matrix.os == 'android' || matrix.os == 'ios' }}
run: cargo install --force --locked bindgen-cli
# For aws-lc-sys. Error returned otherwise:
# > Unable to generate bindings: ClangDiagnostic("/usr/include/stdint.h:26:10: fatal error: 'bits/libc-header-start.h' file not found\n")
- name: Install gcc-multilib
if: ${{ matrix.os == 'android' }}
run: |
sudo apt-get update
sudo apt-get install gcc-multilib
- name: Setup LLVM
if: ${{ matrix.os == 'linux' }}
uses: Devolutions/actions-public/setup-llvm@v1
with:
version: "18.1.8"
- name: Setup CBake
if: ${{ matrix.os == 'linux' }}
uses: Devolutions/actions-public/setup-cbake@v1
with:
cargo_env_scripts: true
- name: Build native lib (${{matrix.os}}-${{matrix.arch}})
run: |
$DotNetOs = '${{matrix.os}}'
$DotNetArch = '${{matrix.arch}}'
$DotNetRid = '${{matrix.os}}-${{matrix.arch}}'
$RustArch = @{'x64'='x86_64';'arm64'='aarch64';
'x86'='i686';'arm'='armv7'}[$DotNetArch]
$RustPlatform = @{'win'='pc-windows-msvc';
'osx'='apple-darwin';'ios'='apple-ios';
'linux'='unknown-linux-gnu';'android'='linux-android'}[$DotNetOs]
$LibPrefix = @{'win'='';'osx'='lib';'ios'='lib';
'linux'='lib';'android'='lib'}[$DotNetOs]
$LibSuffix = @{'win'='.dll';'osx'='.dylib';'ios'='.dylib';
'linux'='.so';'android'='.so'}[$DotNetOs]
$RustTarget = "$RustArch-$RustPlatform"
if (($DotNetOs -eq 'android') -and ($DotNetArch -eq 'arm')) {
$RustTarget = "armv7-linux-androideabi"
}
rustup target add $RustTarget
if ($DotNetOs -eq 'win') {
$Env:RUSTFLAGS="-C target-feature=+crt-static"
}
$ProjectVersion = '${{ needs.preflight.outputs.project-version }}'
$PackageVersion = '${{ needs.preflight.outputs.package-version }}'
$CargoToml = Get-Content .\ffi\Cargo.toml
$CargoToml = $CargoToml | ForEach-Object {
if ($_.StartsWith("version =")) { "version = `"$PackageVersion`"" } else { $_ }
}
Set-Content -Path .\ffi\Cargo.toml -Value $CargoToml
if ($DotNetOs -eq 'linux') {
$LinuxArch = @{'x64'='amd64';'arm64'='arm64'}[$DotNetArch]
$Env:SYSROOT_NAME = "ubuntu-20.04-$LinuxArch"
. "$HOME/.cargo/cbake/${RustTarget}-enter.ps1"
$Env:AWS_LC_SYS_CMAKE_BUILDER="true"
}
$CargoParams = @(
"build",
"-p", "ffi",
"--profile", "production-ffi",
"--target", "$RustTarget"
)
& cargo $CargoParams
$OutputLibraryName = "${LibPrefix}ironrdp$LibSuffix"
$RenamedLibraryName = "${LibPrefix}DevolutionsIronRdp$LibSuffix"
$OutputLibrary = Join-Path "target" $RustTarget 'production-ffi' $OutputLibraryName
$OutputPath = Join-Path "dependencies" "runtimes" $DotNetRid "native"
New-Item -ItemType Directory -Path $OutputPath | Out-Null
Copy-Item $OutputLibrary $(Join-Path $OutputPath $RenamedLibraryName)
shell: pwsh
- name: Upload native components
uses: actions/upload-artifact@v4
with:
name: ironrdp-${{matrix.os}}-${{matrix.arch}}
path: dependencies/runtimes/${{matrix.os}}-${{matrix.arch}}
build-universal:
name: Universal build
needs: [preflight, build-native]
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
os: [ osx, ios ]
steps:
- name: Checkout ${{ github.repository }}
uses: actions/checkout@v4
- name: Setup CCTools
uses: Devolutions/actions-public/setup-cctools@v1
- name: Download native components
uses: actions/download-artifact@v4
with:
path: dependencies/runtimes
- name: Lipo native components
run: |
Set-Location "dependencies/runtimes"
# No RID for universal binaries, see: https://github.com/dotnet/runtime/issues/53156
$OutputPath = Join-Path "${{ matrix.os }}-universal" "native"
New-Item -ItemType Directory -Path $OutputPath | Out-Null
$Libraries = Get-ChildItem -Recurse -Path "ironrdp-${{ matrix.os }}-*" -Filter "*.dylib" | Foreach-Object { $_.FullName } | Select -Unique
$LipoCmd = $(@('lipo', '-create', '-output', (Join-Path -Path $OutputPath -ChildPath "libDevolutionsIronRdp.dylib")) + $Libraries) -Join ' '
Write-Host $LipoCmd
Invoke-Expression $LipoCmd
shell: pwsh
- name: Framework
if: ${{ matrix.os == 'ios' }}
run: |
$Version = '${{ needs.preflight.outputs.project-version }}'
$ShortVersion = '${{ needs.preflight.outputs.package-version }}'
$BundleName = "libDevolutionsIronRdp"
$RuntimesDir = Join-Path "dependencies" "runtimes" "ios-universal" "native"
$FrameworkDir = Join-Path "$RuntimesDir" "$BundleName.framework"
New-Item -Path $FrameworkDir -ItemType "directory" -Force
$FrameworkExecutable = Join-Path $FrameworkDir $BundleName
Copy-Item -Path (Join-Path "$RuntimesDir" "$BundleName.dylib") -Destination $FrameworkExecutable -Force
$RPathCmd = $(@('install_name_tool', '-id', "@rpath/$BundleName.framework/$BundleName", "$FrameworkExecutable")) -Join ' '
Write-Host $RPathCmd
Invoke-Expression $RPathCmd
[xml] $InfoPlistXml = Get-Content (Join-Path "ffi" "dotnet" "Devolutions.IronRdp" "Info.plist")
Select-Xml -xml $InfoPlistXml -XPath "/plist/dict/key[. = 'CFBundleIdentifier']/following-sibling::string[1]" |
%{
$_.Node.InnerXml = "com.devolutions.ironrdp"
}
Select-Xml -xml $InfoPlistXml -XPath "/plist/dict/key[. = 'CFBundleExecutable']/following-sibling::string[1]" |
%{
$_.Node.InnerXml = $BundleName
}
Select-Xml -xml $InfoPlistXml -XPath "/plist/dict/key[. = 'CFBundleVersion']/following-sibling::string[1]" |
%{
$_.Node.InnerXml = $Version
}
Select-Xml -xml $InfoPlistXml -XPath "/plist/dict/key[. = 'CFBundleShortVersionString']/following-sibling::string[1]" |
%{
$_.Node.InnerXml = $ShortVersion
}
# Write the plist *without* a BOM
$Encoding = New-Object System.Text.UTF8Encoding($false)
$Writer = New-Object System.IO.StreamWriter((Join-Path $FrameworkDir "Info.plist"), $false, $Encoding)
$InfoPlistXml.Save($Writer)
$Writer.Close()
# .NET XML document inserts two square brackets at the end of the DOCTYPE tag
# It's perfectly valid XML, but we're dealing with plists here and dyld will not be able to read the file
((Get-Content -Path (Join-Path $FrameworkDir "Info.plist") -Raw) -Replace 'PropertyList-1.0.dtd"\[\]', 'PropertyList-1.0.dtd"') | Set-Content -Path (Join-Path $FrameworkDir "Info.plist")
shell: pwsh
- name: Upload native components
uses: actions/upload-artifact@v4
with:
name: ironrdp-${{ matrix.os }}-universal
path: dependencies/runtimes/${{ matrix.os }}-universal
build-managed:
name: Managed build
needs: [build-universal]
runs-on: windows-2022
steps:
- name: Check out ${{ github.repository }}
uses: actions/checkout@v4
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v2
- name: Install ios workload
run: dotnet workload install ios
- name: Prepare dependencies
run: |
New-Item -ItemType Directory -Path "dependencies/runtimes" | Out-Null
shell: pwsh
- name: Download native components
uses: actions/download-artifact@v4
with:
path: dependencies/runtimes
- name: Rename dependencies
run: |
Set-Location "dependencies/runtimes"
$(Get-Item ".\ironrdp-*") | ForEach-Object { Rename-Item $_ $_.Name.Replace("ironrdp-", "") }
Get-ChildItem * -Recurse
shell: pwsh
- name: Build Devolutions.IronRdp (managed)
run: |
# net8.0 target packaged as Devolutions.IronRdp
dotnet build .\ffi\dotnet\Devolutions.IronRdp\Devolutions.IronRdp.csproj -c Release
# net9.0-ios target packaged as Devolutions.IronRdp.iOS
dotnet build .\ffi\dotnet\Devolutions.IronRdp\Devolutions.IronRdp.csproj -c Release /p:PackageId=Devolutions.IronRdp.iOS
shell: pwsh
- name: Upload managed components
uses: actions/upload-artifact@v4
with:
name: ironrdp-nupkg
path: ffi/dotnet/Devolutions.IronRdp/bin/Release/*.nupkg
publish:
name: Publish NuGet package
environment: nuget-publish
if: ${{ needs.preflight.outputs.dry-run == 'false' }}
needs: [preflight, build-managed]
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- name: Download NuGet package artifact
uses: actions/download-artifact@v4
with:
name: ironrdp-nupkg
path: package
- name: NuGet login (OIDC)
uses: NuGet/login@v1
id: nuget-login
with:
user: ${{ secrets.NUGET_BOT_USERNAME }}
- name: Publish to nuget.org
run: |
$Files = Get-ChildItem -Recurse package/*.nupkg
foreach ($File in $Files) {
$PushCmd = @(
'dotnet',
'nuget',
'push',
"$File",
'--api-key',
'${{ steps.nuget-login.outputs.NUGET_API_KEY }}',
'--source',
'https://api.nuget.org/v3/index.json',
'--skip-duplicate'
)
Write-Host "Publishing $($File.Name)..."
$PushCmd = $PushCmd -Join ' '
Invoke-Expression $PushCmd
}
shell: pwsh
notify:
name: Notify failure
if: ${{ always() && contains(needs.*.result, 'failure') && github.event_name == 'schedule' }}
needs: [preflight, build-native, build-universal, build-managed]
runs-on: ubuntu-latest
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_ARCHITECTURE }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
steps:
- name: Send slack notification
id: slack
uses: slackapi/slack-github-action@v1.26.0
with:
payload: |
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*${{ github.repository }}* :fire::fire::fire::fire::fire: \n The scheduled build for *${{ github.repository }}* is <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|broken>"
}
}
]
}

86
.github/workflows/release-crates.yml vendored Normal file
View file

@ -0,0 +1,86 @@
name: Release crates
permissions:
pull-requests: write
contents: write
on:
workflow_dispatch:
push:
branches:
- master
jobs:
# Create a PR with the new versions and changelog, preparing the next release.
open-pr:
name: Open release PR
environment: cratesio-publish
runs-on: ubuntu-latest
concurrency:
group: release-plz-${{ github.ref }}
cancel-in-progress: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 512
- name: Run release-plz
id: release-plz
uses: Devolutions/actions-public/release-plz@v1
with:
command: release-pr
git-name: Devolutions Bot
git-email: bot@devolutions.net
github-token: ${{ secrets.DEVOLUTIONSBOT_WRITE_TOKEN }}
- name: Update fuzz/Cargo.lock
if: ${{ steps.release-plz.outputs.did-open-pr == 'true' }}
run: |
$prRaw = '${{ steps.release-plz.outputs.pr }}'
Write-Host "prRaw: $prRaw"
$pr = $prRaw | ConvertFrom-Json
Write-Host "pr: $pr"
Write-Host "Fetch branch $($pr.head_branch)"
git fetch origin "$($pr.head_branch)"
Write-Host "Switch to branch $($pr.head_branch)"
git checkout "$($pr.head_branch)"
Write-Host "Update ./fuzz/Cargo.lock"
cargo update --manifest-path ./fuzz/Cargo.toml
Write-Host "Update last commit"
git add ./fuzz/Cargo.lock
git commit --amend --no-edit
Write-Host "Update the release pull request"
git push --force
shell: pwsh
# Release unpublished packages.
release:
name: Release crates
environment: cratesio-publish
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 512
- name: Authenticate with crates.io
id: auth
uses: rust-lang/crates-io-auth-action@v1
- name: Run release-plz
uses: Devolutions/actions-public/release-plz@v1
with:
command: release
registry-token: ${{ steps.auth.outputs.token }}

24
.gitignore vendored
View file

@ -1,2 +1,22 @@
Cargo.lock
target/
# Build artifacts
/target
/dependencies
# Local cargo root
/.cargo/local_root
# Log files
*.log
# Coverage
/docs/coverage
# Editor/IDE files
*~
/tags
.idea
.vscode
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sw?

345
ARCHITECTURE.md Normal file
View file

@ -0,0 +1,345 @@
# Architecture
This document describes the high-level architecture of IronRDP.
> Roughly, it takes 2x more time to write a patch if you are unfamiliar with the
> project, but it takes 10x more time to figure out where you should change the
> code.
[Source](https://matklad.github.io/2021/02/06/ARCHITECTURE.md.html)
## Code Map
This section talks briefly about various important directories and data structures.
Note also which crates are **API Boundaries**.
Remember, [rules at the boundary are different](https://www.tedinski.com/2018/02/06/system-boundaries.html).
### Core Tier
Set of foundational libraries for which strict quality standards must be observed.
Note that all crates in this tier are **API Boundaries**.
Pay attention to the "**Architecture Invariant**" sections.
**Architectural Invariant**: doing I/O is not allowed for these crates.
**Architectural Invariant**: all these crates must be fuzzed.
**Architectural Invariant**: must be `#[no_std]`-compatible (optionally using the `alloc` crate). Usage of the standard
library must be opt-in through a feature flag called `std` that is enabled by default. When the `alloc` crate is optional,
a feature flag called `alloc` must exist to enable its use.
**Architectural Invariant**: no platform-dependant code (`#[cfg(windows)]` and such).
**Architectural Invariant**: no non-essential dependency is allowed.
**Architectural Invariant**: no proc-macro dependency. Dependencies such as `syn` should be pushed
as far as possible from the foundational crates so it doesnt become too much of a compilation
bottleneck. [Compilation time is a multiplier for everything][why-care-about-build-time].
The paper [Developer Productivity For Humans, Part 4: Build Latency, Predictability,
and Developer Productivity][developer-productivity] by Ciera Jaspan and Collin Green, Google
researchers, also elaborates on why it is important to keep build times low.
**Architectural Invariant**: unless the performance, usability or ergonomic gain is really worth
it, the amount of [monomorphization] incurred in downstream user code should be minimal to avoid
binary bloating and to keep the compilation as parallel as possible. Large generic functions should
be avoided if possible.
[why-care-about-build-time]: https://matklad.github.io/2021/09/04/fast-rust-builds.html#Why-Care-About-Build-Times
[developer-productivity]: https://www.computer.org/csdl/magazine/so/2023/04/10176199/1OAJyfknInm
[monomorphization]: https://rustc-dev-guide.rust-lang.org/backend/monomorph.html
#### [`crates/ironrdp`](./crates/ironrdp)
Meta crate re-exporting important crates.
**Architectural Invariant**: this crate re-exports other crates and does not provide anything else.
#### [`crates/ironrdp-core`](./crates/ironrdp-core)
Common traits and types.
This crate is motivated by the fact that only a few items are required to build most of the other crates such as the virtual channels.
To move up these crates up in the compilation tree, `ironrdp-core` must remain small, with very few dependencies.
It contains the most "low-context" building blocks.
Most notable traits are `Decode` and `Encode` which are used to define a common interface for PDU encoding and decoding.
These are object-safe, and must remain so.
Most notable types are `ReadCursor`, `WriteCursor` and `WriteBuf` which are used pervasively for encoding and decoding in a `no-std` manner.
#### [`crates/ironrdp-pdu`](./crates/ironrdp-pdu)
PDU encoding and decoding.
_TODO_: clean up the dependencies
#### [`crates/ironrdp-graphics`](./crates/ironrdp-graphics)
Image processing primitives.
_TODO_: break down into multiple smaller crates
_TODO_: clean up the dependencies
#### [`crates/ironrdp-svc`](./crates/ironrdp-svc)
Traits to implement RDP static virtual channels.
#### [`crates/ironrdp-dvc`](./crates/ironrdp-dvc)
DRDYNVC static channel implementation and traits to implement dynamic virtual channels.
#### [`crates/ironrdp-cliprdr`](./crates/ironrdp-cliprdr)
CLIPRDR static channel for clipboard implemented as described in MS-RDPECLIP.
#### [`crates/ironrdp-rdpdr`](./crates/ironrdp-rdpdr)
RDPDR channel implementation.
#### [`crates/ironrdp-rdpsnd`](./crates/ironrdp-rdpsnd)
RDPSND static channel for audio output implemented as described in MS-RDPEA.
#### [`crates/ironrdp-connector`](./crates/ironrdp-connector)
State machines to drive an RDP connection sequence.
#### [`crates/ironrdp-session`](./crates/ironrdp-session)
State machines to drive an RDP session.
#### [`crates/ironrdp-input`](./crates/ironrdp-input)
Utilities to manage and build input packets.
#### [`crates/ironrdp-rdcleanpath`](./crates/ironrdp-rdcleanpath)
RDCleanPath PDU structure used by IronRDP web client and Devolutions Gateway.
#### [`crates/ironrdp-error`](./crates/ironrdp-error)
Lightweight and `no_std`-compatible generic `Error` and `Report` types.
The `Error` type wraps a custom consumer-defined type for domain-specific details (such as `PduErrorKind`).
#### [`crates/ironrdp-propertyset`](./crates/ironrdp-propertyset)
The main type is `PropertySet`, a key-value store for configuration options.
#### [`crates/ironrdp-rdpfile`](./crates/ironrdp-rdpfile)
Loader and writer for the .RDPfile format.
### Extra Tier
Higher level libraries and binaries built on top of the core tier.
Guidelines and constraints are relaxed to some extent.
#### [`crates/ironrdp-blocking`](./crates/ironrdp-blocking)
Blocking I/O abstraction wrapping the state machines conveniently.
This crate is an **API Boundary**.
#### [`crates/ironrdp-async`](./crates/ironrdp-async)
Provides `Future`s wrapping the state machines conveniently.
This crate is an **API Boundary**.
#### [`crates/ironrdp-tokio`](./crates/ironrdp-tokio)
`Framed*` traits implementation above `tokio`s traits.
This crate is an **API Boundary**.
#### [`crates/ironrdp-futures`](./crates/ironrdp-futures)
`Framed*` traits implementation above `futures`s traits.
This crate is an **API Boundary**.
#### [`crates/ironrdp-tls`](./crates/ironrdp-tls)
TLS boilerplate common with most IronRDP clients.
NOTE: its not yet clear if this crate is an API Boundary or an implementation detail for the native clients.
#### [`crates/ironrdp-client`](./crates/ironrdp-client)
Portable RDP client without GPU acceleration.
#### [`crates/ironrdp-web`](./crates/ironrdp-web)
WebAssembly high-level bindings targeting web browsers.
This crate is an **API Boundary** (WASM module).
#### [`web-client/iron-remote-desktop`](./web-client/iron-remote-desktop)
Core frontend UI used by `iron-svelte-client` as a Web Component.
This crate is an **API Boundary**.
#### [`web-client/iron-remote-desktop-rdp`](./web-client/iron-remote-desktop-rdp)
Implementation of the TypeScript interfaces exposed by WebAssembly bindings from `ironrdp-web` and used by `iron-svelte-client`.
This crate is an **API Boundary**.
#### [`web-client/iron-svelte-client`](./web-client/iron-svelte-client)
Web-based frontend using `Svelte` and `Material` frameworks.
#### [`crates/ironrdp-cliprdr-native`](./crates/ironrdp-cliprdr-native)
Native CLIPRDR backend implementations.
#### [`crates/ironrdp-cfg`](./crates/ironrdp-cfg)
IronRDP-related utilities for ironrdp-propertyset.
### Internal Tier
Crates that are only used inside the IronRDP project, not meant to be published.
This is mostly test case generators, fuzzing oracles, build tools, and so on.
**Architecture Invariant**: these crates are not, and will never be, an **API Boundary**.
#### [`crates/ironrdp-pdu-generators`](./crates/ironrdp-pdu-generators)
`proptest` generators for `ironrdp-pdu` types.
#### [`crates/ironrdp-session-generators`](./crates/ironrdp-session-generators)
`proptest` generators for `ironrdp-session` types.
#### [`crates/ironrdp-testsuite-core`](./crates/ironrdp-testsuite-core)
Contains all integration tests for code living in the core tier, in a single binary, organized in modules.
**Architectural Invariant**: no dependency from another tier is allowed. It must be the case that
compiling and running the core test suite does not require building any library from the extra tier.
This is to keep iteration time short.
#### [`crates/ironrdp-testsuite-extra`](./crates/ironrdp-testsuite-extra)
Contains all integration tests for code living in the extra tier, in a single binary, organized in modules.
#### [`crates/ironrdp-fuzzing`](./crates/ironrdp-fuzzing)
Provides test case generators and oracles for use with fuzzing.
#### [`fuzz`](./fuzz)
Fuzz targets for code in core tier.
#### [`xtask`](./xtask)
IronRDPs free-form automation using Rust code.
### Community Tier
Crates provided and maintained by the community. Core maintainers will not invest a lot of time into
these. One or several community maintainers are associated to each one.
The IronRDP team is happy to accept new crates but may not necessarily commit to keeping them
working when changing foundational libraries. We promise to notify you if such a crate breaks, and
will always try to fix things when it's a minor change.
#### [`crates/ironrdp-acceptor`](./crates/ironrdp-acceptor) (@mihneabuz)
State machines to drive an RDP connection acceptance sequence
#### [`crates/ironrdp-server`](./crates/ironrdp-server) (@mihneabuz)
Extendable skeleton for implementing custom RDP servers.
#### [`crates/ironrdp-mstsgu`](./crates/ironrdp-mstsgu) (@steffengy)
Terminal Services Gateway Server Protocol implementation.
#### [`crates/ironrdp-glutin-renderer`](./crates/ironrdp-glutin-renderer) (no maintainer)
`glutin` primitives for OpenGL rendering.
#### [`crates/ironrdp-client-glutin`](./crates/ironrdp-client-glutin) (no maintainer)
GPU-accelerated RDP client using glutin.
#### [`crates/ironrdp-replay-client`](./crates/ironrdp-replay-client) (no maintainer)
Utility tool to replay RDP graphics pipeline for debugging purposes.
## Cross-Cutting Concerns
This section talks about the things which are everywhere and nowhere in particular.
### General
- Dependency injection when runtime information is necessary in core tier crates (no system call such as `gethostname`)
- Keep non-portable code out of core tier crates
- Make crate `no_std`-compatible wherever possible
- Facilitate fuzzing
- In libraries, provide concrete error types either hand-crafted or using `thiserror` crate
- In binaries, use the convenient catch-all error type `anyhow::Error`
- Free-form automation a-la `make` following [`cargo xtask`](https://github.com/matklad/cargo-xtask) specification
### Avoid I/O wherever possible
**Architecture Invariant**: core tier crates must never interact with the outside world. Only extra tier crates
such as `ironrdp-client`, `ironrdp-web` or `ironrdp-async` are allowed to do I/O.
### Continuous integration
We use GitHub action and our workflows simply run `cargo xtask`.
The expectation is that, if `cargo xtask ci` passes locally, the CI will be green as well.
**Architecture Invariant**: `cargo xtask ci` and CI workflow must be logically equivalents. It must
be the case that a successful `cargo xtask ci` run implies a successful CI workflow run and vice versa.
### Testing
#### Test at the boundaries (test features, not code)
We should focus on testing the public API of libraries (keyword: **API boundary**).
Thats why most (if not all) tests should go into the `ironrdp-testsuite-core` and `ironrdp-testsuite-extra` crates.
#### Do not depend on external resources
**Architecture Invariant**: tests do not depend on any kind of external resources, they are perfectly reproducible.
#### Fuzzing
See [`fuzz/README.md`](./fuzz/README.md).
#### Readability
Do not include huge binary chunks directly in source files (`*.rs`). Place these in separate files (`*.bin`, `*.bmp`)
and include them using macros such as `include_bytes!` or `include_str!`.
#### Use `expect-test` for snapshot testing
When comparing structured data (e.g.: error results, decoded PDUs), use `expect-test`. It is both easy to create
and maintain such tests. When something affecting the representation is changed, simply run the test again with
`UPDATE_EXPECT=1` env variable to magically update the code.
See:
- <https://matklad.github.io/2021/05/31/how-to-test.html#Expect-Tests>
- <https://docs.rs/expect-test/latest/expect_test/>
TODO: take further inspiration from rust-analyzer
- https://github.com/rust-lang/rust-analyzer/blob/d7c99931d05e3723d878bea5dc26766791fa4e69/docs/dev/architecture.md#testing
- https://matklad.github.io/2021/05/31/how-to-test.html
#### Use `rstest` for fixture-based testing
When a test can be generalized for multiple inputs, use [`rstest`](https://github.com/la10736/rstest) to avoid code duplication.
#### Use `proptest` for property testing
It allows to test that certain properties of your code hold for arbitrary inputs, and if a failure
is found, automatically finds the minimal test case to reproduce the problem.

7207
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,213 @@
[workspace]
members = [
"ironrdp",
"ironrdp_client"
"crates/*",
"benches",
"xtask",
"ffi",
]
resolver = "2"
# FIXME: fix compilation
exclude = [
"crates/ironrdp-client-glutin",
"crates/ironrdp-glutin-renderer",
"crates/ironrdp-replay-client",
]
[workspace.package]
edition = "2021"
license = "MIT OR Apache-2.0"
homepage = "https://github.com/Devolutions/IronRDP"
repository = "https://github.com/Devolutions/IronRDP"
authors = ["Devolutions Inc. <infos@devolutions.net>", "Teleport <goteleport.com>"]
keywords = ["rdp", "remote-desktop", "network", "client", "protocol"]
categories = ["network-programming"]
[workspace.dependencies]
# Note that for better cross-tooling interactions, do not use workspace
# dependencies for anything that is not "workspace internal" (e.g.: mostly
# dev-dependencies). E.g.: release-plz cant detect that a dependency has been
# updated in a way warranting a version bump in the dependant if no commit is
# touching a file associated to the crate. It is technically okay to use that
# for "private" (i.e.: not used in the public API) dependencies too, but we
# still want to make follow-up releases to stay up to date with the community,
# even for private dependencies.
expect-test = "1"
proptest = "1.4"
rstest = "0.26"
# Note: we are trying to move away from using these crates.
# They are being kept around for now for legacy compatibility,
# but new usage should be avoided.
num-derive = "0.4"
num-traits = "0.2"
[workspace.lints.rust]
# == Safer unsafe == #
unsafe_op_in_unsafe_fn = "warn"
invalid_reference_casting = "warn"
unused_unsafe = "warn"
missing_unsafe_on_extern = "warn"
unsafe_attr_outside_unsafe = "warn"
# == Correctness == #
ambiguous_negative_literals = "warn"
keyword_idents_2024 = "warn" # FIXME: remove when switched to 2024 edition
# == Style, readability == #
elided_lifetimes_in_paths = "warn" # https://quinedot.github.io/rust-learning/dont-hide.html
absolute_paths_not_starting_with_crate = "warn"
single_use_lifetimes = "warn"
unreachable_pub = "warn"
unused_lifetimes = "warn"
unused_qualifications = "warn"
keyword_idents = "warn"
noop_method_call = "warn"
macro_use_extern_crate = "warn"
redundant_imports = "warn"
redundant_lifetimes = "warn"
trivial_numeric_casts = "warn"
# missing_docs = "warn" # TODO: NOTE(@CBenoit): we probably want to ensure this in core tier crates only
# == Compile-time / optimization == #
unused_crate_dependencies = "warn"
unused_macro_rules = "warn"
# == Extra-pedantic rustc == #
unit_bindings = "warn"
[workspace.lints.clippy]
# == Safer unsafe == #
undocumented_unsafe_blocks = "warn"
unnecessary_safety_comment = "warn"
multiple_unsafe_ops_per_block = "warn"
missing_safety_doc = "warn"
transmute_ptr_to_ptr = "warn"
as_ptr_cast_mut = "warn"
as_pointer_underscore = "warn"
cast_ptr_alignment = "warn"
fn_to_numeric_cast_any = "warn"
ptr_cast_constness = "warn"
# == Correctness == #
as_conversions = "warn"
cast_lossless = "warn"
cast_possible_truncation = "warn"
cast_possible_wrap = "warn"
cast_sign_loss = "warn"
filetype_is_file = "warn"
float_cmp = "warn"
lossy_float_literal = "warn"
float_cmp_const = "warn"
as_underscore = "warn"
unwrap_used = "warn"
large_stack_frames = "warn"
mem_forget = "warn"
mixed_read_write_in_expression = "warn"
needless_raw_strings = "warn"
non_ascii_literal = "warn"
panic = "warn"
precedence_bits = "warn"
rc_mutex = "warn"
same_name_method = "warn"
string_slice = "warn"
suspicious_xor_used_as_pow = "warn"
unused_result_ok = "warn"
missing_panics_doc = "warn"
# == Style, readability == #
semicolon_outside_block = "warn" # With semicolon-outside-block-ignore-multiline = true
clone_on_ref_ptr = "warn"
cloned_instead_of_copied = "warn"
pub_without_shorthand = "warn"
infinite_loop = "warn"
empty_enum_variants_with_brackets = "warn"
deref_by_slicing = "warn"
multiple_inherent_impl = "warn"
map_with_unused_argument_over_ranges = "warn"
partial_pub_fields = "warn"
trait_duplication_in_bounds = "warn"
type_repetition_in_bounds = "warn"
checked_conversions = "warn"
get_unwrap = "warn"
similar_names = "warn" # Reduce risk of confusing similar names together, and protects against typos when variable shadowing was intended.
str_to_string = "warn"
string_to_string = "warn"
std_instead_of_core = "warn"
separated_literal_suffix = "warn"
unused_self = "warn"
useless_let_if_seq = "warn"
string_add = "warn"
range_plus_one = "warn"
self_named_module_files = "warn"
# TODO: partial_pub_fields = "warn" (should we enable only in pdu crates?)
redundant_type_annotations = "warn"
unnecessary_self_imports = "warn"
try_err = "warn"
rest_pat_in_fully_bound_structs = "warn"
# == Compile-time / optimization == #
doc_include_without_cfg = "warn"
inline_always = "warn"
large_include_file = "warn"
or_fun_call = "warn"
rc_buffer = "warn"
string_lit_chars_any = "warn"
unnecessary_box_returns = "warn"
large_futures = "warn"
# == Extra-pedantic clippy == #
allow_attributes = "warn"
cfg_not_test = "warn"
disallowed_script_idents = "warn"
non_zero_suggestions = "warn"
renamed_function_params = "warn"
unused_trait_names = "warn"
collection_is_never_read = "warn"
copy_iterator = "warn"
expl_impl_clone_on_copy = "warn"
implicit_clone = "warn"
large_types_passed_by_value = "warn"
redundant_clone = "warn"
alloc_instead_of_core = "warn"
empty_drop = "warn"
return_self_not_must_use = "warn"
wildcard_dependencies = "warn"
wildcard_imports = "warn"
# == Lets not merge unintended eprint!/print! statements in libraries == #
print_stderr = "warn"
print_stdout = "warn"
dbg_macro = "warn"
todo = "warn"
[profile.dev]
opt-level = 1
[profile.production]
inherits = "release"
lto = true
[profile.production-ffi]
inherits = "release"
strip = "symbols"
codegen-units = 1
lto = true
[profile.production-wasm]
inherits = "release"
opt-level = "s"
lto = true
[profile.test.package.proptest]
opt-level = 3
[profile.test.package.rand_chacha]
opt-level = 3
[patch.crates-io]
# FIXME: We need to catch up with Diplomat upstream again, but this is a significant amount of work.
# In the meantime, we use this forked version which fixes an undefined behavior in the code expanded by the bridge macro.
diplomat = { git = "https://github.com/CBenoit/diplomat", rev = "6dc806e80162b6b39509a04a2835744236cd2396" }

View file

@ -1,4 +1,74 @@
# IronRDP
A Rust implementation of the Microsoft Remote Desktop Protocol, with a focus on security.
[![](https://docs.rs/ironrdp/badge.svg)](https://docs.rs/ironrdp/) [![](https://img.shields.io/crates/v/ironrdp)](https://crates.io/crates/ironrdp)
A collection of Rust crates providing an implementation of the Microsoft Remote Desktop Protocol, with a focus on security.
## Demonstration
<https://user-images.githubusercontent.com/3809077/202049929-76f42471-aeb0-41da-9118-0dc6ea491bd2.mp4>
## Video Codec Support
Supported codecs:
- Uncompressed raw bitmap
- Interleaved Run-Length Encoding (RLE) Bitmap Codec
- RDP 6.0 Bitmap Compression
- Microsoft RemoteFX (RFX)
## Examples
### [`ironrdp-client`](https://github.com/Devolutions/IronRDP/tree/master/crates/ironrdp-client)
A full-fledged RDP client based on IronRDP crates suite, and implemented using non-blocking, asynchronous I/O.
```shell
cargo run --bin ironrdp-client -- <HOSTNAME> --username <USERNAME> --password <PASSWORD>
```
### [`screenshot`](https://github.com/Devolutions/IronRDP/blob/master/crates/ironrdp/examples/screenshot.rs)
Example of utilizing IronRDP in a blocking, synchronous fashion.
This example showcases the use of IronRDP in a blocking manner. It
demonstrates how to create a basic RDP client with just a few hundred lines
of code by leveraging the IronRDP crates suite.
In this basic client implementation, the client establishes a connection
with the destination server, decodes incoming graphics updates, and saves the
resulting output as a BMP image file on the disk.
```shell
cargo run --example=screenshot -- --host <HOSTNAME> --username <USERNAME> --password <PASSWORD> --output out.bmp
```
### How to enable RemoteFX on server
Run the following PowerShell commands, and reboot.
```pwsh
Set-ItemProperty -Path 'HKLM:\Software\Policies\Microsoft\Windows NT\Terminal Services' -Name 'ColorDepth' -Type DWORD -Value 5
Set-ItemProperty -Path 'HKLM:\Software\Policies\Microsoft\Windows NT\Terminal Services' -Name 'fEnableVirtualizedGraphics' -Type DWORD -Value 1
```
Alternatively, you may change a few group policies using `gpedit.msc`:
1. Run `gpedit.msc`.
2. Enable `Computer Configuration/Administrative Templates/Windows Components/Remote Desktop Services/Remote Desktop Session Host/Remote Session Environment/RemoteFX for Windows Server 2008 R2/Configure RemoteFX`
3. Enable `Computer Configuration/Administrative Templates/Windows Components/Remote Desktop Services/Remote Desktop Session Host/Remote Session Environment/Enable RemoteFX encoding for RemoteFX clients designed for Windows Server 2008 R2 SP1`
4. Enable `Computer Configuration/Administrative Templates/Windows Components/Remote Desktop Services/Remote Desktop Session Host/Remote Session Environment/Limit maximum color depth`
5. Reboot.
## Architecture
See the [ARCHITECTURE.md](https://github.com/Devolutions/IronRDP/blob/master/ARCHITECTURE.md) document.
## Getting help
- Report bugs in the [issue tracker](https://github.com/Devolutions/IronRDP/issues)
- Discuss the project on the [matrix room](https://matrix.to/#/#IronRDP:matrix.org)

623
STYLE.md Normal file
View file

@ -0,0 +1,623 @@
Our approach to "clean code" is two-fold:
- we avoid blocking PRs on style changes, but
- at the same time, the codebase is constantly refactored.
It is explicitly OK for a reviewer to flag only some nits in the PR, and then send a follow-up cleanup PR for things which are easier to explain by example, cc'ing the original author.
Sending small cleanup PRs (like renaming a single local variable) is encouraged.
These PRs are easy to merge and very welcomed.
When reviewing pull requests prefer extending this document to leaving non-reusable comments on the pull request itself.
# Style
## Formatting for sizes / lengths (e.g.: in `Encode::size()` and `FIXED_PART_SIZE` definitions)
Use an inline comment for each field of the structure.
```rust
// GOOD
const FIXED_PART_SIZE: usize = 1 /* Version */ + 1 /* Endianness */ + 2 /* CommonHeaderLength */ + 4 /* Filler */;
// GOOD
const FIXED_PART_SIZE: usize = 1 // Version
+ 1 // Endianness
+ 2 // CommonHeaderLength
+ 4; // Filler
// GOOD
fn size(&self) -> usize {
4 // ReturnCode
+ 4 // cBytes
+ self.reader_names.size() // mszReaderNames
+ 4 // dwState
+ 4 // dwProtocol
+ self.atr.len() // pbAtr
+ 4 // cbAtrLen
}
// BAD
const FIXED_PART_SIZE: usize = 1 + 1 + 2 + 4;
// BAD
const FIXED_PART_SIZE: usize = size_of::<u8>() + size_of::<u8>() + size_of::<u16>() + size_of::<u32>();
// BAD
fn size(&self) -> usize {
size_of::<u32>() * 5 + self.reader_names.size() + self.atr.len()
}
```
**Rationale**: boring and readable, having a comment with the name of the field is useful when following along the documentation.
Here is an excerpt illustrating this:
![Documentation excerpt](https://user-images.githubusercontent.com/3809077/272724889-681a83c9-aa83-4f48-85f4-0721c3148508.png)
`size_of::<u8>()` by itself is not really more useful than writing `1` directly.
The size of `u8` is not going to change, and its not hard to predict.
The struct also does not necessarily directly hold a `u8` as-is, and it may be hard to correlate a wrapper type with the corresponding `size_of::<u8>()`.
The memory representation of the wrapper type may differ from its network representation, so its not possible to always replace with `size_of::<Wrapper>()` instead.
## Error handling
### Return type
Use `crate_name::Result` (e.g.: `anyhow::Result`) rather than just `Result`.
**Rationale:** makes it immediately clear what result that is.
Exception: its not necessary when the type alias is clear enough (e.g.: `ConnectionResult`).
### Formatting of error messages
A single sentence which:
- is short and concise,
- does not start by a capital letter, and
- does not contain trailing punctuation.
This is the convention adopted by the Rust project:
- [Rust API Guidelines][api-guidelines-errors]
- [std::error::Error][std-error-trait]
Also, use proper abbreviation casing, e.g., IPv4 and IPv6 (not ipv4/ipv6).
```rust
// GOOD
"invalid X.509 certificate"
// BAD
"Invalid X.509 certificate."
```
**Rationale**: its easier to compose with other error messages.
To illustrate with terminal error reports:
```
// GOOD
Error: invalid server license, caused by invalid X.509 certificate, caused by unexpected ASN.1 DER tag: expected SEQUENCE, got CONTEXT-SPECIFIC [19] (primitive)
// BAD
Error: Invalid server license., Caused by Invalid X.509 certificate., Caused by Unexpected ASN.1 DER tag: expected SEQUENCE, got CONTEXT-SPECIFIC [19] (primitive)
```
The error reporter (e.g.: `ironrdp_error::ErrorReport`) is responsible for adding the punctuation and/or capitalizing the text down the line.
[api-guidelines-errors]: https://rust-lang.github.io/api-guidelines/interoperability.html#error-types-are-meaningful-and-well-behaved-c-good-err
[std-error-trait]: https://doc.rust-lang.org/stable/std/error/trait.Error.html
## Logging
If any, the human-readable message should start with a capital letter and not end with a period.
```rust
// GOOD
info!("Connect to RDP host");
// BAD
info!("connect to RDP host.");
```
**Rationale**: consistency.
Log messages are typically not composed together like error messages, so its fine to start with a capital letter.
Use tracing ability to [record structured fields][tracing-fields].
```rust
// GOOD
info!(%server_addr, "Looked up server address");
// BAD
info!("Looked up server address: {server_addr}");
```
**Rationale**: structured diagnostic information is tracings strength.
Its possible to retrieve the records emitted by tracing in a structured manner.
Name fields after what already exist consistently as much as possible.
For example, errors are typically recorded as fields named `error`.
```rust
// GOOD
error!(?error, "Active stage failed");
error!(error = ?e, "Active stage failed");
error!(%error, "Active stage failed");
error!(error = format!("{err:#}"), "Active stage failed");
// BAD
error!(?e, "Active stage failed");
error!(%err, "Active stage failed");
```
**Rationale**: consistency.
We can rely on this to filter and collect diagnostics.
[tracing-fields]: https://docs.rs/tracing/latest/tracing/index.html#recording-fields
## Helper functions
Avoid creating single-use helper functions:
```rust
// GOOD
let buf = {
let mut buf = WriteBuf::new();
buf.write_u32(42);
buf
};
// BAD
let buf = prepare_buf(42);
// Somewhere else
fn prepare_buf(value: u32) -> WriteBuf {
let mut buf = WriteBuf::new();
buf.write_u32(value);
buf
}
```
**Rationale:** single-use functions change frequently, adding or removing parameters adds churn.
A block serves just as well to delineate a bit of logic, but has access to all the context.
Re-using originally single-purpose function often leads to bad coupling.
Exception: if you want to make use of `return` or `?`.
## Local helper functions
Put nested helper functions at the end of the enclosing functions (this requires using return statement).
Don't nest more than one level deep.
```rust
// GOOD
fn func() -> u32 {
return helper();
fn helper() -> u32 {
/* ... */
}
}
// BAD
fn func() -> u32 {
fn helper() -> u32 {
/* ... */
}
helper()
}
```
**Rationale:** consistency, improved top-down readability.
## Documentation
### Doc comments should link to reference documents
Add links to specification and/or other relevant documents in doc comments.
Include verbatim the name of the section or the description of the item from the specification.
Use reference-style links for readability.
Do not make the link too long.
```rust
// GOOD
/// [2.2.3.3.8] Server Drive Query Information Request (DR_DRIVE_QUERY_INFORMATION_REQ)
///
/// The server issues a query information request on a redirected file system device.
///
/// [2.2.3.3.8]: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/e43dcd68-2980-40a9-9238-344b6cf94946
pub struct ServerDriveQueryInformationRequest {
/* snip */
}
// BAD (no doc comment)
pub struct ServerDriveQueryInformationRequest {
/* snip */
}
// BAD (non reference-style links make barely readable, very long lines)
/// [2.2.3.3.8](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/e43dcd68-2980-40a9-9238-344b6cf94946) Server Drive Query Information Request (DR_DRIVE_QUERY_INFORMATION_REQ)
///
/// The server issues a query information request on a redirected file system device.
pub struct ServerDriveQueryInformationRequest {
/* snip */
}
// BAD (long link)
/// [2.2.3.3.8 Server Drive Query Information Request (DR_DRIVE_QUERY_INFORMATION_REQ)]
///
/// The server issues a query information request on a redirected file system device.
///
/// [2.2.3.3.8 Server Drive Query Information Request (DR_DRIVE_QUERY_INFORMATION_REQ)]: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/e43dcd68-2980-40a9-9238-344b6cf94946
pub struct ServerDriveQueryInformationRequest {
/* snip */
}
```
**Rationale**: consistency.
Easy cross-referencing between code and reference documents.
### Inline code comments are proper sentences
Style inline code comments as proper sentences.
Start with a capital letter, end with a dot.
```rust
// GOOD
// When building a library, `-` in the artifact name are replaced by `_`.
let artifact_name = format!("{}.wasm", package.replace('-', "_"));
// BAD
// when building a library, `-` in the artifact name are replaced by `_`
let artifact_name = format!("{}.wasm", package.replace('-', "_"));
```
**Rationale:** writing a sentence (or maybe even a paragraph) rather just "a comment" creates a more appropriate frame of mind.
It tricks you into writing down more of the context you keep in your head while coding.
Exception: no period for brief comments (e.g., `// VER`, `// RSV`, `// ATYP`)
### "Sentence per line" style
For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines.
If the line is too long, you want to split the sentence in two.
**Rationale:** much easier to edit the text and read the diff, see [this link][asciidoctor-practices].
[asciidoctor-practices]: https://asciidoctor.org/docs/asciidoc-recommended-practices/#one-sentence-per-line
## Invariants
Recommended reads:
- <https://en.wikipedia.org/wiki/Invariant_(mathematics)#Invariants_in_computer_science>
- <https://en.wikipedia.org/wiki/Loop_invariant>
- <https://en.wikipedia.org/wiki/Class_invariant>
- <https://matklad.github.io/2023/10/06/what-is-an-invariant.html>
- <https://matklad.github.io/2023/09/13/comparative-analysis.html>
### Write down invariants clearly
Write down invariants using `INVARIANT:` code comments.
```rust
// GOOD
// INVARIANT: for i in 0..lo: xs[i] < x
// BAD
// for i in 0..lo: xs[i] < x
```
**Rationale**: invariants should be upheld at all times.
Its useful to keep invariants in mind when analyzing the flow of the code.
Its easy to look up the local invariants when programming "in the small".
For field invariants, a doc comment should come at the place where they are declared, inside the type definition.
```rust
// GOOD
struct BitmapInfoHeader {
/// INVARIANT: `width.abs() <= u16::MAX`
width: i32,
}
// BAD
/// INVARIANT: `width.abs() <= u16::MAX`
struct BitmapInfoHeader {
width: i32,
}
// BAD
struct BitmapInfoHeader {
width: i32,
}
impl BitmapInfoHeader {
fn new(width: i32) -> Option<BitmapInfoHeader> {
// INVARIANT: width.abs() <= u16::MAX
if !(width.abs() <= i32::from(u16::MAX)) {
return None;
}
Some(BitmapInfoHeader { width })
}
}
```
**Rationale**: its easy to find about the invariant.
The invariant will show up in the documentation (typically available by hovering the item in IDEs).
For loop invariants, the comment should come before or at the beginning of the loop.
```rust
// GOOD
/// Computes the smallest index such that, if `x` is inserted at this index, the array remains sorted.
fn insertion_point(xs: &[i32], x: i32) -> usize {
let mut lo = 0;
let mut hi = xs.len();
while lo < hi {
// INVARIANT: for i in 0..lo: xs[i] < x
// INVARIANT: for i in hi..: x <= xs[i]
let mid = lo + (hi - lo) / 2;
if xs[mid] < x {
lo = mid + 1;
} else {
hi = mid;
}
}
lo
}
// BAD
fn insertion_point(xs: &[i32], x: i32) -> usize {
let mut lo = 0;
let mut hi = xs.len();
while lo < hi {
let mid = lo + (hi - lo) / 2;
if xs[mid] < x {
lo = mid + 1;
} else {
hi = mid;
}
}
// INVARIANT: for i in 0..lo: xs[i] < x
// INVARIANT: for i in hi..: x <= xs[i]
lo
}
```
**Rationale**: improved top-down readability, only read forward, no need to backtrack.
For function output invariants, the comment should be specified in the doc comment.
(However, consider [enforcing this invariant][parse-dont-validate] using [the type system][type-safety] instead.)
```rust
// GOOD
/// Computes the stride of an uncompressed RGB bitmap.
///
/// INVARIANT: `width <= output (stride) <= width * 4`
fn rgb_bmp_stride(width: u16, bit_count: u16) -> usize {
assert!(bit_count <= 32);
let stride = /* ... */;
stride
}
// BAD
/// Computes the stride of an uncompressed RGB bitmap.
fn rgb_bmp_stride(width: u16, bit_count: u16) -> usize {
assert!(bit_count <= 32);
// INVARIANT: width <= stride <= width * 4
let stride = /* ... */;
stride
}
```
**Rationale**: its easy to find about the invariant.
The invariant will show up in the documentation (typically available by hovering the item in IDEs).
[parse-dont-validate]: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/
[type-safety]: https://www.parsonsmatt.org/2017/10/11/type_safety_back_and_forth.html
### Explain non-obvious assumptions by referencing the invariants
Explain clearly non-obvious assumptions and invariants relied upon (e.g.: when disabling a lint locally).
When referencing invariants, do not use the `INVARIANT:` comment prefix which is reserved for defining them.
```rust
// GOOD
// Per invariants: width * dst_n_samples <= 10_000 * 4 < usize::MAX
#[allow(clippy::arithmetic_side_effects)]
let dst_stride = usize::from(width) * dst_n_samples;
// BAD
#[allow(clippy::arithmetic_side_effects)]
let dst_stride = usize::from(width) * dst_n_samples;
// BAD
// INVARIANT: width * dst_n_samples <= 10_000 * 4 < usize::MAX
#[allow(clippy::arithmetic_side_effects)]
let dst_stride = usize::from(width) * dst_n_samples;
```
**Rationale**: make the assumption obvious.
The code is easier to review.
No one will lose time refactoring based on the wrong assumption.
### State invariants positively
Establish invariants positively.
Prefer `if !invariant` to `if negated_invariant`.
```rust
// GOOD
if !(idx < len) {
return None;
}
// GOOD
check_invariant(idx < len)?;
// GOOD
ensure!(idx < len);
// GOOD
debug_assert!(idx < len);
// GOOD
if idx < len {
/* ... */
} else {
return None;
}
// BAD
if idx >= len {
return None;
}
```
**Rationale:** it's useful to see the invariant relied upon by the rest of the function clearly spelled out.
### Strongly prefer `<` and `<=` over `>` and `>=`
Use `<` and `<=` operators instead of `>` and `>=`.
```rust
/// GOOD
if lo <= x && x <= hi {}
if x < lo || hi < x {}
/// BAD
if x >= lo && x <= hi {}
if x < lo || x > hi {}
```
**Rationale**: consistent, canonicalized form that is trivial to visualize by reading from left to right.
Things are naturally ordered from small to big like in the [number line].
[number line]: https://en.wikipedia.org/wiki/Number_line
## Context parameters
Some parameters are threaded unchanged through many function calls.
They determine the "context" of the operation.
Pass such parameters first, not last.
If there are several context parameters, consider [packing them into a `struct Ctx` and passing it as `&self`][ra-ctx-struct].
```rust
// GOOD
fn do_something(connector: &mut ClientConnector, certificate: &[u8]) {
let public_key = extract_public_key(certificate);
do_something_else(connector, public_key, |kind| /* … */);
}
fn do_something_else(connector: &mut ClientConnector, public_key: &[u8], op: impl Fn(KeyKind) -> bool) {
/* ... */
}
// BAD
fn do_something(certificate: &[u8], connector: &mut ClientConnector) {
let public_key = extract_public_key(certificate);
do_something_else(|kind| /* … */, connector, public_key);
}
fn do_something_else(op: impl Fn(KeyKind) -> bool, connector: &mut ClientConnector, public_key: &[u8]) {
/* ... */
}
```
**Rationale:** consistency.
Context-first works better when non-context parameter is a lambda.
[ra-ctx-struct]: https://github.com/rust-lang/rust-analyzer/blob/76633199f4316b9c659d4ec0c102774d693cd940/crates/ide-db/src/path_transform.rs#L192-L339
# Runtime and compile time performance
## Avoid allocations
Avoid writing code which is slower than it needs to be.
Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly.
```rust
// GOOD
let second_word = text.split(' ').nth(1)?;
// BAD
let words: Vec<&str> = text.split(' ').collect();
let second_word = words.get(1)?;
```
**Rationale:** not allocating is almost always faster.
## Push allocations to the call site
If allocation is inevitable, let the caller allocate the resource:
```rust
// GOOD
fn frobnicate(s: String) {
/* snip */
}
// BAD
fn frobnicate(s: &str) {
let s = s.to_string();
/* snip */
}
```
**Rationale:** reveals the costs.
It is also more efficient when the caller already owns the allocation.
## Avoid monomorphization
Avoid making a lot of code type parametric, *especially* on the boundaries between crates.
```rust
// GOOD
fn frobnicate(f: impl FnMut()) {
frobnicate_impl(&mut f)
}
fn frobnicate_impl(f: &mut dyn FnMut()) {
/* lots of code */
}
// BAD
fn frobnicate(f: impl FnMut()) {
/* lots of code */
}
```
Avoid `AsRef` polymorphism, it pays back only for widely used libraries:
```rust
// GOOD
fn frobnicate(f: &Path) { }
// BAD
fn frobnicate(f: impl AsRef<Path>) { }
```
**Rationale:** Rust uses monomorphization to compile generic code, meaning that for each instantiation of a generic functions with concrete types, the function is compiled afresh, *per crate*.
This allows for fantastic performance, but leads to increased compile times.
Runtime performance obeys the 80/20 rule (Pareto Principle) — only a small fraction of code is hot.
Compile time **does not** obey this rule — all code has to be compiled.

32
benches/Cargo.toml Normal file
View file

@ -0,0 +1,32 @@
[package]
name = "benches"
version = "0.0.0"
description = "IronRDP benchmarks"
publish = false
edition.workspace = true
[[bin]]
name = "perfenc"
path = "src/perfenc.rs"
[features]
default = ["qoi", "qoiz"]
qoi = ["ironrdp/qoi"]
qoiz = ["ironrdp/qoiz"]
[dependencies]
anyhow = "1.0.99"
async-trait = "0.1.89"
bytesize = "2.3"
ironrdp = { path = "../crates/ironrdp", features = [
"server",
"pdu",
"__bench",
] }
pico-args = "0.5.0"
tokio = { version = "1", features = ["sync", "fs", "time"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing = { version = "0.1", features = ["log"] }
[lints]
workspace = true

210
benches/src/perfenc.rs Normal file
View file

@ -0,0 +1,210 @@
#![allow(unused_crate_dependencies)] // False positives because there are both a library and a binary.
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
use core::num::{NonZeroU16, NonZeroUsize};
use core::time::Duration;
use std::io::Write as _;
use std::time::Instant;
use anyhow::Context as _;
use ironrdp::pdu::rdp::capability_sets::{CmdFlags, EntropyBits};
use ironrdp::server::bench::encoder::{UpdateEncoder, UpdateEncoderCodecs};
use ironrdp::server::{BitmapUpdate, DesktopSize, DisplayUpdate, PixelFormat, RdpServerDisplayUpdates};
use tokio::fs::File;
use tokio::io::AsyncReadExt as _;
use tokio::time::sleep;
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), anyhow::Error> {
setup_logging()?;
let mut args = pico_args::Arguments::from_env();
if args.contains(["-h", "--help"]) {
println!("Usage: perfenc [OPTIONS] <RGBX_INPUT_FILENAME>");
println!();
println!("Measure the performance of the IronRDP server encoder, given a raw RGBX video input file.");
println!();
println!("Options:");
println!(" --width <WIDTH> Width of the display (default: 3840)");
println!(" --height <HEIGHT> Height of the display (default: 2400)");
println!(" --codec <CODEC> Codec to use (default: remotefx)");
println!(" Valid values: qoi, qoiz, remotefx, bitmap, none");
println!(" --fps <FPS> Frames per second (default: none)");
std::process::exit(0);
}
let width = args.opt_value_from_str("--width")?.unwrap_or(3840);
let height = args.opt_value_from_str("--height")?.unwrap_or(2400);
let codec = args.opt_value_from_str("--codec")?.unwrap_or_else(OptCodec::default);
let fps = args.opt_value_from_str("--fps")?.unwrap_or(0);
let filename: String = args.free_from_str().context("missing RGBX input filename")?;
let file = File::open(&filename)
.await
.with_context(|| format!("Failed to open file: {filename}"))?;
let mut flags = CmdFlags::all();
let mut update_codecs = UpdateEncoderCodecs::new();
match codec {
OptCodec::RemoteFX => update_codecs.set_remotefx(Some((EntropyBits::Rlgr3, 0))),
OptCodec::Bitmap => {
flags -= CmdFlags::SET_SURFACE_BITS;
}
OptCodec::None => {}
#[cfg(feature = "qoi")]
OptCodec::Qoi => update_codecs.set_qoi(Some(0)),
#[cfg(feature = "qoiz")]
OptCodec::QoiZ => update_codecs.set_qoiz(Some(0)),
};
let mut encoder = UpdateEncoder::new(DesktopSize { width, height }, flags, update_codecs)
.context("failed to initialize update encoder")?;
let mut total_raw = 0u64;
let mut total_enc = 0u64;
let mut n_updates = 0u64;
let mut updates = DisplayUpdates::new(file, DesktopSize { width, height }, fps);
while let Some(up) = updates.next_update().await? {
if let DisplayUpdate::Bitmap(ref up) = up {
total_raw += u64::try_from(up.data.len())?;
} else {
eprintln!("Invalid update");
break;
}
let mut iter = encoder.update(up);
loop {
let Some(frag) = iter.next().await else {
break;
};
let len = u64::try_from(frag?.data.len())?;
total_enc += len;
}
n_updates += 1;
print!(".");
std::io::stdout().flush()?;
}
println!();
#[expect(clippy::as_conversions, reason = "casting u64 to f64")]
let ratio = total_enc as f64 / total_raw as f64;
let percent = 100.0 - ratio * 100.0;
println!("Encoder: {encoder:?}");
println!("Nb updates: {n_updates:?}");
println!(
"Sum of bytes: {}/{} ({:.2}%)",
bytesize::ByteSize(total_enc),
bytesize::ByteSize(total_raw),
percent,
);
Ok(())
}
struct DisplayUpdates {
file: File,
desktop_size: DesktopSize,
fps: u64,
last_update_time: Option<Instant>,
}
impl DisplayUpdates {
fn new(file: File, desktop_size: DesktopSize, fps: u64) -> Self {
Self {
file,
desktop_size,
fps,
last_update_time: None,
}
}
}
#[async_trait::async_trait]
impl RdpServerDisplayUpdates for DisplayUpdates {
async fn next_update(&mut self) -> anyhow::Result<Option<DisplayUpdate>> {
let stride = self.desktop_size.width as usize * 4;
let frame_size = stride * self.desktop_size.height as usize;
let mut buf = vec![0u8; frame_size];
// FIXME: AsyncReadExt::read_exact is not cancellation safe.
self.file.read_exact(&mut buf).await.context("read exact")?;
let now = Instant::now();
if let Some(last_update_time) = self.last_update_time {
let elapsed = now - last_update_time;
if self.fps > 0 && elapsed < Duration::from_millis(1000 / self.fps) {
sleep(Duration::from_millis(
1000 / self.fps
- u64::try_from(elapsed.as_millis())
.context("invalid `elapsed millis`: out of range integral conversion")?,
))
.await;
}
}
self.last_update_time = Some(now);
let up = DisplayUpdate::Bitmap(BitmapUpdate {
x: 0,
y: 0,
width: NonZeroU16::new(self.desktop_size.width).context("width cannot be zero")?,
height: NonZeroU16::new(self.desktop_size.height).context("height cannot be zero")?,
format: PixelFormat::RgbX32,
data: buf.into(),
stride: NonZeroUsize::new(stride).context("stride cannot be zero")?,
});
Ok(Some(up))
}
}
fn setup_logging() -> anyhow::Result<()> {
use tracing::metadata::LevelFilter;
use tracing_subscriber::prelude::*;
use tracing_subscriber::EnvFilter;
let fmt_layer = tracing_subscriber::fmt::layer().compact();
let env_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::WARN.into())
.with_env_var("IRONRDP_LOG")
.from_env_lossy();
tracing_subscriber::registry()
.with(fmt_layer)
.with(env_filter)
.try_init()
.context("failed to set tracing global subscriber")?;
Ok(())
}
enum OptCodec {
RemoteFX,
Bitmap,
None,
#[cfg(feature = "qoi")]
Qoi,
#[cfg(feature = "qoiz")]
QoiZ,
}
impl Default for OptCodec {
fn default() -> Self {
Self::RemoteFX
}
}
impl core::str::FromStr for OptCodec {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"remotefx" => Ok(Self::RemoteFX),
"bitmap" => Ok(Self::Bitmap),
"none" => Ok(Self::None),
#[cfg(feature = "qoi")]
"qoi" => Ok(Self::Qoi),
#[cfg(feature = "qoiz")]
"qoiz" => Ok(Self::QoiZ),
_ => anyhow::bail!("unknown codec: {s}"),
}
}
}

View file

@ -1,13 +0,0 @@
set -ex
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo build
cargo build --release
cargo build --all --exclude=ironrdp_client --target wasm32-unknown-unknown
cargo build --all --exclude=ironrdp_client --target wasm32-unknown-unknown --release
cargo test
cargo test --release

94
cliff.toml Normal file
View file

@ -0,0 +1,94 @@
# Configuration file for git-cliff
[changelog]
trim = false
header = """
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
"""
# https://tera.netlify.app/docs/#introduction
body = """
{% if version -%}
## [[{{ version | trim_start_matches(pat="v") }}]{%- if release_link -%}({{ release_link }}){% endif %}] - {{ timestamp | date(format="%Y-%m-%d") }}
{%- else -%}
## [Unreleased]
{%- endif %}
{% for group, commits in commits | group_by(attribute="group") -%}
### {{ group | upper_first }}
{%- for commit in commits %}
{%- set message = commit.message | upper_first %}
{%- if commit.breaking %}
{%- set breaking = "[**breaking**] " %}
{%- else %}
{%- set breaking = "" %}
{%- endif %}
{%- set short_sha = commit.id | truncate(length=10, end="") %}
{%- set commit_url = "https://github.com/Devolutions/IronRDP/commit/" ~ commit.id %}
{%- set commit_link = "[" ~ short_sha ~ "](" ~ commit_url ~ ")" %}
- {{ breaking }}{{ message }} ({{ commit_link }}) \
{% if commit.body %}\n\n {{ commit.body | replace(from="\n", to="\n ") }}{% endif %}
{%- endfor %}
{% endfor -%}
"""
footer = ""
[git]
conventional_commits = true
filter_unconventional = false
filter_commits = false
date_order = false
protect_breaking_commits = true
sort_commits = "oldest"
commit_preprocessors = [
# Replace the issue number with the link.
{ pattern = "\\(#([0-9]+)\\)", replace = "([#${1}](https://github.com/Devolutions/IronRDP/issues/${1}))" },
# Replace commit sha1 with the link.
{ pattern = '([a-f0-9]{10})([a-f0-9]{30})', replace = "[${0}](https://github.com/Devolutions/IronRDP/commit/${1}${2})" },
]
# regex for parsing and grouping commits
# <!-- <NUMBER> --> is a trick to control the section order: https://github.com/orhun/git-cliff/issues/9#issuecomment-914521594
commit_parsers = [
{ message = "^chore", skip = true },
{ message = "^style", skip = true },
{ message = "^refactor", skip = true },
{ message = "^test", skip = true },
{ message = "^ci", skip = true },
{ message = "^chore\\(release\\): prepare for", skip = true },
{ footer = "^[Cc]hangelog: ?ignore", skip = true },
{ message = "(?i)security", group = "<!-- 0 -->Security" },
{ body = "(?i)security", group = "<!-- 0 -->Security" },
{ footer = "^[Ss]ecurity: ?yes", group = "<!-- 0 -->Security" },
{ message = "^feat", group = "<!-- 1 -->Features" },
{ message = "^revert", group = "<!-- 3 -->Revert" },
{ message = "^fix", group = "<!-- 4 -->Bug Fixes" },
{ message = "^perf", group = "<!-- 5 -->Performance" },
{ message = "^doc", group = "<!-- 6 -->Documentation" },
{ message = "^build", group = "<!-- 7 -->Build" },
{ message = "(?i)improve", group = "<!-- 2 -->Improvements" },
{ message = "(?i)adjust", group = "<!-- 2 -->Improvements" },
{ message = "(?i)change", group = "<!-- 2 -->Improvements" },
{ message = ".*", group = "<!-- 99 -->Please Sort" },
]

6
clippy.toml Normal file
View file

@ -0,0 +1,6 @@
msrv = "1.87"
semicolon-outside-block-ignore-multiline = true
accept-comment-above-statement = true
accept-comment-above-attributes = true
allow-panic-in-tests = true
allow-unwrap-in-tests = true

View file

@ -0,0 +1,39 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [[0.7.0](https://github.com/Devolutions/IronRDP/compare/iron-remote-desktop-v0.6.0...iron-remote-desktop-v0.7.0)] - 2025-09-29
### <!-- 4 -->Bug Fixes
- [**breaking**] Changed onClipboardChanged to not consume the input (#992) ([6127e13c83](https://github.com/Devolutions/IronRDP/commit/6127e13c836d06764d483b6b55188fd23a4314a2))
## [[0.6.0](https://github.com/Devolutions/IronRDP/compare/iron-remote-desktop-v0.5.0...iron-remote-desktop-v0.6.0)] - 2025-08-29
### <!-- 1 -->Features
- [**breaking**] Extend `DeviceEvent.wheelRotations` event to support passing rotation units other than pixels (#952) ([23c0cc2c36](https://github.com/Devolutions/IronRDP/commit/23c0cc2c365159d24330a89ec4015121b67bccb6))
## [[0.5.0](https://github.com/Devolutions/IronRDP/compare/iron-remote-desktop-v0.4.0...iron-remote-desktop-v0.5.0)] - 2025-08-29
### <!-- 4 -->Bug Fixes
- [**breaking**] Remove the `remote_received_format_list_callback` method from Session common API (#935) ([5b948e2161](https://github.com/Devolutions/IronRDP/commit/5b948e2161b08b13d32bdbb480b26c8fa44d42f7))
## [[0.4.0](https://github.com/Devolutions/IronRDP/compare/iron-remote-desktop-v0.3.0...iron-remote-desktop-v0.4.0)] - 2025-06-27
### <!-- 1 -->Features
- [**breaking**] Add `canvas_resized_callback` method to `SessionBuilder` trait (#842) ([f6285c5989](https://github.com/Devolutions/IronRDP/commit/f6285c598915c8afb07553c765648d85ac4140cb))
## [[0.3.0](https://github.com/Devolutions/IronRDP/compare/iron-remote-desktop-v0.2.0...iron-remote-desktop-v0.3.0)] - 2025-06-03
### <!-- 4 -->Bug Fixes
- [**breaking**] Rename extension_call to invoke_extension (#803) ([f68cd06ac3](https://github.com/Devolutions/IronRDP/commit/f68cd06ac3705608e6f2ac6bde684d9ae906ea53))

View file

@ -0,0 +1,34 @@
[package]
name = "iron-remote-desktop"
version = "0.7.0"
readme = "README.md"
description = "Helper crate for building WASM modules compatible with iron-remote-desktop WebComponent"
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
authors.workspace = true
keywords.workspace = true
categories.workspace = true
[features]
panic_hook = ["dep:console_error_panic_hook"]
[dependencies]
# WASM
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["HtmlCanvasElement"] }
tracing-web = "0.1"
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1", optional = true }
# Logging
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3", features = ["time"] }
[lints]
workspace = true

View file

@ -0,0 +1,8 @@
# Iron Remote Desktop — Helper Crate
Helper crate for building WASM modules compatible with the `iron-remote-desktop` WebComponent.
Implement the `RemoteDesktopApi` trait on a Rust type, and call the `make_bridge!` on
it to generate the WASM API that is expected by `iron-remote-desktop`.
See the `ironrdp-web` crate in the repository to see how it is used in practice.

View file

@ -0,0 +1,23 @@
use wasm_bindgen::JsValue;
pub trait ClipboardData {
type Item: ClipboardItem;
fn create() -> Self;
fn add_text(&mut self, mime_type: &str, text: &str);
fn add_binary(&mut self, mime_type: &str, binary: &[u8]);
fn items(&self) -> &[Self::Item];
fn is_empty(&self) -> bool {
self.items().is_empty()
}
}
pub trait ClipboardItem {
fn mime_type(&self) -> &str;
fn value(&self) -> impl Into<JsValue>;
}

View file

@ -0,0 +1,10 @@
#[derive(Debug)]
pub enum CursorStyle {
Default,
Hidden,
Url {
data: String,
hotspot_x: u16,
hotspot_y: u16,
},
}

View file

@ -0,0 +1,16 @@
use wasm_bindgen::prelude::wasm_bindgen;
#[wasm_bindgen]
#[derive(Clone, Copy)]
pub struct DesktopSize {
pub width: u16,
pub height: u16,
}
#[wasm_bindgen]
impl DesktopSize {
#[wasm_bindgen(constructor)]
pub fn create(width: u16, height: u16) -> Self {
DesktopSize { width, height }
}
}

View file

@ -0,0 +1,26 @@
use wasm_bindgen::prelude::*;
pub trait IronError {
fn backtrace(&self) -> String;
fn kind(&self) -> IronErrorKind;
}
#[derive(Clone, Copy)]
#[wasm_bindgen]
pub enum IronErrorKind {
/// Catch-all error kind
General,
/// Incorrect password used
WrongPassword,
/// Unable to login to machine
LogonFailure,
/// Insufficient permission, server denied access
AccessDenied,
/// Something wrong happened when sending or receiving the RDCleanPath message
RDCleanPath,
/// Couldnt connect to proxy
ProxyConnect,
/// Protocol negotiation failed
NegotiationFailure,
}

View file

@ -0,0 +1,76 @@
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
#[macro_export]
macro_rules! extension_match {
( @ $jsval:expr, $value:ident, String, $operation:block ) => {{
if let Some($value) = $jsval.as_string() {
$operation
} else {
warn!("Unexpected value for extension {}", stringify!($ident));
}
}};
( @ $jsval:expr, $value:ident, f64, $operation:block ) => {{
if let Some($value) = $jsval.as_f64() {
$operation
} else {
warn!("Unexpected value for extension {}", stringify!($ident));
}
}};
( @ $jsval:expr, $value:ident, bool, $operation:block ) => {{
if let Some($value) = $jsval.as_bool() {
$operation
} else {
warn!("Unexpected value for extension {}", stringify!($ident));
}
}};
( @ $jsval:expr, $value:ident, JsValue, $operation:block ) => {{
let $value = $jsval;
$operation
}};
( match $ext:ident ; $( | $value:ident : $ty:ident | $operation:block ; )* ) => {
let ident = $ext.ident();
match ident {
$( stringify!($value) => $crate::extension_match!( @ $ext.into_value(), $value, $ty, $operation ), )*
unknown_extension => ::tracing::warn!("Unknown extension: {unknown_extension}"),
}
};
}
#[wasm_bindgen]
pub struct Extension {
ident: String,
value: JsValue,
}
#[wasm_bindgen]
impl Extension {
#[wasm_bindgen(constructor)]
pub fn create(ident: String, value: JsValue) -> Self {
Self { ident, value }
}
}
#[expect(
clippy::allow_attributes,
reason = "Unfortunately, expect attribute doesn't work with clippy::multiple_inherent_impl lint"
)]
#[allow(
clippy::multiple_inherent_impl,
reason = "We don't want to expose these methods to JS"
)]
impl Extension {
pub fn ident(&self) -> &str {
self.ident.as_str()
}
pub fn value(&self) -> &JsValue {
&self.value
}
pub fn into_value(self) -> JsValue {
self.value
}
}

View file

@ -0,0 +1,34 @@
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub enum RotationUnit {
Pixel,
Line,
Page,
}
pub trait DeviceEvent {
fn mouse_button_pressed(button: u8) -> Self;
fn mouse_button_released(button: u8) -> Self;
fn mouse_move(x: u16, y: u16) -> Self;
fn wheel_rotations(vertical: bool, rotation_amount: i16, rotation_unit: RotationUnit) -> Self;
fn key_pressed(scancode: u16) -> Self;
fn key_released(scancode: u16) -> Self;
fn unicode_pressed(unicode: char) -> Self;
fn unicode_released(unicode: char) -> Self;
}
pub trait InputTransaction {
type DeviceEvent: DeviceEvent;
fn create() -> Self;
fn add_event(&mut self, event: Self::DeviceEvent);
}

View file

@ -0,0 +1,480 @@
#![cfg_attr(doc, doc = include_str!("../README.md"))]
#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
mod clipboard;
mod cursor;
mod desktop_size;
mod error;
mod extension;
mod input;
mod session;
pub use clipboard::{ClipboardData, ClipboardItem};
pub use cursor::CursorStyle;
pub use desktop_size::DesktopSize;
pub use error::{IronError, IronErrorKind};
pub use extension::Extension;
pub use input::{DeviceEvent, InputTransaction, RotationUnit};
pub use session::{Session, SessionBuilder, SessionTerminationInfo};
pub trait RemoteDesktopApi {
type Session: Session;
type SessionBuilder: SessionBuilder;
type SessionTerminationInfo: SessionTerminationInfo;
type DeviceEvent: DeviceEvent;
type InputTransaction: InputTransaction;
type ClipboardData: ClipboardData;
type ClipboardItem: ClipboardItem;
type Error: IronError;
/// Called before the logger is set.
fn pre_setup() {}
/// Called after the logger is set.
fn post_setup() {}
}
#[macro_export]
macro_rules! make_bridge {
($api:ty) => {
#[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
pub struct Session(<$api as $crate::RemoteDesktopApi>::Session);
#[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
pub struct SessionBuilder(<$api as $crate::RemoteDesktopApi>::SessionBuilder);
#[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
pub struct SessionTerminationInfo(<$api as $crate::RemoteDesktopApi>::SessionTerminationInfo);
#[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
pub struct DeviceEvent(<$api as $crate::RemoteDesktopApi>::DeviceEvent);
#[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
pub struct InputTransaction(<$api as $crate::RemoteDesktopApi>::InputTransaction);
#[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
pub struct ClipboardData(<$api as $crate::RemoteDesktopApi>::ClipboardData);
#[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
pub struct ClipboardItem(<$api as $crate::RemoteDesktopApi>::ClipboardItem);
#[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
pub struct IronError(<$api as $crate::RemoteDesktopApi>::Error);
impl From<<$api as $crate::RemoteDesktopApi>::Session> for Session {
fn from(value: <$api as $crate::RemoteDesktopApi>::Session) -> Self {
Self(value)
}
}
impl From<<$api as $crate::RemoteDesktopApi>::SessionBuilder> for SessionBuilder {
fn from(value: <$api as $crate::RemoteDesktopApi>::SessionBuilder) -> Self {
Self(value)
}
}
impl From<<$api as $crate::RemoteDesktopApi>::SessionTerminationInfo> for SessionTerminationInfo {
fn from(value: <$api as $crate::RemoteDesktopApi>::SessionTerminationInfo) -> Self {
Self(value)
}
}
impl From<<$api as $crate::RemoteDesktopApi>::DeviceEvent> for DeviceEvent {
fn from(value: <$api as $crate::RemoteDesktopApi>::DeviceEvent) -> Self {
Self(value)
}
}
impl From<<$api as $crate::RemoteDesktopApi>::InputTransaction> for InputTransaction {
fn from(value: <$api as $crate::RemoteDesktopApi>::InputTransaction) -> Self {
Self(value)
}
}
impl From<<$api as $crate::RemoteDesktopApi>::ClipboardData> for ClipboardData {
fn from(value: <$api as $crate::RemoteDesktopApi>::ClipboardData) -> Self {
Self(value)
}
}
impl From<<$api as $crate::RemoteDesktopApi>::ClipboardItem> for ClipboardItem {
fn from(value: <$api as $crate::RemoteDesktopApi>::ClipboardItem) -> Self {
Self(value)
}
}
impl From<<$api as $crate::RemoteDesktopApi>::Error> for IronError {
fn from(value: <$api as $crate::RemoteDesktopApi>::Error) -> Self {
Self(value)
}
}
#[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
#[doc(hidden)]
pub fn setup(log_level: &str) {
<$api as $crate::RemoteDesktopApi>::pre_setup();
$crate::internal::setup(log_level);
<$api as $crate::RemoteDesktopApi>::post_setup();
}
#[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
#[doc(hidden)]
impl Session {
pub async fn run(&self) -> Result<SessionTerminationInfo, IronError> {
$crate::Session::run(&self.0)
.await
.map(SessionTerminationInfo)
.map_err(IronError)
}
#[wasm_bindgen(js_name = desktopSize)]
pub fn desktop_size(&self) -> $crate::DesktopSize {
$crate::Session::desktop_size(&self.0)
}
#[wasm_bindgen(js_name = applyInputs)]
pub fn apply_inputs(&self, transaction: InputTransaction) -> Result<(), IronError> {
$crate::Session::apply_inputs(&self.0, transaction.0).map_err(IronError)
}
#[wasm_bindgen(js_name = releaseAllInputs)]
pub fn release_all_inputs(&self) -> Result<(), IronError> {
$crate::Session::release_all_inputs(&self.0).map_err(IronError)
}
#[wasm_bindgen(js_name = synchronizeLockKeys)]
pub fn synchronize_lock_keys(
&self,
scroll_lock: bool,
num_lock: bool,
caps_lock: bool,
kana_lock: bool,
) -> Result<(), IronError> {
$crate::Session::synchronize_lock_keys(&self.0, scroll_lock, num_lock, caps_lock, kana_lock)
.map_err(IronError)
}
pub fn shutdown(&self) -> Result<(), IronError> {
$crate::Session::shutdown(&self.0).map_err(IronError)
}
#[wasm_bindgen(js_name = onClipboardPaste)]
pub async fn on_clipboard_paste(&self, content: &ClipboardData) -> Result<(), IronError> {
$crate::Session::on_clipboard_paste(&self.0, &content.0)
.await
.map_err(IronError)
}
pub fn resize(
&self,
width: u32,
height: u32,
scale_factor: Option<u32>,
physical_width: Option<u32>,
physical_height: Option<u32>,
) {
$crate::Session::resize(
&self.0,
width,
height,
scale_factor,
physical_width,
physical_height,
);
}
#[wasm_bindgen(js_name = supportsUnicodeKeyboardShortcuts)]
pub fn supports_unicode_keyboard_shortcuts(&self) -> bool {
$crate::Session::supports_unicode_keyboard_shortcuts(&self.0)
}
#[wasm_bindgen(js_name = invokeExtension)]
pub fn invoke_extension(
&self,
ext: $crate::Extension,
) -> Result<$crate::internal::wasm_bindgen::JsValue, IronError> {
<<$api as $crate::RemoteDesktopApi>::Session as $crate::Session>::invoke_extension(&self.0, ext)
.map_err(IronError)
}
}
#[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
#[doc(hidden)]
impl SessionBuilder {
#[wasm_bindgen(constructor)]
pub fn create() -> Self {
Self(<<$api as $crate::RemoteDesktopApi>::SessionBuilder as $crate::SessionBuilder>::create())
}
pub fn username(&self, username: String) -> Self {
Self($crate::SessionBuilder::username(&self.0, username))
}
pub fn destination(&self, destination: String) -> Self {
Self($crate::SessionBuilder::destination(&self.0, destination))
}
#[wasm_bindgen(js_name = serverDomain)]
pub fn server_domain(&self, server_domain: String) -> Self {
Self($crate::SessionBuilder::server_domain(&self.0, server_domain))
}
pub fn password(&self, password: String) -> Self {
Self($crate::SessionBuilder::password(&self.0, password))
}
#[wasm_bindgen(js_name = proxyAddress)]
pub fn proxy_address(&self, address: String) -> Self {
Self($crate::SessionBuilder::proxy_address(&self.0, address))
}
#[wasm_bindgen(js_name = authToken)]
pub fn auth_token(&self, token: String) -> Self {
Self($crate::SessionBuilder::auth_token(&self.0, token))
}
#[wasm_bindgen(js_name = desktopSize)]
pub fn desktop_size(&self, desktop_size: $crate::DesktopSize) -> Self {
Self($crate::SessionBuilder::desktop_size(&self.0, desktop_size))
}
#[wasm_bindgen(js_name = renderCanvas)]
pub fn render_canvas(&self, canvas: $crate::internal::web_sys::HtmlCanvasElement) -> Self {
Self($crate::SessionBuilder::render_canvas(&self.0, canvas))
}
#[wasm_bindgen(js_name = setCursorStyleCallback)]
pub fn set_cursor_style_callback(&self, callback: $crate::internal::web_sys::js_sys::Function) -> Self {
Self($crate::SessionBuilder::set_cursor_style_callback(
&self.0, callback,
))
}
#[wasm_bindgen(js_name = setCursorStyleCallbackContext)]
pub fn set_cursor_style_callback_context(&self, context: $crate::internal::wasm_bindgen::JsValue) -> Self {
Self($crate::SessionBuilder::set_cursor_style_callback_context(
&self.0, context,
))
}
#[wasm_bindgen(js_name = remoteClipboardChangedCallback)]
pub fn remote_clipboard_changed_callback(
&self,
callback: $crate::internal::web_sys::js_sys::Function,
) -> Self {
Self($crate::SessionBuilder::remote_clipboard_changed_callback(
&self.0, callback,
))
}
#[wasm_bindgen(js_name = forceClipboardUpdateCallback)]
pub fn force_clipboard_update_callback(
&self,
callback: $crate::internal::web_sys::js_sys::Function,
) -> Self {
Self($crate::SessionBuilder::force_clipboard_update_callback(
&self.0, callback,
))
}
#[wasm_bindgen(js_name = canvasResizedCallback)]
pub fn canvas_resized_callback(&self, callback: $crate::internal::web_sys::js_sys::Function) -> Self {
Self($crate::SessionBuilder::canvas_resized_callback(&self.0, callback))
}
pub fn extension(&self, ext: $crate::Extension) -> Self {
Self($crate::SessionBuilder::extension(&self.0, ext))
}
pub async fn connect(&self) -> Result<Session, IronError> {
$crate::SessionBuilder::connect(&self.0)
.await
.map(Session)
.map_err(IronError)
}
}
#[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
#[doc(hidden)]
impl SessionTerminationInfo {
pub fn reason(&self) -> String {
$crate::SessionTerminationInfo::reason(&self.0)
}
}
#[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
#[doc(hidden)]
impl DeviceEvent {
#[wasm_bindgen(js_name = mouseButtonPressed)]
pub fn mouse_button_pressed(button: u8) -> Self {
Self(
<<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::mouse_button_pressed(
button,
),
)
}
#[wasm_bindgen(js_name = mouseButtonReleased)]
pub fn mouse_button_released(button: u8) -> Self {
Self(
<<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::mouse_button_released(
button,
),
)
}
#[wasm_bindgen(js_name = mouseMove)]
pub fn mouse_move(x: u16, y: u16) -> Self {
Self(<<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::mouse_move(x, y))
}
#[wasm_bindgen(js_name = wheelRotations)]
pub fn wheel_rotations(vertical: bool, rotation_amount: i16, rotation_unit: $crate::RotationUnit) -> Self {
Self(
<<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::wheel_rotations(
vertical,
rotation_amount,
rotation_unit,
),
)
}
#[wasm_bindgen(js_name = keyPressed)]
pub fn key_pressed(scancode: u16) -> Self {
Self(<<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::key_pressed(scancode))
}
#[wasm_bindgen(js_name = keyReleased)]
pub fn key_released(scancode: u16) -> Self {
Self(<<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::key_released(scancode))
}
#[wasm_bindgen(js_name = unicodePressed)]
pub fn unicode_pressed(unicode: char) -> Self {
Self(<<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::unicode_pressed(unicode))
}
#[wasm_bindgen(js_name = unicodeReleased)]
pub fn unicode_released(unicode: char) -> Self {
Self(
<<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::unicode_released(unicode),
)
}
}
#[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
#[doc(hidden)]
impl InputTransaction {
#[wasm_bindgen(constructor)]
pub fn create() -> Self {
Self(<<$api as $crate::RemoteDesktopApi>::InputTransaction as $crate::InputTransaction>::create())
}
#[wasm_bindgen(js_name = addEvent)]
pub fn add_event(&mut self, event: DeviceEvent) {
$crate::InputTransaction::add_event(&mut self.0, event.0);
}
}
#[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
#[doc(hidden)]
impl ClipboardData {
#[wasm_bindgen(constructor)]
pub fn create() -> Self {
Self(<<$api as $crate::RemoteDesktopApi>::ClipboardData as $crate::ClipboardData>::create())
}
#[wasm_bindgen(js_name = addText)]
pub fn add_text(&mut self, mime_type: &str, text: &str) {
$crate::ClipboardData::add_text(&mut self.0, mime_type, text);
}
#[wasm_bindgen(js_name = addBinary)]
pub fn add_binary(&mut self, mime_type: &str, binary: &[u8]) {
$crate::ClipboardData::add_binary(&mut self.0, mime_type, binary);
}
pub fn items(&self) -> Vec<ClipboardItem> {
$crate::ClipboardData::items(&self.0)
.into_iter()
.cloned()
.map(ClipboardItem)
.collect()
}
#[wasm_bindgen(js_name = isEmpty)]
pub fn is_empty(&self) -> bool {
$crate::ClipboardData::is_empty(&self.0)
}
}
#[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
#[doc(hidden)]
impl ClipboardItem {
#[wasm_bindgen(js_name = mimeType)]
pub fn mime_type(&self) -> String {
$crate::ClipboardItem::mime_type(&self.0).to_owned()
}
pub fn value(&self) -> $crate::internal::wasm_bindgen::JsValue {
$crate::ClipboardItem::value(&self.0).into()
}
}
#[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
#[doc(hidden)]
impl IronError {
pub fn backtrace(&self) -> String {
$crate::IronError::backtrace(&self.0)
}
pub fn kind(&self) -> $crate::IronErrorKind {
$crate::IronError::kind(&self.0)
}
}
};
}
#[doc(hidden)]
pub mod internal {
#[doc(hidden)]
pub use wasm_bindgen;
#[doc(hidden)]
pub use web_sys;
#[doc(hidden)]
pub fn setup(log_level: &str) {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "panic_hook")]
console_error_panic_hook::set_once();
if let Ok(level) = log_level.parse::<tracing::Level>() {
set_logger_once(level);
}
}
fn set_logger_once(level: tracing::Level) {
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::fmt::time::UtcTime;
use tracing_subscriber::prelude::*;
use tracing_web::MakeConsoleWriter;
static INIT: std::sync::Once = std::sync::Once::new();
INIT.call_once(|| {
let fmt_layer = tracing_subscriber::fmt::layer()
.with_ansi(false)
.with_timer(UtcTime::rfc_3339()) // std::time is not available in browsers
.with_writer(MakeConsoleWriter);
let level_filter = LevelFilter::from_level(level);
tracing_subscriber::registry().with(fmt_layer).with(level_filter).init();
})
}
}

View file

@ -0,0 +1,106 @@
use wasm_bindgen::JsValue;
use web_sys::{js_sys, HtmlCanvasElement};
use crate::clipboard::ClipboardData;
use crate::error::IronError;
use crate::input::InputTransaction;
use crate::{DesktopSize, Extension};
pub trait SessionBuilder {
type Session: Session;
type Error: IronError;
fn create() -> Self;
#[must_use]
fn username(&self, username: String) -> Self;
#[must_use]
fn destination(&self, destination: String) -> Self;
#[must_use]
fn server_domain(&self, server_domain: String) -> Self;
#[must_use]
fn password(&self, password: String) -> Self;
#[must_use]
fn proxy_address(&self, address: String) -> Self;
#[must_use]
fn auth_token(&self, token: String) -> Self;
#[must_use]
fn desktop_size(&self, desktop_size: DesktopSize) -> Self;
#[must_use]
fn render_canvas(&self, canvas: HtmlCanvasElement) -> Self;
#[must_use]
fn set_cursor_style_callback(&self, callback: js_sys::Function) -> Self;
#[must_use]
fn set_cursor_style_callback_context(&self, context: JsValue) -> Self;
#[must_use]
fn remote_clipboard_changed_callback(&self, callback: js_sys::Function) -> Self;
#[must_use]
fn force_clipboard_update_callback(&self, callback: js_sys::Function) -> Self;
#[must_use]
fn canvas_resized_callback(&self, callback: js_sys::Function) -> Self;
#[must_use]
fn extension(&self, ext: Extension) -> Self;
#[expect(async_fn_in_trait)]
async fn connect(&self) -> Result<Self::Session, Self::Error>;
}
pub trait Session {
type SessionTerminationInfo: SessionTerminationInfo;
type InputTransaction: InputTransaction;
type ClipboardData: ClipboardData;
type Error: IronError;
fn run(&self) -> impl core::future::Future<Output = Result<Self::SessionTerminationInfo, Self::Error>>;
fn desktop_size(&self) -> DesktopSize;
fn apply_inputs(&self, transaction: Self::InputTransaction) -> Result<(), Self::Error>;
fn release_all_inputs(&self) -> Result<(), Self::Error>;
fn synchronize_lock_keys(
&self,
scroll_lock: bool,
num_lock: bool,
caps_lock: bool,
kana_lock: bool,
) -> Result<(), Self::Error>;
fn shutdown(&self) -> Result<(), Self::Error>;
fn on_clipboard_paste(
&self,
content: &Self::ClipboardData,
) -> impl core::future::Future<Output = Result<(), Self::Error>>;
fn resize(
&self,
width: u32,
height: u32,
scale_factor: Option<u32>,
physical_width: Option<u32>,
physical_height: Option<u32>,
);
fn supports_unicode_keyboard_shortcuts(&self) -> bool;
fn invoke_extension(&self, ext: Extension) -> Result<JsValue, Self::Error>;
}
pub trait SessionTerminationInfo {
fn reason(&self) -> String;
}

View file

@ -0,0 +1,77 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [[0.8.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-acceptor-v0.7.0...ironrdp-acceptor-v0.8.0)] - 2025-12-18
### <!-- 4 -->Bug Fixes
- [**breaking**] Use static dispatch for NetworkClient trait ([#1043](https://github.com/Devolutions/IronRDP/issues/1043)) ([bca6d190a8](https://github.com/Devolutions/IronRDP/commit/bca6d190a870708468534d224ff225a658767a9a))
- Rename `AsyncNetworkClient` to `NetworkClient`
- Replace dynamic dispatch (`Option<&mut dyn ...>`) with static dispatch
using generics (`&mut N where N: NetworkClient`)
- Reorder `connect_finalize` parameters for consistency across crates
## [[0.6.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-acceptor-v0.5.0...ironrdp-acceptor-v0.6.0)] - 2025-07-08
### <!-- 1 -->Features
- [**breaking**] Support for server-side Kerberos (#839) ([33530212c4](https://github.com/Devolutions/IronRDP/commit/33530212c42bf28c875ac078ed2408657831b417))
## [[0.5.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-acceptor-v0.4.0...ironrdp-acceptor-v0.5.0)] - 2025-05-27
### <!-- 1 -->Features
- Make the CredsspSequence type public ([5abd9ff8e0](https://github.com/Devolutions/IronRDP/commit/5abd9ff8e0da8ea48c6747526c4b703a39bf4972))
## [[0.4.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-acceptor-v0.3.1...ironrdp-acceptor-v0.4.0)] - 2025-03-12
### <!-- 7 -->Build
- Bump ironrdp-pdu
## [[0.3.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-acceptor-v0.3.0...ironrdp-acceptor-v0.3.1)] - 2025-03-12
### <!-- 7 -->Build
- Update dependencies (#695) ([c21fa44fd6](https://github.com/Devolutions/IronRDP/commit/c21fa44fd6f3c6a6b74788ff68e83133c1314caa))
## [[0.3.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-acceptor-v0.2.1...ironrdp-acceptor-v0.3.0)] - 2025-01-28
### <!-- 0 -->Security
- Allow using basic RDP/no security ([7c72a9f9bb](https://github.com/Devolutions/IronRDP/commit/7c72a9f9bbe726d6f9f2377c19e9a672d8d086d5))
### <!-- 4 -->Bug Fixes
- Drop unexpected PDUs during deactivation-reactivation ([63963182b5](https://github.com/Devolutions/IronRDP/commit/63963182b5af6ad45dc638e93de4b8a0b565c7d3))
The current behavior of handling unmatched PDUs in fn read_by_hint()
isn't good enough. An unexpected PDUs may be received and fail to be
decoded during Acceptor::step().
Change the code to simply drop unexpected PDUs (as opposed to attempting
to replay the unmatched leftover, which isn't clearly needed)
- Reattach existing channels ([c4587b537c](https://github.com/Devolutions/IronRDP/commit/c4587b537c7c0a148e11bc365bc3df88e2c92312))
I couldn't find any explicit behaviour described in the specification,
but apparently, we must just keep the channel state as they were during
reactivation. This fixes various state issues during client resize.
- Do not restart static channels on reactivation ([82c7c2f5b0](https://github.com/Devolutions/IronRDP/commit/82c7c2f5b08c44b1a4f6b04c13ad24d9e2ffa371))
### <!-- 6 -->Documentation
- Use CDN URLs instead of the blob storage URLs for Devolutions logo (#631) ([dd249909a8](https://github.com/Devolutions/IronRDP/commit/dd249909a894004d4f728d30b3a4aa77a0f8193b))
## [[0.2.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-acceptor-v0.2.0...ironrdp-acceptor-v0.2.1)] - 2024-12-14
### Other
- Symlinks to license files in packages ([#604](https://github.com/Devolutions/IronRDP/pull/604)) ([6c2de344c2](https://github.com/Devolutions/IronRDP/commit/6c2de344c2dd93ce9621834e0497ed7c3bfaf91a))

View file

@ -0,0 +1,28 @@
[package]
name = "ironrdp-acceptor"
version = "0.8.0"
readme = "README.md"
description = "State machines to drive an RDP connection acceptance sequence"
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
authors.workspace = true
keywords.workspace = true
categories.workspace = true
[lib]
doctest = false
test = false
[dependencies]
ironrdp-core = { path = "../ironrdp-core", version = "0.1", features = ["alloc"] } # public
ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.6" } # public
ironrdp-svc = { path = "../ironrdp-svc", version = "0.5" } # public
ironrdp-connector = { path = "../ironrdp-connector", version = "0.8" } # public
ironrdp-async = { path = "../ironrdp-async", version = "0.8" } # public
tracing = { version = "0.1", features = ["log"] }
[lints]
workspace = true

View file

@ -0,0 +1 @@
../../LICENSE-APACHE

View file

@ -0,0 +1 @@
../../LICENSE-MIT

View file

@ -0,0 +1,9 @@
# IronRDP Acceptor
State machines to drive an RDP connection acceptance sequence.
For now, it requires the [Tokio runtime](https://tokio.rs/).
This crate is part of the [IronRDP] project.
[IronRDP]: https://github.com/Devolutions/IronRDP

View file

@ -0,0 +1,198 @@
use std::collections::HashSet;
use ironrdp_connector::{
reason_err, ConnectorError, ConnectorErrorExt as _, ConnectorResult, Sequence, State, Written,
};
use ironrdp_core::WriteBuf;
use ironrdp_pdu::mcs;
use ironrdp_pdu::x224::X224;
use tracing::debug;
#[derive(Debug)]
pub struct ChannelConnectionSequence {
state: ChannelConnectionState,
user_channel_id: u16,
channel_ids: Option<HashSet<u16>>,
}
#[derive(Default, Debug)]
pub enum ChannelConnectionState {
#[default]
Consumed,
WaitErectDomainRequest,
WaitAttachUserRequest,
SendAttachUserConfirm,
WaitChannelJoinRequest {
remaining: HashSet<u16>,
},
SendChannelJoinConfirm {
remaining: HashSet<u16>,
channel_id: u16,
},
AllJoined,
}
impl State for ChannelConnectionState {
fn name(&self) -> &'static str {
match self {
Self::Consumed => "Consumed",
Self::WaitErectDomainRequest => "WaitErectDomainRequest",
Self::WaitAttachUserRequest => "WaitAttachUserRequest",
Self::SendAttachUserConfirm => "SendAttachUserConfirm",
Self::WaitChannelJoinRequest { .. } => "WaitChannelJoinRequest",
Self::SendChannelJoinConfirm { .. } => "SendChannelJoinConfirm",
Self::AllJoined { .. } => "AllJoined",
}
}
fn is_terminal(&self) -> bool {
matches!(self, Self::AllJoined { .. })
}
fn as_any(&self) -> &dyn core::any::Any {
self
}
}
impl Sequence for ChannelConnectionSequence {
fn next_pdu_hint(&self) -> Option<&dyn ironrdp_pdu::PduHint> {
match &self.state {
ChannelConnectionState::Consumed => None,
ChannelConnectionState::WaitErectDomainRequest => Some(&ironrdp_pdu::X224_HINT),
ChannelConnectionState::WaitAttachUserRequest => Some(&ironrdp_pdu::X224_HINT),
ChannelConnectionState::SendAttachUserConfirm => None,
ChannelConnectionState::WaitChannelJoinRequest { .. } => Some(&ironrdp_pdu::X224_HINT),
ChannelConnectionState::SendChannelJoinConfirm { .. } => None,
ChannelConnectionState::AllJoined => None,
}
}
fn state(&self) -> &dyn State {
&self.state
}
fn step(&mut self, input: &[u8], output: &mut WriteBuf) -> ConnectorResult<Written> {
let (written, next_state) = match core::mem::take(&mut self.state) {
ChannelConnectionState::WaitErectDomainRequest => {
let erect_domain_request = ironrdp_core::decode::<X224<mcs::ErectDomainPdu>>(input)
.map_err(ConnectorError::decode)
.map(|p| p.0)?;
debug!(message = ?erect_domain_request, "Received");
(Written::Nothing, ChannelConnectionState::WaitAttachUserRequest)
}
ChannelConnectionState::WaitAttachUserRequest => {
let attach_user_request = ironrdp_core::decode::<X224<mcs::AttachUserRequest>>(input)
.map_err(ConnectorError::decode)
.map(|p| p.0)?;
debug!(message = ?attach_user_request, "Received");
(Written::Nothing, ChannelConnectionState::SendAttachUserConfirm)
}
ChannelConnectionState::SendAttachUserConfirm => {
let attach_user_confirm = mcs::AttachUserConfirm {
result: 0,
initiator_id: self.user_channel_id,
};
debug!(message = ?attach_user_confirm, "Send");
let written =
ironrdp_core::encode_buf(&X224(attach_user_confirm), output).map_err(ConnectorError::encode)?;
let next_state = match self.channel_ids.take() {
Some(channel_ids) => ChannelConnectionState::WaitChannelJoinRequest { remaining: channel_ids },
None => ChannelConnectionState::AllJoined,
};
(Written::from_size(written)?, next_state)
}
ChannelConnectionState::WaitChannelJoinRequest { mut remaining } => {
let channel_request = ironrdp_core::decode::<X224<mcs::ChannelJoinRequest>>(input)
.map_err(ConnectorError::decode)
.map(|p| p.0)?;
debug!(message = ?channel_request, "Received");
let is_expected = remaining.remove(&channel_request.channel_id);
if !is_expected {
return Err(reason_err!(
"ChannelJoinConfirm",
"unexpected channel_id in MCS Channel Join Request: got {}, expected one of: {:?}",
channel_request.channel_id,
remaining,
));
}
(
Written::Nothing,
ChannelConnectionState::SendChannelJoinConfirm {
remaining,
channel_id: channel_request.channel_id,
},
)
}
ChannelConnectionState::SendChannelJoinConfirm { remaining, channel_id } => {
let channel_confirm = mcs::ChannelJoinConfirm {
result: 0,
initiator_id: self.user_channel_id,
requested_channel_id: channel_id,
channel_id,
};
debug!(message = ?channel_confirm, "Send");
let written =
ironrdp_core::encode_buf(&X224(channel_confirm), output).map_err(ConnectorError::encode)?;
let next_state = if remaining.is_empty() {
ChannelConnectionState::AllJoined
} else {
ChannelConnectionState::WaitChannelJoinRequest { remaining }
};
(Written::from_size(written)?, next_state)
}
_ => unreachable!(),
};
self.state = next_state;
Ok(written)
}
}
impl ChannelConnectionSequence {
pub fn new(user_channel_id: u16, io_channel_id: u16, other_channels: Vec<u16>) -> Self {
Self {
state: ChannelConnectionState::WaitErectDomainRequest,
user_channel_id,
channel_ids: Some(
vec![user_channel_id, io_channel_id]
.into_iter()
.chain(other_channels)
.collect(),
),
}
}
pub fn skip_channel_join(user_channel_id: u16) -> Self {
Self {
state: ChannelConnectionState::WaitErectDomainRequest,
user_channel_id,
channel_ids: None,
}
}
pub fn is_done(&self) -> bool {
self.state.is_terminal()
}
}

View file

@ -0,0 +1,760 @@
use core::mem;
use ironrdp_connector::{
encode_x224_packet, general_err, reason_err, ConnectorError, ConnectorErrorExt as _, ConnectorResult, DesktopSize,
Sequence, State, Written,
};
use ironrdp_core::{decode, WriteBuf};
use ironrdp_pdu as pdu;
use ironrdp_pdu::nego::SecurityProtocol;
use ironrdp_pdu::x224::X224;
use ironrdp_svc::{StaticChannelSet, SvcServerProcessor};
use pdu::rdp::capability_sets::CapabilitySet;
use pdu::rdp::client_info::Credentials;
use pdu::rdp::headers::ShareControlPdu;
use pdu::rdp::server_error_info::{ErrorInfo, ProtocolIndependentCode, ServerSetErrorInfoPdu};
use pdu::rdp::server_license::{LicensePdu, LicensingErrorMessage};
use pdu::{gcc, mcs, nego, rdp};
use tracing::{debug, warn};
use super::channel_connection::ChannelConnectionSequence;
use super::finalization::FinalizationSequence;
use crate::util::{self, wrap_share_data};
const IO_CHANNEL_ID: u16 = 1003;
const USER_CHANNEL_ID: u16 = 1002;
pub struct Acceptor {
pub(crate) state: AcceptorState,
security: SecurityProtocol,
io_channel_id: u16,
user_channel_id: u16,
desktop_size: DesktopSize,
server_capabilities: Vec<CapabilitySet>,
static_channels: StaticChannelSet,
saved_for_reactivation: AcceptorState,
pub(crate) creds: Option<Credentials>,
reactivation: bool,
}
#[derive(Debug)]
pub struct AcceptorResult {
pub static_channels: StaticChannelSet,
pub capabilities: Vec<CapabilitySet>,
pub input_events: Vec<Vec<u8>>,
pub user_channel_id: u16,
pub io_channel_id: u16,
pub reactivation: bool,
}
impl Acceptor {
pub fn new(
security: SecurityProtocol,
desktop_size: DesktopSize,
capabilities: Vec<CapabilitySet>,
creds: Option<Credentials>,
) -> Self {
Self {
security,
state: AcceptorState::InitiationWaitRequest,
user_channel_id: USER_CHANNEL_ID,
io_channel_id: IO_CHANNEL_ID,
desktop_size,
server_capabilities: capabilities,
static_channels: StaticChannelSet::new(),
saved_for_reactivation: Default::default(),
creds,
reactivation: false,
}
}
pub fn new_deactivation_reactivation(
mut consumed: Acceptor,
static_channels: StaticChannelSet,
desktop_size: DesktopSize,
) -> ConnectorResult<Self> {
let AcceptorState::CapabilitiesSendServer {
early_capability,
channels,
} = consumed.saved_for_reactivation
else {
return Err(general_err!("invalid acceptor state"));
};
for cap in consumed.server_capabilities.iter_mut() {
if let CapabilitySet::Bitmap(cap) = cap {
cap.desktop_width = desktop_size.width;
cap.desktop_height = desktop_size.height;
}
}
let state = AcceptorState::CapabilitiesSendServer {
early_capability,
channels: channels.clone(),
};
let saved_for_reactivation = AcceptorState::CapabilitiesSendServer {
early_capability,
channels,
};
Ok(Self {
security: consumed.security,
state,
user_channel_id: consumed.user_channel_id,
io_channel_id: consumed.io_channel_id,
desktop_size,
server_capabilities: consumed.server_capabilities,
static_channels,
saved_for_reactivation,
creds: consumed.creds,
reactivation: true,
})
}
pub fn attach_static_channel<T>(&mut self, channel: T)
where
T: SvcServerProcessor + 'static,
{
self.static_channels.insert(channel);
}
pub fn reached_security_upgrade(&self) -> Option<SecurityProtocol> {
match self.state {
AcceptorState::SecurityUpgrade { .. } => Some(self.security),
_ => None,
}
}
/// # Panics
///
/// Panics if state is not [AcceptorState::SecurityUpgrade].
pub fn mark_security_upgrade_as_done(&mut self) {
assert!(self.reached_security_upgrade().is_some());
self.step(&[], &mut WriteBuf::new()).expect("transition to next state");
debug_assert!(self.reached_security_upgrade().is_none());
}
pub fn should_perform_credssp(&self) -> bool {
matches!(self.state, AcceptorState::Credssp { .. })
}
/// # Panics
///
/// Panics if state is not [AcceptorState::Credssp].
pub fn mark_credssp_as_done(&mut self) {
assert!(self.should_perform_credssp());
let res = self.step(&[], &mut WriteBuf::new()).expect("transition to next state");
debug_assert!(!self.should_perform_credssp());
assert_eq!(res, Written::Nothing);
}
pub fn get_result(&mut self) -> Option<AcceptorResult> {
match mem::take(&mut self.state) {
AcceptorState::Accepted {
channels: _channels, // TODO: what about ChannelDef?
client_capabilities,
input_events,
} => Some(AcceptorResult {
static_channels: mem::take(&mut self.static_channels),
capabilities: client_capabilities,
input_events,
user_channel_id: self.user_channel_id,
io_channel_id: self.io_channel_id,
reactivation: self.reactivation,
}),
previous_state => {
self.state = previous_state;
None
}
}
}
}
#[derive(Default, Debug)]
pub enum AcceptorState {
#[default]
Consumed,
InitiationWaitRequest,
InitiationSendConfirm {
requested_protocol: SecurityProtocol,
},
SecurityUpgrade {
requested_protocol: SecurityProtocol,
protocol: SecurityProtocol,
},
Credssp {
requested_protocol: SecurityProtocol,
protocol: SecurityProtocol,
},
BasicSettingsWaitInitial {
requested_protocol: SecurityProtocol,
protocol: SecurityProtocol,
},
BasicSettingsSendResponse {
requested_protocol: SecurityProtocol,
protocol: SecurityProtocol,
early_capability: Option<gcc::ClientEarlyCapabilityFlags>,
channels: Vec<(u16, Option<gcc::ChannelDef>)>,
},
ChannelConnection {
protocol: SecurityProtocol,
early_capability: Option<gcc::ClientEarlyCapabilityFlags>,
channels: Vec<(u16, gcc::ChannelDef)>,
connection: ChannelConnectionSequence,
},
RdpSecurityCommencement {
protocol: SecurityProtocol,
early_capability: Option<gcc::ClientEarlyCapabilityFlags>,
channels: Vec<(u16, gcc::ChannelDef)>,
},
SecureSettingsExchange {
protocol: SecurityProtocol,
early_capability: Option<gcc::ClientEarlyCapabilityFlags>,
channels: Vec<(u16, gcc::ChannelDef)>,
},
LicensingExchange {
early_capability: Option<gcc::ClientEarlyCapabilityFlags>,
channels: Vec<(u16, gcc::ChannelDef)>,
},
CapabilitiesSendServer {
early_capability: Option<gcc::ClientEarlyCapabilityFlags>,
channels: Vec<(u16, gcc::ChannelDef)>,
},
MonitorLayoutSend {
channels: Vec<(u16, gcc::ChannelDef)>,
},
CapabilitiesWaitConfirm {
channels: Vec<(u16, gcc::ChannelDef)>,
},
ConnectionFinalization {
finalization: FinalizationSequence,
channels: Vec<(u16, gcc::ChannelDef)>,
client_capabilities: Vec<CapabilitySet>,
},
Accepted {
channels: Vec<(u16, gcc::ChannelDef)>,
client_capabilities: Vec<CapabilitySet>,
input_events: Vec<Vec<u8>>,
},
}
impl State for AcceptorState {
fn name(&self) -> &'static str {
match self {
Self::Consumed => "Consumed",
Self::InitiationWaitRequest => "InitiationWaitRequest",
Self::InitiationSendConfirm { .. } => "InitiationSendConfirm",
Self::SecurityUpgrade { .. } => "SecurityUpgrade",
Self::Credssp { .. } => "Credssp",
Self::BasicSettingsWaitInitial { .. } => "BasicSettingsWaitInitial",
Self::BasicSettingsSendResponse { .. } => "BasicSettingsSendResponse",
Self::ChannelConnection { .. } => "ChannelConnection",
Self::RdpSecurityCommencement { .. } => "RdpSecurityCommencement",
Self::SecureSettingsExchange { .. } => "SecureSettingsExchange",
Self::LicensingExchange { .. } => "LicensingExchange",
Self::CapabilitiesSendServer { .. } => "CapabilitiesSendServer",
Self::MonitorLayoutSend { .. } => "MonitorLayoutSend",
Self::CapabilitiesWaitConfirm { .. } => "CapabilitiesWaitConfirm",
Self::ConnectionFinalization { .. } => "ConnectionFinalization",
Self::Accepted { .. } => "Connected",
}
}
fn is_terminal(&self) -> bool {
matches!(self, Self::Accepted { .. })
}
fn as_any(&self) -> &dyn core::any::Any {
self
}
}
impl Sequence for Acceptor {
fn next_pdu_hint(&self) -> Option<&dyn pdu::PduHint> {
match &self.state {
AcceptorState::Consumed => None,
AcceptorState::InitiationWaitRequest => Some(&pdu::X224_HINT),
AcceptorState::InitiationSendConfirm { .. } => None,
AcceptorState::SecurityUpgrade { .. } => None,
AcceptorState::Credssp { .. } => None,
AcceptorState::BasicSettingsWaitInitial { .. } => Some(&pdu::X224_HINT),
AcceptorState::BasicSettingsSendResponse { .. } => None,
AcceptorState::ChannelConnection { connection, .. } => connection.next_pdu_hint(),
AcceptorState::RdpSecurityCommencement { .. } => None,
AcceptorState::SecureSettingsExchange { .. } => Some(&pdu::X224_HINT),
AcceptorState::LicensingExchange { .. } => None,
AcceptorState::CapabilitiesSendServer { .. } => None,
AcceptorState::MonitorLayoutSend { .. } => None,
AcceptorState::CapabilitiesWaitConfirm { .. } => Some(&pdu::X224_HINT),
AcceptorState::ConnectionFinalization { finalization, .. } => finalization.next_pdu_hint(),
AcceptorState::Accepted { .. } => None,
}
}
fn state(&self) -> &dyn State {
&self.state
}
fn step(&mut self, input: &[u8], output: &mut WriteBuf) -> ConnectorResult<Written> {
let prev_state = mem::take(&mut self.state);
let (written, next_state) = match prev_state {
AcceptorState::InitiationWaitRequest => {
let connection_request = decode::<X224<nego::ConnectionRequest>>(input)
.map_err(ConnectorError::decode)
.map(|p| p.0)?;
debug!(message = ?connection_request, "Received");
(
Written::Nothing,
AcceptorState::InitiationSendConfirm {
requested_protocol: connection_request.protocol,
},
)
}
AcceptorState::InitiationSendConfirm { requested_protocol } => {
let protocols = requested_protocol & self.security;
let protocol = if protocols.intersects(SecurityProtocol::HYBRID_EX) {
SecurityProtocol::HYBRID_EX
} else if protocols.intersects(SecurityProtocol::HYBRID) {
SecurityProtocol::HYBRID
} else if protocols.intersects(SecurityProtocol::SSL) {
SecurityProtocol::SSL
} else if self.security.is_empty() {
SecurityProtocol::empty()
} else {
return Err(ConnectorError::general("failed to negotiate security protocol"));
};
let connection_confirm = nego::ConnectionConfirm::Response {
flags: nego::ResponseFlags::empty(),
protocol,
};
debug!(message = ?connection_confirm, "Send");
let written =
ironrdp_core::encode_buf(&X224(connection_confirm), output).map_err(ConnectorError::encode)?;
(
Written::from_size(written)?,
AcceptorState::SecurityUpgrade {
requested_protocol,
protocol,
},
)
}
AcceptorState::SecurityUpgrade {
requested_protocol,
protocol,
} => {
debug!(?requested_protocol);
let next_state = if protocol.intersects(SecurityProtocol::HYBRID | SecurityProtocol::HYBRID_EX) {
AcceptorState::Credssp {
requested_protocol,
protocol,
}
} else {
AcceptorState::BasicSettingsWaitInitial {
requested_protocol,
protocol,
}
};
(Written::Nothing, next_state)
}
AcceptorState::Credssp {
requested_protocol,
protocol,
} => (
Written::Nothing,
AcceptorState::BasicSettingsWaitInitial {
requested_protocol,
protocol,
},
),
AcceptorState::BasicSettingsWaitInitial {
requested_protocol,
protocol,
} => {
let x224_payload = decode::<X224<pdu::x224::X224Data<'_>>>(input)
.map_err(ConnectorError::decode)
.map(|p| p.0)?;
let settings_initial =
decode::<mcs::ConnectInitial>(x224_payload.data.as_ref()).map_err(ConnectorError::decode)?;
debug!(message = ?settings_initial, "Received");
let gcc_blocks = settings_initial.conference_create_request.into_gcc_blocks();
let early_capability = gcc_blocks.core.optional_data.early_capability_flags;
let joined: Vec<_> = gcc_blocks
.network
.map(|network| {
network
.channels
.into_iter()
.map(|c| {
self.static_channels
.get_by_channel_name(&c.name)
.map(|(type_id, _)| (type_id, c))
})
.collect()
})
.unwrap_or_default();
#[expect(clippy::arithmetic_side_effects)] // IO channel ID is not big enough for overflowing.
let channels = joined
.into_iter()
.enumerate()
.map(|(i, channel)| {
let channel_id = u16::try_from(i).expect("always in the range") + self.io_channel_id + 1;
if let Some((type_id, c)) = channel {
self.static_channels.attach_channel_id(type_id, channel_id);
(channel_id, Some(c))
} else {
(channel_id, None)
}
})
.collect();
(
Written::Nothing,
AcceptorState::BasicSettingsSendResponse {
requested_protocol,
protocol,
early_capability,
channels,
},
)
}
AcceptorState::BasicSettingsSendResponse {
requested_protocol,
protocol,
early_capability,
channels,
} => {
let channel_ids: Vec<u16> = channels.iter().map(|&(i, _)| i).collect();
let skip_channel_join = early_capability
.is_some_and(|client| client.contains(gcc::ClientEarlyCapabilityFlags::SUPPORT_SKIP_CHANNELJOIN));
let server_blocks = create_gcc_blocks(
self.io_channel_id,
channel_ids.clone(),
requested_protocol,
skip_channel_join,
);
let settings_response = mcs::ConnectResponse {
conference_create_response: gcc::ConferenceCreateResponse::new(self.user_channel_id, server_blocks)
.map_err(ConnectorError::decode)?,
called_connect_id: 1,
domain_parameters: mcs::DomainParameters::target(),
};
debug!(message = ?settings_response, "Send");
let written = encode_x224_packet(&settings_response, output)?;
let channels = channels.into_iter().filter_map(|(i, c)| c.map(|c| (i, c))).collect();
(
Written::from_size(written)?,
AcceptorState::ChannelConnection {
protocol,
early_capability,
channels,
connection: if skip_channel_join {
ChannelConnectionSequence::skip_channel_join(self.user_channel_id)
} else {
ChannelConnectionSequence::new(self.user_channel_id, self.io_channel_id, channel_ids)
},
},
)
}
AcceptorState::ChannelConnection {
protocol,
early_capability,
channels,
mut connection,
} => {
let written = connection.step(input, output)?;
let state = if connection.is_done() {
AcceptorState::RdpSecurityCommencement {
protocol,
early_capability,
channels,
}
} else {
AcceptorState::ChannelConnection {
protocol,
early_capability,
channels,
connection,
}
};
(written, state)
}
AcceptorState::RdpSecurityCommencement {
protocol,
early_capability,
channels,
..
} => (
Written::Nothing,
AcceptorState::SecureSettingsExchange {
protocol,
early_capability,
channels,
},
),
AcceptorState::SecureSettingsExchange {
protocol,
early_capability,
channels,
} => {
let data: X224<mcs::SendDataRequest<'_>> = decode(input).map_err(ConnectorError::decode)?;
let data = data.0;
let client_info: rdp::ClientInfoPdu =
decode(data.user_data.as_ref()).map_err(ConnectorError::decode)?;
debug!(message = ?client_info, "Received");
if !protocol.intersects(SecurityProtocol::HYBRID | SecurityProtocol::HYBRID_EX) {
let creds = client_info.client_info.credentials;
if self.creds.as_ref() != Some(&creds) {
// FIXME: How authorization should be denied with standard RDP security?
// Since standard RDP security is not a priority, we just send a ServerDeniedConnection ServerSetErrorInfo PDU.
let info = ServerSetErrorInfoPdu(ErrorInfo::ProtocolIndependentCode(
ProtocolIndependentCode::ServerDeniedConnection,
));
debug!(message = ?info, "Send");
util::encode_send_data_indication(self.user_channel_id, self.io_channel_id, &info, output)?;
return Err(ConnectorError::general("invalid credentials"));
}
}
(
Written::Nothing,
AcceptorState::LicensingExchange {
early_capability,
channels,
},
)
}
AcceptorState::LicensingExchange {
early_capability,
channels,
} => {
let license: LicensePdu = LicensingErrorMessage::new_valid_client()
.map_err(ConnectorError::encode)?
.into();
debug!(message = ?license, "Send");
let written =
util::encode_send_data_indication(self.user_channel_id, self.io_channel_id, &license, output)?;
self.saved_for_reactivation = AcceptorState::CapabilitiesSendServer {
early_capability,
channels: channels.clone(),
};
(
Written::from_size(written)?,
AcceptorState::CapabilitiesSendServer {
early_capability,
channels,
},
)
}
AcceptorState::CapabilitiesSendServer {
early_capability,
channels,
} => {
let demand_active = rdp::headers::ShareControlHeader {
share_id: 0,
pdu_source: self.io_channel_id,
share_control_pdu: ShareControlPdu::ServerDemandActive(rdp::capability_sets::ServerDemandActive {
pdu: rdp::capability_sets::DemandActive {
source_descriptor: "".into(),
capability_sets: self.server_capabilities.clone(),
},
}),
};
debug!(message = ?demand_active, "Send");
let written = util::encode_send_data_indication(
self.user_channel_id,
self.io_channel_id,
&demand_active,
output,
)?;
let layout_flag = gcc::ClientEarlyCapabilityFlags::SUPPORT_MONITOR_LAYOUT_PDU;
let next_state = if early_capability.is_some_and(|c| c.contains(layout_flag)) {
AcceptorState::MonitorLayoutSend { channels }
} else {
AcceptorState::CapabilitiesWaitConfirm { channels }
};
(Written::from_size(written)?, next_state)
}
AcceptorState::MonitorLayoutSend { channels } => {
let monitor_layout =
rdp::headers::ShareDataPdu::MonitorLayout(rdp::finalization_messages::MonitorLayoutPdu {
monitors: vec![gcc::Monitor {
left: 0,
top: 0,
right: i32::from(self.desktop_size.width),
bottom: i32::from(self.desktop_size.height),
flags: gcc::MonitorFlags::PRIMARY,
}],
});
debug!(message = ?monitor_layout, "Send");
let share_data = wrap_share_data(monitor_layout, self.io_channel_id);
let written =
util::encode_send_data_indication(self.user_channel_id, self.io_channel_id, &share_data, output)?;
(
Written::from_size(written)?,
AcceptorState::CapabilitiesWaitConfirm { channels },
)
}
AcceptorState::CapabilitiesWaitConfirm { ref channels } => {
let message = decode::<X224<mcs::McsMessage<'_>>>(input)
.map_err(ConnectorError::decode)
.map(|p| p.0);
let message = match message {
Ok(msg) => msg,
Err(e) => {
if self.reactivation {
debug!("Dropping unexpected PDU during reactivation");
self.state = prev_state;
return Ok(Written::Nothing);
} else {
return Err(e);
}
}
};
match message {
mcs::McsMessage::SendDataRequest(data) => {
let capabilities_confirm = decode::<rdp::headers::ShareControlHeader>(data.user_data.as_ref())
.map_err(ConnectorError::decode);
let capabilities_confirm = match capabilities_confirm {
Ok(capabilities_confirm) => capabilities_confirm,
Err(e) => {
if self.reactivation {
debug!("Dropping unexpected PDU during reactivation");
self.state = prev_state;
return Ok(Written::Nothing);
} else {
return Err(e);
}
}
};
debug!(message = ?capabilities_confirm, "Received");
let ShareControlPdu::ClientConfirmActive(confirm) = capabilities_confirm.share_control_pdu
else {
return Err(ConnectorError::general("expected client confirm active"));
};
(
Written::Nothing,
AcceptorState::ConnectionFinalization {
channels: channels.clone(),
finalization: FinalizationSequence::new(self.user_channel_id, self.io_channel_id),
client_capabilities: confirm.pdu.capability_sets,
},
)
}
mcs::McsMessage::DisconnectProviderUltimatum(ultimatum) => {
return Err(reason_err!("received disconnect ultimatum", "{:?}", ultimatum.reason))
}
_ => {
warn!(?message, "Unexpected MCS message received");
(Written::Nothing, prev_state)
}
}
}
AcceptorState::ConnectionFinalization {
mut finalization,
channels,
client_capabilities,
} => {
let written = finalization.step(input, output)?;
let state = if finalization.is_done() {
AcceptorState::Accepted {
channels,
client_capabilities,
input_events: finalization.into_input_events(),
}
} else {
AcceptorState::ConnectionFinalization {
finalization,
channels,
client_capabilities,
}
};
(written, state)
}
_ => unreachable!(),
};
self.state = next_state;
Ok(written)
}
}
fn create_gcc_blocks(
io_channel: u16,
channel_ids: Vec<u16>,
requested: SecurityProtocol,
skip_channel_join: bool,
) -> gcc::ServerGccBlocks {
gcc::ServerGccBlocks {
core: gcc::ServerCoreData {
version: gcc::RdpVersion::V5_PLUS,
optional_data: gcc::ServerCoreOptionalData {
client_requested_protocols: Some(requested),
early_capability_flags: skip_channel_join
.then_some(gcc::ServerEarlyCapabilityFlags::SKIP_CHANNELJOIN_SUPPORTED),
},
},
security: gcc::ServerSecurityData::no_security(),
network: gcc::ServerNetworkData {
channel_ids,
io_channel,
},
message_channel: None,
multi_transport_channel: None,
}
}

View file

@ -0,0 +1,184 @@
use ironrdp_async::NetworkClient;
use ironrdp_connector::sspi::credssp::{
CredSspServer, CredentialsProxy, ServerError, ServerMode, ServerState, TsRequest,
};
use ironrdp_connector::sspi::generator::{Generator, GeneratorState};
use ironrdp_connector::sspi::negotiate::ProtocolConfig;
use ironrdp_connector::sspi::{self, AuthIdentity, KerberosServerConfig, NegotiateConfig, NetworkRequest, Username};
use ironrdp_connector::{
custom_err, general_err, ConnectorError, ConnectorErrorKind, ConnectorResult, ServerName, Written,
};
use ironrdp_core::{other_err, WriteBuf};
use ironrdp_pdu::PduHint;
use tracing::debug;
#[derive(Debug)]
pub(crate) enum CredsspState {
Ongoing,
Finished,
ServerError(sspi::Error),
}
#[derive(Clone, Copy, Debug)]
struct CredsspTsRequestHint;
const CREDSSP_TS_REQUEST_HINT: CredsspTsRequestHint = CredsspTsRequestHint;
impl PduHint for CredsspTsRequestHint {
fn find_size(&self, bytes: &[u8]) -> ironrdp_core::DecodeResult<Option<(bool, usize)>> {
match TsRequest::read_length(bytes) {
Ok(length) => Ok(Some((true, length))),
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => Ok(None),
Err(e) => Err(other_err!("CredsspTsRequestHint", source: e)),
}
}
}
pub type CredsspProcessGenerator<'a> =
Generator<'a, NetworkRequest, sspi::Result<Vec<u8>>, Result<ServerState, ServerError>>;
#[derive(Debug)]
pub struct CredsspSequence<'a> {
server: CredSspServer<CredentialsProxyImpl<'a>>,
state: CredsspState,
}
#[derive(Debug)]
struct CredentialsProxyImpl<'a> {
credentials: &'a AuthIdentity,
}
impl<'a> CredentialsProxyImpl<'a> {
fn new(credentials: &'a AuthIdentity) -> Self {
Self { credentials }
}
}
impl CredentialsProxy for CredentialsProxyImpl<'_> {
type AuthenticationData = AuthIdentity;
fn auth_data_by_user(&mut self, username: &Username) -> std::io::Result<Self::AuthenticationData> {
if username.account_name() != self.credentials.username.account_name() {
return Err(std::io::Error::other("invalid username"));
}
let mut data = self.credentials.clone();
// keep the original user/domain
data.username = username.clone();
Ok(data)
}
}
pub(crate) async fn resolve_generator(
generator: &mut CredsspProcessGenerator<'_>,
network_client: &mut impl NetworkClient,
) -> Result<ServerState, ServerError> {
let mut state = generator.start();
loop {
match state {
GeneratorState::Suspended(request) => {
let response = network_client.send(&request).await.map_err(|err| ServerError {
ts_request: None,
error: sspi::Error::new(sspi::ErrorKind::InternalError, err),
})?;
state = generator.resume(Ok(response));
}
GeneratorState::Completed(client_state) => break client_state,
}
}
}
impl<'a> CredsspSequence<'a> {
pub fn next_pdu_hint(&self) -> ConnectorResult<Option<&dyn PduHint>> {
match &self.state {
CredsspState::Ongoing => Ok(Some(&CREDSSP_TS_REQUEST_HINT)),
CredsspState::Finished => Ok(None),
CredsspState::ServerError(err) => Err(custom_err!("Credssp server error", err.clone())),
}
}
pub fn init(
creds: &'a AuthIdentity,
client_computer_name: ServerName,
public_key: Vec<u8>,
krb_config: Option<KerberosServerConfig>,
) -> ConnectorResult<Self> {
let client_computer_name = client_computer_name.into_inner();
let credentials = CredentialsProxyImpl::new(creds);
let credssp_config: Box<dyn ProtocolConfig> = if let Some(krb_config) = krb_config {
Box::new(krb_config)
} else {
Box::<sspi::ntlm::NtlmConfig>::default()
};
let server = CredSspServer::new(
public_key,
credentials,
ServerMode::Negotiate(NegotiateConfig {
protocol_config: credssp_config,
package_list: None,
client_computer_name,
}),
)
.map_err(|e| ConnectorError::new("CredSSP", ConnectorErrorKind::Credssp(e)))?;
let sequence = Self {
server,
state: CredsspState::Ongoing,
};
Ok(sequence)
}
/// Returns Some(ts_request) when a TS request is received from client,
pub fn decode_client_message(&mut self, input: &[u8]) -> ConnectorResult<Option<TsRequest>> {
match self.state {
CredsspState::Ongoing => {
let message = TsRequest::from_buffer(input).map_err(|e| custom_err!("TsRequest", e))?;
debug!(?message, "Received");
Ok(Some(message))
}
_ => Err(general_err!(
"attempted to feed client request to CredSSP sequence in an unexpected state"
)),
}
}
pub fn process_ts_request(&mut self, request: TsRequest) -> CredsspProcessGenerator<'_> {
self.server.process(request)
}
pub fn handle_process_result(
&mut self,
result: Result<ServerState, ServerError>,
output: &mut WriteBuf,
) -> ConnectorResult<Written> {
let (ts_request, next_state) = match result {
Ok(ServerState::ReplyNeeded(ts_request)) => (Some(ts_request), CredsspState::Ongoing),
Ok(ServerState::Finished(_id)) => (None, CredsspState::Finished),
Err(err) => (
err.ts_request.map(|ts_request| *ts_request),
CredsspState::ServerError(err.error),
),
};
self.state = next_state;
if let Some(ts_request) = ts_request {
debug!(?ts_request, "Send");
let length = usize::from(ts_request.buffer_len());
let unfilled_buffer = output.unfilled_to(length);
ts_request
.encode_ts_request(unfilled_buffer)
.map_err(|e| custom_err!("TsRequest", e))?;
output.advance(length);
Ok(Written::from_size(length)?)
} else {
Ok(Written::Nothing)
}
}
}

View file

@ -0,0 +1,249 @@
use ironrdp_connector::{ConnectorError, ConnectorErrorExt as _, ConnectorResult, Sequence, State, Written};
use ironrdp_core::WriteBuf;
use ironrdp_pdu::rdp;
use ironrdp_pdu::x224::X224;
use tracing::debug;
use crate::util::{self, wrap_share_data};
#[derive(Debug)]
pub struct FinalizationSequence {
state: FinalizationState,
user_channel_id: u16,
io_channel_id: u16,
input_events: Vec<Vec<u8>>,
}
#[derive(Default, Debug)]
pub enum FinalizationState {
#[default]
Consumed,
WaitSynchronize,
WaitControlCooperate,
WaitRequestControl,
WaitFontList,
SendSynchronizeConfirm,
SendControlCooperateConfirm,
SendGrantedControlConfirm,
SendFontMap,
Finished,
}
impl State for FinalizationState {
fn name(&self) -> &'static str {
match self {
Self::Consumed => "Consumed",
Self::WaitSynchronize => "WaitSynchronize",
Self::WaitControlCooperate => "WaitControlCooperate",
Self::WaitRequestControl => "WaitRequestControl",
Self::WaitFontList => "WaitFontList",
Self::SendSynchronizeConfirm => "SendSynchronizeConfirm",
Self::SendControlCooperateConfirm => "SendControlCooperateConfirm",
Self::SendGrantedControlConfirm => "SendGrantedControlConfirm",
Self::SendFontMap => "SendFontMap",
Self::Finished => "Finished",
}
}
fn is_terminal(&self) -> bool {
matches!(self, Self::Finished { .. })
}
fn as_any(&self) -> &dyn core::any::Any {
self
}
}
impl Sequence for FinalizationSequence {
fn next_pdu_hint(&self) -> Option<&dyn ironrdp_pdu::PduHint> {
match &self.state {
FinalizationState::Consumed => None,
FinalizationState::WaitSynchronize => Some(&ironrdp_pdu::X224Hint),
FinalizationState::WaitControlCooperate => Some(&ironrdp_pdu::X224Hint),
FinalizationState::WaitRequestControl => Some(&ironrdp_pdu::X224Hint),
FinalizationState::WaitFontList => Some(&ironrdp_pdu::RdpHint),
FinalizationState::SendSynchronizeConfirm => None,
FinalizationState::SendControlCooperateConfirm => None,
FinalizationState::SendGrantedControlConfirm => None,
FinalizationState::SendFontMap => None,
FinalizationState::Finished => None,
}
}
fn state(&self) -> &dyn State {
&self.state
}
fn step(&mut self, input: &[u8], output: &mut WriteBuf) -> ConnectorResult<Written> {
let (written, next_state) = match core::mem::take(&mut self.state) {
FinalizationState::WaitSynchronize => {
let synchronize = decode_share_control(input);
debug!(message = ?synchronize, "Received");
(Written::Nothing, FinalizationState::WaitControlCooperate)
}
FinalizationState::WaitControlCooperate => {
let cooperate = decode_share_control(input);
debug!(message = ?cooperate, "Received");
(Written::Nothing, FinalizationState::WaitRequestControl)
}
FinalizationState::WaitRequestControl => {
let control = decode_share_control(input)?;
debug!(message = ?control, "Received");
(Written::Nothing, FinalizationState::WaitFontList)
}
FinalizationState::WaitFontList => match decode_font_list(input) {
Ok(font_list) => {
debug!(message = ?font_list, "Received");
(Written::Nothing, FinalizationState::SendSynchronizeConfirm)
}
Err(()) => {
self.input_events.push(input.to_vec());
(Written::Nothing, FinalizationState::WaitFontList)
}
},
FinalizationState::SendSynchronizeConfirm => {
let synchronize_confirm = create_synchronize_confirm();
debug!(message = ?synchronize_confirm, "Send");
let share_data = wrap_share_data(synchronize_confirm, self.io_channel_id);
let written =
util::encode_send_data_indication(self.user_channel_id, self.io_channel_id, &share_data, output)?;
(
Written::from_size(written)?,
FinalizationState::SendControlCooperateConfirm,
)
}
FinalizationState::SendControlCooperateConfirm => {
let cooperate_confirm = create_cooperate_confirm();
debug!(message = ?cooperate_confirm, "Send");
let share_data = wrap_share_data(cooperate_confirm, self.io_channel_id);
let written =
util::encode_send_data_indication(self.user_channel_id, self.io_channel_id, &share_data, output)?;
(
Written::from_size(written)?,
FinalizationState::SendGrantedControlConfirm,
)
}
FinalizationState::SendGrantedControlConfirm => {
let control_confirm = create_control_confirm(self.user_channel_id);
debug!(message = ?control_confirm, "Send");
let share_data = wrap_share_data(control_confirm, self.io_channel_id);
let written =
util::encode_send_data_indication(self.user_channel_id, self.io_channel_id, &share_data, output)?;
(Written::from_size(written)?, FinalizationState::SendFontMap)
}
FinalizationState::SendFontMap => {
let font_map = create_font_map();
debug!(message = ?font_map, "Send");
let share_data = wrap_share_data(font_map, self.io_channel_id);
let written =
util::encode_send_data_indication(self.user_channel_id, self.io_channel_id, &share_data, output)?;
(Written::from_size(written)?, FinalizationState::Finished)
}
_ => unreachable!(),
};
self.state = next_state;
Ok(written)
}
}
impl FinalizationSequence {
pub fn new(user_channel_id: u16, io_channel_id: u16) -> Self {
Self {
state: FinalizationState::WaitSynchronize,
user_channel_id,
io_channel_id,
input_events: Vec::new(),
}
}
pub fn into_input_events(self) -> Vec<Vec<u8>> {
self.input_events
}
pub fn is_done(&self) -> bool {
self.state.is_terminal()
}
}
fn create_synchronize_confirm() -> rdp::headers::ShareDataPdu {
rdp::headers::ShareDataPdu::Synchronize(rdp::finalization_messages::SynchronizePdu { target_user_id: 0 })
}
fn create_cooperate_confirm() -> rdp::headers::ShareDataPdu {
rdp::headers::ShareDataPdu::Control(rdp::finalization_messages::ControlPdu {
action: rdp::finalization_messages::ControlAction::Cooperate,
grant_id: 0,
control_id: 0,
})
}
fn create_control_confirm(user_id: u16) -> rdp::headers::ShareDataPdu {
rdp::headers::ShareDataPdu::Control(rdp::finalization_messages::ControlPdu {
action: rdp::finalization_messages::ControlAction::GrantedControl,
grant_id: user_id,
control_id: u32::from(rdp::capability_sets::SERVER_CHANNEL_ID),
})
}
fn create_font_map() -> rdp::headers::ShareDataPdu {
rdp::headers::ShareDataPdu::FontMap(rdp::finalization_messages::FontPdu::default())
}
fn decode_share_control(input: &[u8]) -> ConnectorResult<rdp::headers::ShareControlHeader> {
let data_request = ironrdp_core::decode::<X224<ironrdp_pdu::mcs::SendDataRequest<'_>>>(input)
.map_err(ConnectorError::decode)
.map(|p| p.0)?;
let share_control = ironrdp_core::decode::<rdp::headers::ShareControlHeader>(data_request.user_data.as_ref())
.map_err(ConnectorError::decode)?;
Ok(share_control)
}
fn decode_font_list(input: &[u8]) -> Result<rdp::finalization_messages::FontPdu, ()> {
use ironrdp_pdu::rdp::headers::{ShareControlPdu, ShareDataPdu};
let share_control = decode_share_control(input).map_err(|_| ())?;
let ShareControlPdu::Data(data_pdu) = share_control.share_control_pdu else {
return Err(());
};
let ShareDataPdu::FontList(font_pdu) = data_pdu.share_data_pdu else {
return Err(());
};
Ok(font_pdu)
}

View file

@ -0,0 +1,225 @@
#![cfg_attr(doc, doc = include_str!("../README.md"))]
#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
use ironrdp_async::{single_sequence_step, Framed, FramedRead, FramedWrite, NetworkClient, StreamWrapper};
use ironrdp_connector::sspi::credssp::EarlyUserAuthResult;
use ironrdp_connector::sspi::{AuthIdentity, KerberosServerConfig, Username};
use ironrdp_connector::{custom_err, general_err, ConnectorResult, ServerName};
use ironrdp_core::WriteBuf;
use tracing::{debug, instrument, trace};
mod channel_connection;
mod connection;
pub mod credssp;
mod finalization;
mod util;
pub use ironrdp_connector::DesktopSize;
use ironrdp_pdu::nego;
pub use self::channel_connection::{ChannelConnectionSequence, ChannelConnectionState};
pub use self::connection::{Acceptor, AcceptorResult, AcceptorState};
pub use self::finalization::{FinalizationSequence, FinalizationState};
use crate::credssp::resolve_generator;
pub enum BeginResult<S>
where
S: StreamWrapper,
{
ShouldUpgrade(S::InnerStream),
Continue(Framed<S>),
}
pub async fn accept_begin<S>(mut framed: Framed<S>, acceptor: &mut Acceptor) -> ConnectorResult<BeginResult<S>>
where
S: FramedRead + FramedWrite + StreamWrapper,
{
let mut buf = WriteBuf::new();
loop {
if let Some(security) = acceptor.reached_security_upgrade() {
let result = if security.is_empty() {
BeginResult::Continue(framed)
} else {
BeginResult::ShouldUpgrade(framed.into_inner_no_leftover())
};
return Ok(result);
}
single_sequence_step(&mut framed, acceptor, &mut buf).await?;
}
}
pub async fn accept_credssp<S, N>(
framed: &mut Framed<S>,
acceptor: &mut Acceptor,
network_client: &mut N,
client_computer_name: ServerName,
public_key: Vec<u8>,
kerberos_config: Option<KerberosServerConfig>,
) -> ConnectorResult<()>
where
S: FramedRead + FramedWrite,
N: NetworkClient,
{
let mut buf = WriteBuf::new();
if acceptor.should_perform_credssp() {
perform_credssp_step(
framed,
acceptor,
network_client,
&mut buf,
client_computer_name,
public_key,
kerberos_config,
)
.await
} else {
Ok(())
}
}
pub async fn accept_finalize<S>(
mut framed: Framed<S>,
acceptor: &mut Acceptor,
) -> ConnectorResult<(Framed<S>, AcceptorResult)>
where
S: FramedRead + FramedWrite,
{
let mut buf = WriteBuf::new();
loop {
if let Some(result) = acceptor.get_result() {
return Ok((framed, result));
}
single_sequence_step(&mut framed, acceptor, &mut buf).await?;
}
}
#[instrument(level = "trace", skip_all, ret)]
async fn perform_credssp_step<S, N>(
framed: &mut Framed<S>,
acceptor: &mut Acceptor,
network_client: &mut N,
buf: &mut WriteBuf,
client_computer_name: ServerName,
public_key: Vec<u8>,
kerberos_config: Option<KerberosServerConfig>,
) -> ConnectorResult<()>
where
S: FramedRead + FramedWrite,
N: NetworkClient,
{
assert!(acceptor.should_perform_credssp());
let AcceptorState::Credssp { protocol, .. } = acceptor.state else {
unreachable!()
};
let result = credssp_loop(
framed,
acceptor,
network_client,
buf,
client_computer_name,
public_key,
kerberos_config,
)
.await;
if protocol.intersects(nego::SecurityProtocol::HYBRID_EX) {
trace!(?result, "HYBRID_EX");
let result = if result.is_ok() {
EarlyUserAuthResult::Success
} else {
EarlyUserAuthResult::AccessDenied
};
buf.clear();
result
.to_buffer(&mut *buf)
.map_err(|e| ironrdp_connector::custom_err!("to_buffer", e))?;
let response = &buf[..result.buffer_len()];
framed
.write_all(response)
.await
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
}
result?;
acceptor.mark_credssp_as_done();
return Ok(());
async fn credssp_loop<S, N>(
framed: &mut Framed<S>,
acceptor: &mut Acceptor,
network_client: &mut N,
buf: &mut WriteBuf,
client_computer_name: ServerName,
public_key: Vec<u8>,
kerberos_config: Option<KerberosServerConfig>,
) -> ConnectorResult<()>
where
S: FramedRead + FramedWrite,
N: NetworkClient,
{
let creds = acceptor
.creds
.as_ref()
.ok_or_else(|| general_err!("no credentials while doing credssp"))?;
let username = Username::new(&creds.username, None).map_err(|e| custom_err!("invalid username", e))?;
let identity = AuthIdentity {
username,
password: creds.password.clone().into(),
};
let mut sequence =
credssp::CredsspSequence::init(&identity, client_computer_name, public_key, kerberos_config)?;
loop {
let Some(next_pdu_hint) = sequence.next_pdu_hint()? else {
break;
};
debug!(
acceptor.state = ?acceptor.state,
hint = ?next_pdu_hint,
"Wait for PDU"
);
let pdu = framed
.read_by_hint(next_pdu_hint)
.await
.map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?;
trace!(length = pdu.len(), "PDU received");
let Some(ts_request) = sequence.decode_client_message(&pdu)? else {
break;
};
let result = {
let mut generator = sequence.process_ts_request(ts_request);
resolve_generator(&mut generator, network_client).await
}; // drop generator
buf.clear();
let written = sequence.handle_process_result(result, buf)?;
if let Some(response_len) = written.size() {
let response = &buf[..response_len];
trace!(response_len, "Send response");
framed
.write_all(response)
.await
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
}
}
Ok(())
}
}

View file

@ -0,0 +1,41 @@
use std::borrow::Cow;
use ironrdp_connector::{ConnectorError, ConnectorErrorExt as _, ConnectorResult};
use ironrdp_core::{encode_vec, Encode, WriteBuf};
use ironrdp_pdu::rdp;
use ironrdp_pdu::x224::X224;
pub(crate) fn encode_send_data_indication<T>(
initiator_id: u16,
channel_id: u16,
user_msg: &T,
buf: &mut WriteBuf,
) -> ConnectorResult<usize>
where
T: Encode,
{
let user_data = encode_vec(user_msg).map_err(ConnectorError::encode)?;
let pdu = ironrdp_pdu::mcs::SendDataIndication {
initiator_id,
channel_id,
user_data: Cow::Owned(user_data),
};
let written = ironrdp_core::encode_buf(&X224(pdu), buf).map_err(ConnectorError::encode)?;
Ok(written)
}
pub(crate) fn wrap_share_data(pdu: rdp::headers::ShareDataPdu, io_channel_id: u16) -> rdp::headers::ShareControlHeader {
rdp::headers::ShareControlHeader {
share_id: 0,
pdu_source: io_channel_id,
share_control_pdu: rdp::headers::ShareControlPdu::Data(rdp::headers::ShareDataHeader {
share_data_pdu: pdu,
stream_priority: rdp::headers::StreamPriority::Undefined,
compression_flags: rdp::headers::CompressionFlags::empty(),
compression_type: rdp::client_info::CompressionType::K8,
}),
}
}

View file

@ -0,0 +1,36 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [[0.2.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-ainput-v0.2.0...ironrdp-ainput-v0.2.1)] - 2025-05-27
### <!-- 7 -->Build
- Bump bitflags from 2.9.0 to 2.9.1 in the patch group across 1 directory (#792) ([87ed315bc2](https://github.com/Devolutions/IronRDP/commit/87ed315bc28fdd2dcfea89b052fa620a7e346e5a))
## [[0.1.3](https://github.com/Devolutions/IronRDP/compare/ironrdp-ainput-v0.1.2...ironrdp-ainput-v0.1.3)] - 2025-03-12
### <!-- 7 -->Build
- Update dependencies (#695) ([c21fa44fd6](https://github.com/Devolutions/IronRDP/commit/c21fa44fd6f3c6a6b74788ff68e83133c1314caa))
## [[0.1.2](https://github.com/Devolutions/IronRDP/compare/ironrdp-ainput-v0.1.1...ironrdp-ainput-v0.1.2)] - 2025-01-28
### <!-- 6 -->Documentation
- Use CDN URLs instead of the blob storage URLs for Devolutions logo (#631) ([dd249909a8](https://github.com/Devolutions/IronRDP/commit/dd249909a894004d4f728d30b3a4aa77a0f8193b))
## [[0.1.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-ainput-v0.1.0...ironrdp-ainput-v0.1.1)] - 2024-12-14
### Other
- Symlinks to license files in packages ([#604](https://github.com/Devolutions/IronRDP/pull/604)) ([6c2de344c2](https://github.com/Devolutions/IronRDP/commit/6c2de344c2dd93ce9621834e0497ed7c3bfaf91a))

View file

@ -0,0 +1,27 @@
[package]
name = "ironrdp-ainput"
version = "0.4.0"
readme = "README.md"
description = "AInput dynamic channel implementation"
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
authors.workspace = true
keywords.workspace = true
categories.workspace = true
[lib]
doctest = false
test = false
[dependencies]
ironrdp-core = { path = "../ironrdp-core", version = "0.1" } # public
ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.4" } # public
bitflags = "2.9"
num-derive.workspace = true # TODO: remove
num-traits.workspace = true # TODO: remove
[lints]
workspace = true

View file

@ -0,0 +1 @@
../../LICENSE-APACHE

View file

@ -0,0 +1 @@
../../LICENSE-MIT

View file

@ -0,0 +1,8 @@
# IronRDP AInput
Implements the "Advanced Input" dynamic channel as defined from [Freerdp][here].
This crate is part of the [IronRDP] project.
[here]: https://github.com/FreeRDP/FreeRDP/blob/master/include/freerdp/channels/ainput.h
[IronRDP]: https://github.com/Devolutions/IronRDP

View file

@ -0,0 +1,290 @@
#![cfg_attr(doc, doc = include_str!("../README.md"))]
#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
use bitflags::bitflags;
use ironrdp_core::{
ensure_fixed_part_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, ReadCursor, WriteCursor,
};
use ironrdp_dvc::DvcEncode;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive as _;
// Advanced Input channel as defined from Freerdp, [here]:
//
// [here]: https://github.com/FreeRDP/FreeRDP/blob/master/include/freerdp/channels/ainput.h
const VERSION_MAJOR: u32 = 1;
const VERSION_MINOR: u32 = 0;
pub const CHANNEL_NAME: &str = "FreeRDP::Advanced::Input";
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MouseEventFlags: u64 {
const WHEEL = 0x0000_0001;
const MOVE = 0x0000_0004;
const DOWN = 0x0000_0008;
const REL = 0x0000_0010;
const HAVE_REL = 0x0000_0020;
const BUTTON1 = 0x0000_1000; /* left */
const BUTTON2 = 0x0000_2000; /* right */
const BUTTON3 = 0x0000_4000; /* middle */
const XBUTTON1 = 0x0000_0100;
const XBUTTON2 = 0x0000_0200;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VersionPdu {
major_version: u32,
minor_version: u32,
}
impl VersionPdu {
const NAME: &'static str = "AInputVersionPdu";
const FIXED_PART_SIZE: usize = 4 /* MajorVersion */ + 4 /* MinorVersion */;
pub fn new() -> Self {
Self {
major_version: VERSION_MAJOR,
minor_version: VERSION_MINOR,
}
}
}
impl Default for VersionPdu {
fn default() -> Self {
Self::new()
}
}
impl Encode for VersionPdu {
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
ensure_fixed_part_size!(in: dst);
dst.write_u32(self.major_version);
dst.write_u32(self.minor_version);
Ok(())
}
fn name(&self) -> &'static str {
Self::NAME
}
fn size(&self) -> usize {
Self::FIXED_PART_SIZE
}
}
impl<'de> Decode<'de> for VersionPdu {
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
ensure_fixed_part_size!(in: src);
let major_version = src.read_u32();
let minor_version = src.read_u32();
Ok(Self {
major_version,
minor_version,
})
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive)]
#[repr(u16)]
pub enum ServerPduType {
Version = 0x01,
}
impl ServerPduType {
#[expect(
clippy::as_conversions,
reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive"
)]
fn as_u16(&self) -> u16 {
*self as u16
}
}
impl<'a> From<&'a ServerPdu> for ServerPduType {
fn from(s: &'a ServerPdu) -> Self {
match s {
ServerPdu::Version(_) => Self::Version,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ServerPdu {
Version(VersionPdu),
}
impl ServerPdu {
const NAME: &'static str = "AInputServerPdu";
const FIXED_PART_SIZE: usize = 2 /* PduType */;
}
impl Encode for ServerPdu {
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
ensure_fixed_part_size!(in: dst);
dst.write_u16(ServerPduType::from(self).as_u16());
match self {
ServerPdu::Version(pdu) => pdu.encode(dst),
}
}
fn name(&self) -> &'static str {
Self::NAME
}
fn size(&self) -> usize {
Self::FIXED_PART_SIZE
.checked_add(match self {
ServerPdu::Version(pdu) => pdu.size(),
})
.expect("never overflow")
}
}
impl DvcEncode for ServerPdu {}
impl<'de> Decode<'de> for ServerPdu {
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
ensure_fixed_part_size!(in: src);
let pdu_type =
ServerPduType::from_u16(src.read_u16()).ok_or_else(|| invalid_field_err!("pduType", "invalid pdu type"))?;
let server_pdu = match pdu_type {
ServerPduType::Version => ServerPdu::Version(VersionPdu::decode(src)?),
};
Ok(server_pdu)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MousePdu {
pub time: u64,
pub flags: MouseEventFlags,
pub x: i32,
pub y: i32,
}
impl MousePdu {
const NAME: &'static str = "AInputMousePdu";
const FIXED_PART_SIZE: usize = 8 /* Time */ + 8 /* Flags */ + 4 /* X */ + 4 /* Y */;
}
impl Encode for MousePdu {
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
ensure_fixed_part_size!(in: dst);
dst.write_u64(self.time);
dst.write_u64(self.flags.bits());
dst.write_i32(self.x);
dst.write_i32(self.y);
Ok(())
}
fn name(&self) -> &'static str {
Self::NAME
}
fn size(&self) -> usize {
Self::FIXED_PART_SIZE
}
}
impl<'de> Decode<'de> for MousePdu {
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
ensure_fixed_part_size!(in: src);
let time = src.read_u64();
let flags = MouseEventFlags::from_bits_retain(src.read_u64());
let x = src.read_i32();
let y = src.read_i32();
Ok(Self { time, flags, x, y })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ClientPdu {
Mouse(MousePdu),
}
impl ClientPdu {
const NAME: &'static str = "AInputClientPdu";
const FIXED_PART_SIZE: usize = 2 /* PduType */;
}
impl Encode for ClientPdu {
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
ensure_fixed_part_size!(in: dst);
dst.write_u16(ClientPduType::from(self).as_u16());
match self {
ClientPdu::Mouse(pdu) => pdu.encode(dst),
}
}
fn name(&self) -> &'static str {
Self::NAME
}
fn size(&self) -> usize {
Self::FIXED_PART_SIZE
.checked_add(match self {
ClientPdu::Mouse(pdu) => pdu.size(),
})
.expect("never overflow")
}
}
impl<'de> Decode<'de> for ClientPdu {
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
ensure_fixed_part_size!(in: src);
let pdu_type =
ClientPduType::from_u16(src.read_u16()).ok_or_else(|| invalid_field_err!("pduType", "invalid pdu type"))?;
let client_pdu = match pdu_type {
ClientPduType::Mouse => ClientPdu::Mouse(MousePdu::decode(src)?),
};
Ok(client_pdu)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive)]
#[repr(u16)]
pub enum ClientPduType {
Mouse = 0x02,
}
impl ClientPduType {
#[expect(
clippy::as_conversions,
reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive"
)]
fn as_u16(self) -> u16 {
self as u16
}
}
impl<'a> From<&'a ClientPdu> for ClientPduType {
fn from(s: &'a ClientPdu) -> Self {
match s {
ClientPdu::Mouse(_) => Self::Mouse,
}
}
}

View file

@ -0,0 +1,48 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [[0.8.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-async-v0.7.0...ironrdp-async-v0.8.0)] - 2025-12-18
### <!-- 4 -->Bug Fixes
- [**breaking**] Use static dispatch for NetworkClient trait ([#1043](https://github.com/Devolutions/IronRDP/issues/1043)) ([bca6d190a8](https://github.com/Devolutions/IronRDP/commit/bca6d190a870708468534d224ff225a658767a9a))
- Rename `AsyncNetworkClient` to `NetworkClient`
- Replace dynamic dispatch (`Option<&mut dyn ...>`) with static dispatch
using generics (`&mut N where N: NetworkClient`)
- Reorder `connect_finalize` parameters for consistency across crates
## [[0.3.2](https://github.com/Devolutions/IronRDP/compare/ironrdp-async-v0.3.1...ironrdp-async-v0.3.2)] - 2025-03-12
### <!-- 7 -->Build
- Bump ironrdp-pdu
## [[0.3.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-async-v0.3.0...ironrdp-async-v0.3.1)] - 2025-03-12
### <!-- 7 -->Build
- Update dependencies (#695) ([c21fa44fd6](https://github.com/Devolutions/IronRDP/commit/c21fa44fd6f3c6a6b74788ff68e83133c1314caa))
## [[0.3.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-async-v0.2.1...ironrdp-async-v0.3.0)] - 2025-01-28
### <!-- 4 -->Changed
- Remove unmatched parameter from `Framed::read_by_hint` function ([63963182b5](https://github.com/Devolutions/IronRDP/commit/63963182b5af6ad45dc638e93de4b8a0b565c7d3))
### <!-- 6 -->Documentation
- Use CDN URLs instead of the blob storage URLs for Devolutions logo (#631) ([dd249909a8](https://github.com/Devolutions/IronRDP/commit/dd249909a894004d4f728d30b3a4aa77a0f8193b))
## [[0.2.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-async-v0.2.0...ironrdp-async-v0.2.1)] - 2024-12-14
### Other
- Symlinks to license files in packages ([#604](https://github.com/Devolutions/IronRDP/pull/604)) ([6c2de344c2](https://github.com/Devolutions/IronRDP/commit/6c2de344c2dd93ce9621834e0497ed7c3bfaf91a))

View file

@ -0,0 +1,26 @@
[package]
name = "ironrdp-async"
version = "0.8.0"
readme = "README.md"
description = "Provides `Future`s wrapping the IronRDP state machines conveniently"
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
authors.workspace = true
keywords.workspace = true
categories.workspace = true
[lib]
doctest = false
test = false
[dependencies]
ironrdp-connector = { path = "../ironrdp-connector", version = "0.8" } # public
ironrdp-core = { path = "../ironrdp-core", version = "0.1", features = ["alloc"] } # public
ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.6" } # public
tracing = { version = "0.1", features = ["log"] }
bytes = "1" # public
[lints]
workspace = true

View file

@ -0,0 +1 @@
../../LICENSE-APACHE

View file

@ -0,0 +1 @@
../../LICENSE-MIT

View file

@ -0,0 +1,7 @@
# IronRDP Async
`Future`s built on top of `ironrdp-connector` and `ironrdp-session` crates.
This crate is part of the [IronRDP] project.
[IronRDP]: https://github.com/Devolutions/IronRDP

View file

@ -0,0 +1,189 @@
use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, KerberosConfig};
use ironrdp_connector::sspi::credssp::ClientState;
use ironrdp_connector::sspi::generator::GeneratorState;
use ironrdp_connector::{
general_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, ServerName,
State as _,
};
use ironrdp_core::WriteBuf;
use tracing::{debug, info, instrument, trace};
use crate::framed::{Framed, FramedRead, FramedWrite};
use crate::{single_sequence_step, NetworkClient};
#[non_exhaustive]
pub struct ShouldUpgrade;
#[instrument(skip_all)]
pub async fn connect_begin<S>(framed: &mut Framed<S>, connector: &mut ClientConnector) -> ConnectorResult<ShouldUpgrade>
where
S: Sync + FramedRead + FramedWrite,
{
let mut buf = WriteBuf::new();
info!("Begin connection procedure");
while !connector.should_perform_security_upgrade() {
single_sequence_step(framed, connector, &mut buf).await?;
}
Ok(ShouldUpgrade)
}
/// # Panics
///
/// Panics if connector state is not [ClientConnectorState::EnhancedSecurityUpgrade].
pub fn skip_connect_begin(connector: &mut ClientConnector) -> ShouldUpgrade {
assert!(connector.should_perform_security_upgrade());
ShouldUpgrade
}
#[non_exhaustive]
pub struct Upgraded;
#[instrument(skip_all)]
pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector) -> Upgraded {
trace!("Marked as upgraded");
connector.mark_security_upgrade_as_done();
Upgraded
}
#[instrument(skip_all)]
pub async fn connect_finalize<S, N>(
_: Upgraded,
mut connector: ClientConnector,
framed: &mut Framed<S>,
network_client: &mut N,
server_name: ServerName,
server_public_key: Vec<u8>,
kerberos_config: Option<KerberosConfig>,
) -> ConnectorResult<ConnectionResult>
where
S: FramedRead + FramedWrite,
N: NetworkClient,
{
let mut buf = WriteBuf::new();
if connector.should_perform_credssp() {
perform_credssp_step(
&mut connector,
framed,
network_client,
&mut buf,
server_name,
server_public_key,
kerberos_config,
)
.await?;
}
let result = loop {
single_sequence_step(framed, &mut connector, &mut buf).await?;
if let ClientConnectorState::Connected { result } = connector.state {
break result;
}
};
info!("Connected with success");
Ok(result)
}
async fn resolve_generator(
generator: &mut CredsspProcessGenerator<'_>,
network_client: &mut impl NetworkClient,
) -> ConnectorResult<ClientState> {
let mut state = generator.start();
loop {
match state {
GeneratorState::Suspended(request) => {
let response = network_client.send(&request).await?;
state = generator.resume(Ok(response));
}
GeneratorState::Completed(client_state) => {
break client_state
.map_err(|e| ConnectorError::new("CredSSP", ironrdp_connector::ConnectorErrorKind::Credssp(e)))
}
}
}
}
#[instrument(level = "trace", skip_all)]
async fn perform_credssp_step<S, N>(
connector: &mut ClientConnector,
framed: &mut Framed<S>,
network_client: &mut N,
buf: &mut WriteBuf,
server_name: ServerName,
server_public_key: Vec<u8>,
kerberos_config: Option<KerberosConfig>,
) -> ConnectorResult<()>
where
S: FramedRead + FramedWrite,
N: NetworkClient,
{
assert!(connector.should_perform_credssp());
let selected_protocol = match connector.state {
ClientConnectorState::Credssp { selected_protocol, .. } => selected_protocol,
_ => return Err(general_err!("invalid connector state for CredSSP sequence")),
};
let (mut sequence, mut ts_request) = CredsspSequence::init(
connector.config.credentials.clone(),
connector.config.domain.as_deref(),
selected_protocol,
server_name,
server_public_key,
kerberos_config,
)?;
loop {
let client_state = {
let mut generator = sequence.process_ts_request(ts_request);
trace!("resolving network");
resolve_generator(&mut generator, network_client).await?
}; // drop generator
buf.clear();
let written = sequence.handle_process_result(client_state, buf)?;
if let Some(response_len) = written.size() {
let response = &buf[..response_len];
trace!(response_len, "Send response");
framed
.write_all(response)
.await
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
}
let Some(next_pdu_hint) = sequence.next_pdu_hint() else {
break;
};
debug!(
connector.state = connector.state.name(),
hint = ?next_pdu_hint,
"Wait for PDU"
);
let pdu = framed
.read_by_hint(next_pdu_hint)
.await
.map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?;
trace!(length = pdu.len(), "PDU received");
if let Some(next_request) = sequence.decode_server_message(&pdu)? {
ts_request = next_request;
} else {
break;
}
}
connector.mark_credssp_as_done();
Ok(())
}

View file

@ -0,0 +1,290 @@
use std::io;
use bytes::{Bytes, BytesMut};
use ironrdp_connector::{ConnectorResult, Sequence, Written};
use ironrdp_core::WriteBuf;
use ironrdp_pdu::PduHint;
use tracing::{debug, trace};
// TODO: investigate if we could use static async fn / return position impl trait in traits when stabilized:
// https://github.com/rust-lang/rust/issues/91611
pub trait FramedRead {
type ReadFut<'read>: core::future::Future<Output = io::Result<usize>> + 'read
where
Self: 'read;
/// Reads from stream and fills internal buffer
///
/// # Cancel safety
///
/// This method is cancel safe. If you use it as the event in a
/// `tokio::select!` statement and some other branch
/// completes first, then it is guaranteed that no data was read.
fn read<'a>(&'a mut self, buf: &'a mut BytesMut) -> Self::ReadFut<'a>;
}
pub trait FramedWrite {
type WriteAllFut<'write>: core::future::Future<Output = io::Result<()>> + 'write
where
Self: 'write;
/// Writes an entire buffer into this stream.
///
/// # Cancel safety
///
/// This method is not cancellation safe. If it is used as the event
/// in a `tokio::select!` statement and some other
/// branch completes first, then the provided buffer may have been
/// partially written, but future calls to `write_all` will start over
/// from the beginning of the buffer.
fn write_all<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteAllFut<'a>;
}
pub trait StreamWrapper: Sized {
type InnerStream;
fn from_inner(stream: Self::InnerStream) -> Self;
fn into_inner(self) -> Self::InnerStream;
fn get_inner(&self) -> &Self::InnerStream;
fn get_inner_mut(&mut self) -> &mut Self::InnerStream;
}
pub struct Framed<S> {
stream: S,
buf: BytesMut,
}
impl<S> Framed<S> {
pub fn peek(&self) -> &[u8] {
&self.buf
}
}
impl<S> Framed<S>
where
S: StreamWrapper,
{
pub fn new(stream: S::InnerStream) -> Self {
Self::new_with_leftover(stream, BytesMut::new())
}
pub fn new_with_leftover(stream: S::InnerStream, leftover: BytesMut) -> Self {
Self {
stream: S::from_inner(stream),
buf: leftover,
}
}
pub fn into_inner(self) -> (S::InnerStream, BytesMut) {
(self.stream.into_inner(), self.buf)
}
pub fn into_inner_no_leftover(self) -> S::InnerStream {
let (stream, leftover) = self.into_inner();
debug_assert_eq!(leftover.len(), 0, "unexpected leftover");
stream
}
pub fn get_inner(&self) -> (&S::InnerStream, &BytesMut) {
(self.stream.get_inner(), &self.buf)
}
pub fn get_inner_mut(&mut self) -> (&mut S::InnerStream, &mut BytesMut) {
(self.stream.get_inner_mut(), &mut self.buf)
}
}
impl<S> Framed<S>
where
S: FramedRead,
{
/// Accumulates at least `length` bytes and returns exactly `length` bytes, keeping the leftover in the internal buffer.
///
/// # Cancel safety
///
/// This method is cancel safe. If you use it as the event in a
/// `tokio::select!` statement and some other branch
/// completes first, then it is safe to drop the future and re-create it later.
/// Data may have been read, but it will be stored in the internal buffer.
pub async fn read_exact(&mut self, length: usize) -> io::Result<BytesMut> {
loop {
if self.buf.len() >= length {
return Ok(self.buf.split_to(length));
} else {
#[expect(clippy::missing_panics_doc, reason = "unreachable panic (checked integer underflow)")]
self.buf
.reserve(length.checked_sub(self.buf.len()).expect("length > self.buf.len()"));
}
let len = self.read().await?;
// Handle EOF
if len == 0 {
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "not enough bytes"));
}
}
}
/// Reads a standard RDP PDU frame.
///
/// # Cancel safety
///
/// This method is cancel safe. If you use it as the event in a
/// `tokio::select!` statement and some other branch
/// completes first, then it is safe to drop the future and re-create it later.
/// Data may have been read, but it will be stored in the internal buffer.
pub async fn read_pdu(&mut self) -> io::Result<(ironrdp_pdu::Action, BytesMut)> {
loop {
// Try decoding and see if a frame has been received already
match ironrdp_pdu::find_size(self.peek()) {
Ok(Some(pdu_info)) => {
let frame = self.read_exact(pdu_info.length).await?;
return Ok((pdu_info.action, frame));
}
Ok(None) => {
let len = self.read().await?;
// Handle EOF
if len == 0 {
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "not enough bytes"));
}
}
Err(e) => return Err(io::Error::other(e)),
};
}
}
/// Reads a frame using the provided PduHint.
///
/// # Cancel safety
///
/// This method is cancel safe. If you use it as the event in a
/// `tokio::select!` statement and some other branch
/// completes first, then it is safe to drop the future and re-create it later.
/// Data may have been read, but it will be stored in the internal buffer.
pub async fn read_by_hint(&mut self, hint: &dyn PduHint) -> io::Result<Bytes> {
loop {
match hint.find_size(self.peek()).map_err(io::Error::other)? {
Some((matched, length)) => {
let bytes = self.read_exact(length).await?.freeze();
if matched {
return Ok(bytes);
} else {
debug!("Received and lost an unexpected PDU");
}
}
None => {
let len = self.read().await?;
// Handle EOF
if len == 0 {
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "not enough bytes"));
}
}
};
}
}
/// Reads from stream and fills internal buffer, returning how many bytes were read.
///
/// # Cancel safety
///
/// This method is cancel safe. If you use it as the event in a
/// `tokio::select!` statement and some other branch
/// completes first, then it is guaranteed that no data was read.
async fn read(&mut self) -> io::Result<usize> {
self.stream.read(&mut self.buf).await
}
}
impl<S> FramedWrite for Framed<S>
where
S: FramedWrite,
{
type WriteAllFut<'write>
= S::WriteAllFut<'write>
where
Self: 'write;
/// Attempts to write an entire buffer into this `Framed`s stream.
///
/// # Cancel safety
///
/// This method is not cancellation safe. If it is used as the event
/// in a `tokio::select!` statement and some other
/// branch completes first, then the provided buffer may have been
/// partially written, but future calls to `write_all` will start over
/// from the beginning of the buffer.
fn write_all<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteAllFut<'a> {
self.stream.write_all(buf)
}
}
pub async fn single_sequence_step<S>(
framed: &mut Framed<S>,
sequence: &mut dyn Sequence,
buf: &mut WriteBuf,
) -> ConnectorResult<()>
where
S: FramedWrite + FramedRead,
{
buf.clear();
let written = single_sequence_step_read(framed, sequence, buf).await?;
single_sequence_step_write(framed, buf, written).await
}
pub async fn single_sequence_step_read<S>(
framed: &mut Framed<S>,
sequence: &mut dyn Sequence,
buf: &mut WriteBuf,
) -> ConnectorResult<Written>
where
S: FramedRead,
{
buf.clear();
if let Some(next_pdu_hint) = sequence.next_pdu_hint() {
debug!(
connector.state = sequence.state().name(),
hint = ?next_pdu_hint,
"Wait for PDU"
);
let pdu = framed
.read_by_hint(next_pdu_hint)
.await
.map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?;
trace!(length = pdu.len(), "PDU received");
sequence.step(&pdu, buf)
} else {
sequence.step_no_input(buf)
}
}
async fn single_sequence_step_write<S>(
framed: &mut Framed<S>,
buf: &mut WriteBuf,
written: Written,
) -> ConnectorResult<()>
where
S: FramedWrite,
{
if let Some(response_len) = written.size() {
debug_assert_eq!(buf.filled_len(), response_len);
let response = buf.filled();
trace!(response_len, "Send response");
framed
.write_all(response)
.await
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
}
Ok(())
}

View file

@ -0,0 +1,21 @@
#![cfg_attr(doc, doc = include_str!("../README.md"))]
#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
use core::future::Future;
pub use bytes;
mod connector;
mod framed;
mod session;
use ironrdp_connector::sspi::generator::NetworkRequest;
use ironrdp_connector::ConnectorResult;
pub use self::connector::*;
pub use self::framed::*;
// pub use self::session::*;
pub trait NetworkClient {
fn send(&mut self, network_request: &NetworkRequest) -> impl Future<Output = ConnectorResult<Vec<u8>>>;
}

View file

@ -0,0 +1 @@
// TODO: active session async helpers

View file

@ -0,0 +1,20 @@
[package]
name = "ironrdp-bench"
version = "0.0.0"
description = "IronRDP benchmarks"
edition.workspace = true
publish = false
[dev-dependencies]
criterion = "0.8"
ironrdp-graphics.path = "../ironrdp-graphics"
ironrdp-pdu.path = "../ironrdp-pdu"
ironrdp-server = { path = "../ironrdp-server", features = ["__bench"] }
[[bench]]
name = "bench"
path = "benches/bench.rs"
harness = false
[lints]
workspace = true

View file

@ -0,0 +1,80 @@
#![expect(clippy::missing_panics_doc, reason = "panics in benches are allowed")]
use core::num::{NonZeroU16, NonZeroUsize};
use criterion::{criterion_group, criterion_main, Criterion};
use ironrdp_graphics::color_conversion::to_64x64_ycbcr_tile;
use ironrdp_pdu::codecs::rfx;
use ironrdp_server::bench::encoder::rfx::{rfx_enc, rfx_enc_tile};
use ironrdp_server::BitmapUpdate;
pub fn rfx_enc_tile_bench(c: &mut Criterion) {
const WIDTH: NonZeroU16 = NonZeroU16::new(64).expect("value is guaranteed to be non-zero");
const HEIGHT: NonZeroU16 = NonZeroU16::new(64).expect("value is guaranteed to be non-zero");
const STRIDE: NonZeroUsize = NonZeroUsize::new(64 * 4).expect("value is guaranteed to be non-zero");
let quant = rfx::Quant::default();
let algo = rfx::EntropyAlgorithm::Rlgr3;
let bitmap = BitmapUpdate {
x: 0,
y: 0,
width: WIDTH,
height: HEIGHT,
format: ironrdp_server::PixelFormat::ARgb32,
data: vec![0; 64 * 64 * 4].into(),
stride: STRIDE,
};
c.bench_function("rfx_enc_tile", |b| b.iter(|| rfx_enc_tile(&bitmap, &quant, algo, 0, 0)));
}
pub fn rfx_enc_bench(c: &mut Criterion) {
const WIDTH: NonZeroU16 = NonZeroU16::new(2048).expect("value is guaranteed to be non-zero");
const HEIGHT: NonZeroU16 = NonZeroU16::new(2048).expect("value is guaranteed to be non-zero");
// FIXME/QUESTION: It looks like we have a bug here, don't we? The stride value should be 2048 * 4.
const STRIDE: NonZeroUsize = NonZeroUsize::new(64 * 4).expect("value is guaranteed to be non-zero");
let quant = rfx::Quant::default();
let algo = rfx::EntropyAlgorithm::Rlgr3;
let bitmap = BitmapUpdate {
x: 0,
y: 0,
width: WIDTH,
height: HEIGHT,
format: ironrdp_server::PixelFormat::ARgb32,
data: vec![0; 2048 * 2048 * 4].into(),
stride: STRIDE,
};
c.bench_function("rfx_enc", |b| b.iter(|| rfx_enc(&bitmap, &quant, algo)));
}
pub fn to_ycbcr_bench(c: &mut Criterion) {
const WIDTH: usize = 64;
const HEIGHT: usize = 64;
let input = vec![0; WIDTH * HEIGHT * 4];
let stride = WIDTH * 4;
let mut y = [0i16; WIDTH * HEIGHT];
let mut cb = [0i16; WIDTH * HEIGHT];
let mut cr = [0i16; WIDTH * HEIGHT];
let format = ironrdp_graphics::image_processing::PixelFormat::ARgb32;
c.bench_function("to_ycbcr", |b| {
b.iter(|| {
to_64x64_ycbcr_tile(
&input,
WIDTH.try_into().expect("can't panic"),
HEIGHT.try_into().expect("can't panic"),
stride.try_into().expect("can't panic"),
format,
&mut y,
&mut cb,
&mut cr,
)
})
});
}
criterion_group!(benches, rfx_enc_tile_bench, rfx_enc_bench, to_ycbcr_bench);
criterion_main!(benches);

View file

@ -0,0 +1,48 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [[0.8.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-blocking-v0.7.0...ironrdp-blocking-v0.8.0)] - 2025-12-18
### <!-- 4 -->Bug Fixes
- [**breaking**] Use static dispatch for NetworkClient trait ([#1043](https://github.com/Devolutions/IronRDP/issues/1043)) ([bca6d190a8](https://github.com/Devolutions/IronRDP/commit/bca6d190a870708468534d224ff225a658767a9a))
- Rename `AsyncNetworkClient` to `NetworkClient`
- Replace dynamic dispatch (`Option<&mut dyn ...>`) with static dispatch
using generics (`&mut N where N: NetworkClient`)
- Reorder `connect_finalize` parameters for consistency across crates
## [[0.4.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-blocking-v0.3.1...ironrdp-blocking-v0.4.0)] - 2025-03-12
### <!-- 7 -->Build
- Bump ironrdp-pdu
## [[0.3.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-blocking-v0.3.0...ironrdp-blocking-v0.3.1)] - 2025-03-12
### <!-- 7 -->Build
- Update dependencies (#695) ([c21fa44fd6](https://github.com/Devolutions/IronRDP/commit/c21fa44fd6f3c6a6b74788ff68e83133c1314caa))
## [[0.3.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-blocking-v0.2.1...ironrdp-blocking-v0.3.0)] - 2025-01-28
### <!-- 4 -->Changed
- Remove unmatched parameter from `Framed::read_by_hint` function ([63963182b5](https://github.com/Devolutions/IronRDP/commit/63963182b5af6ad45dc638e93de4b8a0b565c7d3))
### <!-- 6 -->Documentation
- Use CDN URLs instead of the blob storage URLs for Devolutions logo (#631) ([dd249909a8](https://github.com/Devolutions/IronRDP/commit/dd249909a894004d4f728d30b3a4aa77a0f8193b))
## [[0.2.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-blocking-v0.2.0...ironrdp-blocking-v0.2.1)] - 2024-12-14
### Other
- Symlinks to license files in packages ([#604](https://github.com/Devolutions/IronRDP/pull/604)) ([6c2de344c2](https://github.com/Devolutions/IronRDP/commit/6c2de344c2dd93ce9621834e0497ed7c3bfaf91a))

View file

@ -0,0 +1,27 @@
[package]
name = "ironrdp-blocking"
version = "0.8.0"
readme = "README.md"
description = "Blocking I/O abstraction wrapping the IronRDP state machines conveniently"
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
authors.workspace = true
keywords.workspace = true
categories.workspace = true
[lib]
doctest = false
test = false
[dependencies]
ironrdp-connector = { path = "../ironrdp-connector", version = "0.8" } # public
ironrdp-core = { path = "../ironrdp-core", version = "0.1", features = ["alloc"] } # public
ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.6" } # public
tracing = { version = "0.1", features = ["log"] }
bytes = "1" # public
[lints]
workspace = true

View file

@ -0,0 +1 @@
../../LICENSE-APACHE

View file

@ -0,0 +1 @@
../../LICENSE-MIT

View file

@ -0,0 +1,11 @@
# IronRDP Blocking
Blocking I/O abstraction wrapping the IronRDP state machines conveniently.
This crate is a higher level abstraction for IronRDP state machines using blocking I/O instead of
asynchronous I/O. This results in a simpler API with fewer dependencies that may be used
instead of `ironrdp-async` when concurrency is not a requirement.
This crate is part of the [IronRDP] project.
[IronRDP]: https://github.com/Devolutions/IronRDP

View file

@ -0,0 +1,230 @@
use std::io::{Read, Write};
use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, KerberosConfig};
use ironrdp_connector::sspi::credssp::ClientState;
use ironrdp_connector::sspi::generator::GeneratorState;
use ironrdp_connector::sspi::network_client::NetworkClient;
use ironrdp_connector::{
general_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult,
Sequence as _, ServerName, State as _,
};
use ironrdp_core::WriteBuf;
use tracing::{debug, info, instrument, trace};
use crate::framed::Framed;
#[non_exhaustive]
pub struct ShouldUpgrade;
#[instrument(skip_all)]
pub fn connect_begin<S>(framed: &mut Framed<S>, connector: &mut ClientConnector) -> ConnectorResult<ShouldUpgrade>
where
S: Sync + Read + Write,
{
let mut buf = WriteBuf::new();
info!("Begin connection procedure");
while !connector.should_perform_security_upgrade() {
single_sequence_step(framed, connector, &mut buf)?;
}
Ok(ShouldUpgrade)
}
/// # Panics
///
/// Panics if connector state is not [ClientConnectorState::EnhancedSecurityUpgrade].
pub fn skip_connect_begin(connector: &mut ClientConnector) -> ShouldUpgrade {
assert!(connector.should_perform_security_upgrade());
ShouldUpgrade
}
#[non_exhaustive]
pub struct Upgraded;
#[instrument(skip_all)]
pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector) -> Upgraded {
trace!("Marked as upgraded");
connector.mark_security_upgrade_as_done();
Upgraded
}
#[instrument(skip_all)]
pub fn connect_finalize<S>(
_: Upgraded,
mut connector: ClientConnector,
framed: &mut Framed<S>,
network_client: &mut impl NetworkClient,
server_name: ServerName,
server_public_key: Vec<u8>,
kerberos_config: Option<KerberosConfig>,
) -> ConnectorResult<ConnectionResult>
where
S: Read + Write,
{
let mut buf = WriteBuf::new();
debug!("CredSSP procedure");
if connector.should_perform_credssp() {
perform_credssp_step(
&mut connector,
framed,
network_client,
&mut buf,
server_name,
server_public_key,
kerberos_config,
)?;
}
debug!("Remaining of connection sequence");
let result = loop {
single_sequence_step(framed, &mut connector, &mut buf)?;
if let ClientConnectorState::Connected { result } = connector.state {
break result;
}
};
info!("Connected with success");
Ok(result)
}
fn resolve_generator(
generator: &mut CredsspProcessGenerator<'_>,
network_client: &mut impl NetworkClient,
) -> ConnectorResult<ClientState> {
let mut state = generator.start();
loop {
match state {
GeneratorState::Suspended(request) => {
let response = network_client.send(&request).map_err(|e| {
ConnectorError::new("network client send", ironrdp_connector::ConnectorErrorKind::Credssp(e))
})?;
state = generator.resume(Ok(response));
}
GeneratorState::Completed(client_state) => {
break client_state
.map_err(|e| ConnectorError::new("CredSSP", ironrdp_connector::ConnectorErrorKind::Credssp(e)))
}
}
}
}
#[instrument(level = "trace", skip_all)]
fn perform_credssp_step<S>(
connector: &mut ClientConnector,
framed: &mut Framed<S>,
network_client: &mut impl NetworkClient,
buf: &mut WriteBuf,
server_name: ServerName,
server_public_key: Vec<u8>,
kerberos_config: Option<KerberosConfig>,
) -> ConnectorResult<()>
where
S: Read + Write,
{
assert!(connector.should_perform_credssp());
let selected_protocol = match connector.state {
ClientConnectorState::Credssp { selected_protocol, .. } => selected_protocol,
_ => return Err(general_err!("invalid connector state for CredSSP sequence")),
};
let (mut sequence, mut ts_request) = CredsspSequence::init(
connector.config.credentials.clone(),
connector.config.domain.as_deref(),
selected_protocol,
server_name,
server_public_key,
kerberos_config,
)?;
loop {
let client_state = {
let mut generator = sequence.process_ts_request(ts_request);
resolve_generator(&mut generator, network_client)?
}; // drop generator
buf.clear();
let written = sequence.handle_process_result(client_state, buf)?;
if let Some(response_len) = written.size() {
let response = &buf[..response_len];
trace!(response_len, "Send response");
framed
.write_all(response)
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
}
let Some(next_pdu_hint) = sequence.next_pdu_hint() else {
break;
};
debug!(
connector.state = connector.state.name(),
hint = ?next_pdu_hint,
"Wait for PDU"
);
let pdu = framed
.read_by_hint(next_pdu_hint)
.map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?;
trace!(length = pdu.len(), "PDU received");
if let Some(next_request) = sequence.decode_server_message(&pdu)? {
ts_request = next_request;
} else {
break;
}
}
connector.mark_credssp_as_done();
Ok(())
}
pub fn single_sequence_step<S>(
framed: &mut Framed<S>,
connector: &mut ClientConnector,
buf: &mut WriteBuf,
) -> ConnectorResult<()>
where
S: Read + Write,
{
buf.clear();
let written = if let Some(next_pdu_hint) = connector.next_pdu_hint() {
debug!(
connector.state = connector.state.name(),
hint = ?next_pdu_hint,
"Wait for PDU"
);
let pdu = framed
.read_by_hint(next_pdu_hint)
.map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?;
trace!(length = pdu.len(), "PDU received");
connector.step(&pdu, buf)?
} else {
connector.step_no_input(buf)?
};
if let Some(response_len) = written.size() {
let response = &buf[..response_len];
trace!(response_len, "Send response");
framed
.write_all(response)
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
}
Ok(())
}

View file

@ -0,0 +1,136 @@
use std::io::{self, Read, Write};
use bytes::{Bytes, BytesMut};
use ironrdp_pdu::PduHint;
use tracing::debug;
pub struct Framed<S> {
stream: S,
buf: BytesMut,
}
impl<S> Framed<S> {
pub fn new(stream: S) -> Self {
Self::new_with_leftover(stream, BytesMut::new())
}
pub fn new_with_leftover(stream: S, leftover: BytesMut) -> Self {
Self { stream, buf: leftover }
}
pub fn into_inner(self) -> (S, BytesMut) {
(self.stream, self.buf)
}
pub fn into_inner_no_leftover(self) -> S {
let (stream, leftover) = self.into_inner();
debug_assert_eq!(leftover.len(), 0, "unexpected leftover");
stream
}
pub fn get_inner(&self) -> (&S, &BytesMut) {
(&self.stream, &self.buf)
}
pub fn get_inner_mut(&mut self) -> (&mut S, &mut BytesMut) {
(&mut self.stream, &mut self.buf)
}
pub fn peek(&self) -> &[u8] {
&self.buf
}
}
impl<S> Framed<S>
where
S: Read,
{
/// Accumulates at least `length` bytes and returns exactly `length` bytes, keeping the leftover in the internal buffer.
pub fn read_exact(&mut self, length: usize) -> io::Result<BytesMut> {
loop {
if self.buf.len() >= length {
return Ok(self.buf.split_to(length));
} else {
#[expect(clippy::missing_panics_doc, reason = "unreachable panic (checked underflow)")]
self.buf
.reserve(length.checked_sub(self.buf.len()).expect("length > self.buf.len()"));
}
let len = self.read()?;
// Handle EOF
if len == 0 {
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "not enough bytes"));
}
}
}
/// Reads a standard RDP PDU frame.
pub fn read_pdu(&mut self) -> io::Result<(ironrdp_pdu::Action, BytesMut)> {
loop {
// Try decoding and see if a frame has been received already
match ironrdp_pdu::find_size(self.peek()) {
Ok(Some(pdu_info)) => {
let frame = self.read_exact(pdu_info.length)?;
return Ok((pdu_info.action, frame));
}
Ok(None) => {
let len = self.read()?;
// Handle EOF
if len == 0 {
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "not enough bytes"));
}
}
Err(e) => return Err(io::Error::other(e)),
};
}
}
/// Reads a frame using the provided PduHint.
pub fn read_by_hint(&mut self, hint: &dyn PduHint) -> io::Result<Bytes> {
loop {
match hint.find_size(self.peek()).map_err(io::Error::other)? {
Some((matched, length)) => {
let bytes = self.read_exact(length)?.freeze();
if matched {
return Ok(bytes);
} else {
debug!("Received and lost an unexpected PDU");
}
}
None => {
let len = self.read()?;
// Handle EOF
if len == 0 {
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "not enough bytes"));
}
}
};
}
}
/// Reads from stream and fills internal buffer, returning how many bytes were read.
fn read(&mut self) -> io::Result<usize> {
// FIXME(perf): use read_buf (https://doc.rust-lang.org/std/io/trait.Read.html#method.read_buf)
// once its stabilized. See tracking issue for RFC 2930: https://github.com/rust-lang/rust/issues/78485
let mut read_bytes = [0u8; 1024];
let len = self.stream.read(&mut read_bytes)?;
self.buf.extend_from_slice(&read_bytes[..len]);
Ok(len)
}
}
impl<S> Framed<S>
where
S: Write,
{
/// Attempts to write an entire buffer into this `Framed`s stream.
pub fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
self.stream.write_all(buf)
}
}

View file

@ -0,0 +1,9 @@
#![cfg_attr(doc, doc = include_str!("../README.md"))]
#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
mod connector;
mod framed;
mod session;
pub use self::connector::*;
pub use self::framed::*;

View file

@ -0,0 +1 @@
// TODO: active session I/O helpers? Im not yet sure we need that

View file

@ -0,0 +1,23 @@
[package]
name = "ironrdp-cfg"
version = "0.1.0"
readme = "README.md"
description = "IronRDP utilities for ironrdp-cfgstore"
publish = false # TODO: publish
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
authors.workspace = true
keywords.workspace = true
categories.workspace = true
[lib]
doctest = false
test = false
[dependencies]
ironrdp-propertyset = { path = "../ironrdp-propertyset", version = "0.1" } # public
[lints]
workspace = true

View file

@ -0,0 +1,7 @@
# IronRDP Configuration
IronRDP-related utilities for ironrdp-propertyset.
This crate is part of the [IronRDP] project.
[IronRDP]: https://github.com/Devolutions/IronRDP

View file

@ -0,0 +1,63 @@
// QUESTION: consider auto-generating this file based on a reference file?
// https://gist.github.com/awakecoding/838c7fe2ed3a6208e3ca5d8af25363f6
use ironrdp_propertyset::PropertySet;
pub trait PropertySetExt {
fn full_address(&self) -> Option<&str>;
fn server_port(&self) -> Option<i64>;
fn alternate_full_address(&self) -> Option<&str>;
fn gateway_hostname(&self) -> Option<&str>;
fn remote_application_name(&self) -> Option<&str>;
fn remote_application_program(&self) -> Option<&str>;
fn kdc_proxy_url(&self) -> Option<&str>;
fn username(&self) -> Option<&str>;
/// Target RDP server password - use for testing only
fn clear_text_password(&self) -> Option<&str>;
}
impl PropertySetExt for PropertySet {
fn full_address(&self) -> Option<&str> {
self.get::<&str>("full address")
}
fn server_port(&self) -> Option<i64> {
self.get::<i64>("server port")
}
fn alternate_full_address(&self) -> Option<&str> {
self.get::<&str>("alternate full address")
}
fn gateway_hostname(&self) -> Option<&str> {
self.get::<&str>("gatewayhostname")
}
fn remote_application_name(&self) -> Option<&str> {
self.get::<&str>("remoteapplicationname")
}
fn remote_application_program(&self) -> Option<&str> {
self.get::<&str>("remoteapplicationprogram")
}
fn kdc_proxy_url(&self) -> Option<&str> {
self.get::<&str>("kdcproxyurl")
}
fn username(&self) -> Option<&str> {
self.get::<&str>("username")
}
fn clear_text_password(&self) -> Option<&str> {
self.get::<&str>("ClearTextPassword")
}
}

View file

@ -0,0 +1,49 @@
[package]
name = "ironrdp-client-glutin"
version = "0.1.0"
readme = "README.md"
description = "GPU-accelerated RDP client using glutin"
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
authors.workspace = true
keywords.workspace = true
categories.workspace = true
[features]
default = ["rustls"]
rustls = ["ironrdp-tls/rustls"]
native-tls = ["ironrdp-tls/native-tls"]
[dependencies]
# Protocols
ironrdp.workspace = true
ironrdp-tls.workspace = true
sspi = { workspace = true, features = ["network_client"] }
# CLI
clap = { version = "4.2", features = ["derive", "cargo"] }
exitcode = "1.1"
# logging
tracing.workspace = true
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# async, futures
tokio = { version = "1", features = ["full"]}
tokio-util = { version = "0.7", features = ["compat"] }
futures-util = "0.3"
# Utils
chrono = "0.4"
anyhow = "1.0"
# GUI
glutin = "0.29"
ironrdp-glutin-renderer = { path = "../glutin-renderer"}
[lints]
workspace = true

View file

@ -0,0 +1,13 @@
# GUI client
1. An experimental GUI based of glutin and glow library.
2. Sample command to run the ui client:
```
cargo run --bin ironrdp-gui-client -- -u SimpleUsername -p SimplePassword! --avc444 --thin-client --small-cache --capabilities 0xf 192.168.1.100:3389
```
3. If the GUI has artifacts it can be dumped to a file using the gfx_dump_file parameter. Later the ironrdp-replay-client binary can be used to debug and fix any issues
in the renderer.
This crate is part of the [IronRDP] project.
[IronRDP]: https://github.com/Devolutions/IronRDP

View file

@ -0,0 +1,186 @@
use std::num::ParseIntError;
use std::path::PathBuf;
use clap::clap_derive::ValueEnum;
use clap::{crate_name, Parser};
use ironrdp::session::{GraphicsConfig, InputConfig};
use sspi::AuthIdentity;
const DEFAULT_WIDTH: u16 = 1920;
const DEFAULT_HEIGHT: u16 = 1080;
const GLOBAL_CHANNEL_NAME: &str = "GLOBAL";
const USER_CHANNEL_NAME: &str = "USER";
pub struct Config {
pub log_file: String,
pub addr: String,
pub input: InputConfig,
pub gfx_dump_file: Option<PathBuf>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum SecurityProtocol {
Ssl,
Hybrid,
HybridEx,
}
impl SecurityProtocol {
fn parse(security_protocol: SecurityProtocol) -> ironrdp::pdu::SecurityProtocol {
match security_protocol {
SecurityProtocol::Ssl => ironrdp::pdu::SecurityProtocol::SSL,
SecurityProtocol::Hybrid => ironrdp::pdu::SecurityProtocol::HYBRID,
SecurityProtocol::HybridEx => ironrdp::pdu::SecurityProtocol::HYBRID_EX,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum KeyboardType {
IbmPcXt,
OlivettiIco,
IbmPcAt,
IbmEnhanced,
Nokia1050,
Nokia9140,
Japanese,
}
impl KeyboardType {
fn parse(keyboard_type: KeyboardType) -> ironrdp::pdu::gcc::KeyboardType {
match keyboard_type {
KeyboardType::IbmEnhanced => ironrdp::pdu::gcc::KeyboardType::IbmEnhanced,
KeyboardType::IbmPcAt => ironrdp::pdu::gcc::KeyboardType::IbmPcAt,
KeyboardType::IbmPcXt => ironrdp::pdu::gcc::KeyboardType::IbmPcXt,
KeyboardType::OlivettiIco => ironrdp::pdu::gcc::KeyboardType::OlivettiIco,
KeyboardType::Nokia1050 => ironrdp::pdu::gcc::KeyboardType::Nokia1050,
KeyboardType::Nokia9140 => ironrdp::pdu::gcc::KeyboardType::Nokia9140,
KeyboardType::Japanese => ironrdp::pdu::gcc::KeyboardType::Japanese,
}
}
}
fn parse_hex(input: &str) -> Result<u32, ParseIntError> {
if input.starts_with("0x") {
u32::from_str_radix(input.get(2..).unwrap_or(""), 16)
} else {
input.parse::<u32>()
}
}
/// Devolutions IronRDP client
#[derive(Parser, Debug)]
#[clap(author = "Devolutions", about = "Devolutions-IronRDP client")]
#[clap(version, long_about = None)]
struct Args {
/// A file with IronRDP client logs
#[clap(short, long, value_parser, default_value_t = format!("{}.log", crate_name!()))]
log_file: String,
/// An address on which the client will connect.
addr: String,
/// A target RDP server user name
#[clap(short, long, value_parser)]
username: String,
/// An optional target RDP server domain name
#[clap(short, long, value_parser)]
domain: Option<String>,
/// A target RDP server user password
#[clap(short, long, value_parser)]
password: String,
/// Specify the security protocols to use
#[clap(long, value_enum, value_parser, default_value_t = SecurityProtocol::HybridEx)]
security_protocol: SecurityProtocol,
/// The keyboard type
#[clap(long, value_enum, value_parser, default_value_t = KeyboardType::IbmEnhanced)]
keyboard_type: KeyboardType,
/// The keyboard subtype (an original equipment manufacturer-dependent value)
#[clap(long, value_parser, default_value_t = 0)]
keyboard_subtype: u32,
/// The number of function keys on the keyboard
#[clap(long, value_parser, default_value_t = 12)]
keyboard_functional_keys_count: u32,
/// The input method editor (IME) file name associated with the active input locale
#[clap(long, value_parser, default_value_t = String::from(""))]
ime_file_name: String,
/// Contains a value that uniquely identifies the client
#[clap(long, value_parser, default_value_t = String::from(""))]
dig_product_id: String,
/// Enable AVC444
#[clap(long, group = "avc")]
avc444: bool,
/// Enable H264
#[clap(long, group = "avc")]
h264: bool,
/// Enable thin client
#[clap(long)]
thin_client: bool,
/// Enable small cache
#[clap(long)]
small_cache: bool,
/// Enabled capability versions. Each bit represents enabling a capability version
/// starting from V8 to V10_7
#[clap(long, value_parser = parse_hex, default_value_t = 0)]
capabilities: u32,
/// Enables dumping the gfx stream to a file location
#[clap(long, value_parser)]
gfx_dump_file: Option<PathBuf>,
}
impl Config {
pub fn parse_args() -> Self {
let args = Args::parse();
let graphics_config = if args.avc444 || args.h264 {
Some(GraphicsConfig {
avc444: args.avc444,
h264: args.h264,
thin_client: args.thin_client,
small_cache: args.small_cache,
capabilities: args.capabilities,
})
} else {
None
};
let input = InputConfig {
credentials: AuthIdentity {
username: args.username,
password: args.password.into(),
domain: args.domain,
},
security_protocol: SecurityProtocol::parse(args.security_protocol),
keyboard_type: KeyboardType::parse(args.keyboard_type),
keyboard_subtype: args.keyboard_subtype,
keyboard_functional_keys_count: args.keyboard_functional_keys_count,
ime_file_name: args.ime_file_name,
dig_product_id: args.dig_product_id,
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
global_channel_name: GLOBAL_CHANNEL_NAME.to_string(),
user_channel_name: USER_CHANNEL_NAME.to_string(),
graphics_config,
};
Self {
log_file: args.log_file,
addr: args.addr,
input,
gfx_dump_file: args.gfx_dump_file,
}
}
}

View file

@ -0,0 +1,147 @@
use std::fmt::Debug;
use std::path::PathBuf;
use std::sync::mpsc::{Receiver, SyncSender};
use std::sync::{self, Arc};
use glutin::dpi::PhysicalPosition;
use glutin::event::{Event, WindowEvent};
use glutin::event_loop::ControlFlow;
use ironrdp::pdu::dvc::gfx::ServerPdu;
use ironrdp::session::{ErasedWriter, GfxHandler};
use ironrdp_glutin_renderer::renderer::Renderer;
use tokio::sync::Mutex;
use self::input::{handle_input_events, translate_input_event};
use crate::RdpError;
mod input;
#[derive(Debug, Clone)]
pub struct MessagePassingGfxHandler {
channel: SyncSender<ServerPdu>,
}
impl MessagePassingGfxHandler {
pub fn new(channel: SyncSender<ServerPdu>) -> Self {
Self { channel }
}
}
impl GfxHandler for MessagePassingGfxHandler {
fn on_message(&self, message: ServerPdu) -> Result<Option<ironrdp::pdu::dvc::gfx::ClientPdu>, RdpError> {
self.channel.send(message).map_err(|e| RdpError::Send(e.to_string()))?;
Ok(None)
}
}
pub struct UiContext {
window: glutin::ContextWrapper<glutin::NotCurrent, glutin::window::Window>,
event_loop: glutin::event_loop::EventLoop<UserEvent>,
}
impl UiContext {
fn create_ui_context(
width: i32,
height: i32,
) -> (
glutin::ContextWrapper<glutin::NotCurrent, glutin::window::Window>,
glutin::event_loop::EventLoop<UserEvent>,
) {
let event_loop = glutin::event_loop::EventLoopBuilder::with_user_event().build();
let window_builder = glutin::window::WindowBuilder::new()
.with_title("IronRDP Client")
.with_resizable(false)
.with_inner_size(glutin::dpi::PhysicalSize::new(width, height));
let window = glutin::ContextBuilder::new()
.with_vsync(true)
.build_windowed(window_builder, &event_loop)
.unwrap();
(window, event_loop)
}
pub fn new(width: u16, height: u16) -> Self {
let (window, event_loop) = UiContext::create_ui_context(width as i32, height as i32);
UiContext { window, event_loop }
}
}
#[derive(Debug)]
pub enum UserEvent {}
/// Launches the GUI. Because of the way UI programming works the event loop has to be run from main thread
pub fn launch_gui(
context: UiContext,
gfx_dump_file: Option<PathBuf>,
graphic_receiver: Receiver<ServerPdu>,
stream: Arc<Mutex<ErasedWriter>>,
) -> Result<(), RdpError> {
let (sender, receiver) = sync::mpsc::channel();
tokio::spawn(async move { handle_input_events(receiver, stream).await });
let renderer = Renderer::new(context.window, graphic_receiver, gfx_dump_file);
// We handle events differently between targets
let mut last_position: Option<PhysicalPosition<f64>> = None;
context.event_loop.run(move |main_event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match &main_event {
Event::LoopDestroyed => {}
Event::RedrawRequested(_) => {
let res = renderer.repaint();
if res.is_err() {
error!("Repaint send error: {:?}", res);
}
}
Event::WindowEvent { ref event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::Resized(..) => {
// let width = new_size.width;
// let height = new_size.height;
// let scale_factor = window.window().scale_factor();
// info!("Scale factor: {} Window size: {:?}x {:?}", scale_factor, width, height);
// let layout_pdu = display::ClientPdu::DisplayControlMonitorLayout(MonitorLayoutPdu {
// monitors: vec![Monitor {
// left: 0,
// top: 0,
// width: width,
// height: height,
// flags: MonitorFlags::PRIMARY,
// physical_width: 0,
// physical_height: 0,
// orientation: Orientation::Landscape,
// desktop_scale_factor: 0,
// device_scale_factor: 0,
// }],
// });
// let mut data_buffer = Vec::new();
// layout_pdu.to_buffer(&mut data_buffer)?;
// if let (Some(x224_processor), Some(stream)) = (x224_processor.as_ref(), stream.as_mut()) {
// let mut x224_processor = x224_processor.lock()?;
// // Ignorable error in case of display channel is not connected
// let result =
// x224_processor.send_dynamic(&mut *stream, x224::RDP8_DISPLAY_PIPELINE_NAME, data_buffer);
// if result.is_err() {
// error!("Monitor layout {:?}", result);
// } else {
// error!("Monitor layout success");
// }
// }
}
WindowEvent::KeyboardInput { .. }
| WindowEvent::MouseInput { .. }
| WindowEvent::CursorMoved { .. } => {
if let Some(event) = translate_input_event(main_event, &mut last_position) {
let result = sender.send(event);
if result.is_err() {
error!("Send of event failed: {:?}", result);
}
}
}
_ => {}
},
_ => (),
}
})
}

View file

@ -0,0 +1,92 @@
use std::sync::mpsc::Receiver;
use std::sync::Arc;
use futures_util::AsyncWriteExt;
use glutin::dpi::PhysicalPosition;
use glutin::event::{ElementState, Event, WindowEvent};
use ironrdp::pdu::input::fast_path::{FastPathInput, FastPathInputEvent, KeyboardFlags};
use ironrdp::pdu::input::mouse::PointerFlags;
use ironrdp::pdu::input::MousePdu;
use ironrdp::session::ErasedWriter;
use tokio::sync::Mutex;
use super::UserEvent;
pub async fn handle_input_events(receiver: Receiver<FastPathInputEvent>, event_stream: Arc<Mutex<ErasedWriter>>) {
loop {
let mut fastpath_events = Vec::new();
let event = receiver.recv().unwrap();
fastpath_events.push(event);
while let Ok(event) = receiver.try_recv() {
fastpath_events.push(event);
}
let mut data: Vec<u8> = Vec::new();
let input_pdu = FastPathInput(fastpath_events);
input_pdu.to_buffer(&mut data).unwrap();
let mut event_stream = event_stream.lock().await;
let _result = event_stream.write_all(data.as_slice()).await;
let _result = event_stream.flush().await;
}
}
pub fn translate_input_event(
event: Event<UserEvent>,
last_position: &mut Option<PhysicalPosition<f64>>,
) -> Option<FastPathInputEvent> {
match event {
Event::WindowEvent { ref event, .. } => match event {
WindowEvent::KeyboardInput {
device_id: _,
input,
is_synthetic: _,
} => {
let scan_code = input.scancode & 0xff;
let flags = match input.state {
ElementState::Pressed => KeyboardFlags::empty(),
ElementState::Released => KeyboardFlags::RELEASE,
};
Some(FastPathInputEvent::KeyboardEvent(flags, scan_code as u8))
}
WindowEvent::MouseInput { state, button, .. } => {
if let Some(position) = last_position.as_ref() {
let button = match button {
glutin::event::MouseButton::Left => PointerFlags::LEFT_BUTTON,
glutin::event::MouseButton::Right => PointerFlags::RIGHT_BUTTON,
glutin::event::MouseButton::Middle => PointerFlags::MIDDLE_BUTTON_OR_WHEEL,
glutin::event::MouseButton::Other(_) => PointerFlags::empty(),
};
let button_events = button
| match state {
ElementState::Pressed => PointerFlags::DOWN,
ElementState::Released => PointerFlags::empty(),
};
let pdu = MousePdu {
x_position: position.x as u16,
y_position: position.y as u16,
flags: button_events,
number_of_wheel_rotation_units: 0,
};
Some(FastPathInputEvent::MouseEvent(pdu))
} else {
None
}
}
WindowEvent::CursorMoved { position, .. } => {
*last_position = Some(*position);
let pdu = MousePdu {
x_position: position.x as u16,
y_position: position.y as u16,
flags: PointerFlags::MOVE,
number_of_wheel_rotation_units: 0,
};
Some(FastPathInputEvent::MouseEvent(pdu))
}
_ => None,
},
_ => None,
}
}

View file

@ -0,0 +1,258 @@
mod config;
use std::sync::mpsc::sync_channel;
use std::sync::Arc;
use std::{io, process};
use anyhow::Context as _;
use futures_util::io::AsyncWriteExt as _;
use gui::MessagePassingGfxHandler;
use ironrdp::graphics::image_processing::PixelFormat;
use ironrdp::pdu::dvc::gfx::ServerPdu;
use ironrdp::session::connection_sequence::{process_connection_sequence, UpgradedStream};
use ironrdp::session::image::DecodedImage;
use ironrdp::session::{ActiveStageOutput, ActiveStageProcessor, ErasedWriter, RdpError};
use sspi::network_client::reqwest_network_client::RequestClientFactory;
use tokio::io::AsyncWriteExt as _;
use tokio::net::TcpStream;
use tokio::sync::Mutex;
use tokio_util::compat::TokioAsyncReadCompatExt as _;
use x509_parser::prelude::{FromDer as _, X509Certificate};
use crate::config::Config;
#[cfg(feature = "rustls")]
type TlsStream = tokio_util::compat::Compat<tokio_rustls::client::TlsStream<TcpStream>>;
#[cfg(all(feature = "native-tls", not(feature = "rustls")))]
type TlsStream = tokio_util::compat::Compat<async_native_tls::TlsStream<TcpStream>>;
mod gui;
#[cfg(feature = "rustls")]
mod danger {
use std::time::SystemTime;
use tokio_rustls::rustls::client::ServerCertVerified;
use tokio_rustls::rustls::{Certificate, Error, ServerName};
pub struct NoCertificateVerification;
impl tokio_rustls::rustls::client::ServerCertVerifier for NoCertificateVerification {
fn verify_server_cert(
&self,
_end_entity: &Certificate,
_intermediates: &[Certificate],
_server_name: &ServerName,
_scts: &mut dyn Iterator<Item = &[u8]>,
_ocsp_response: &[u8],
_now: SystemTime,
) -> Result<ServerCertVerified, Error> {
Ok(tokio_rustls::rustls::client::ServerCertVerified::assertion())
}
}
}
#[tokio::main]
async fn main() {
let config = Config::parse_args();
setup_logging(config.log_file.as_str()).expect("failed to initialize logging");
let exit_code = match run(config).await {
Ok(_) => {
println!("RDP successfully finished");
exitcode::OK
}
Err(RdpError::Io(e)) if e.kind() == io::ErrorKind::UnexpectedEof => {
error!("{}", e);
println!("The server has terminated the RDP session");
exitcode::NOHOST
}
Err(ref e) => {
error!("{}", e);
println!("RDP failed because of {e}");
match e {
RdpError::Io(_) => exitcode::IOERR,
RdpError::Connection(_) => exitcode::NOHOST,
_ => exitcode::PROTOCOL,
}
}
};
std::process::exit(exit_code);
}
fn setup_logging(log_file: &str) -> anyhow::Result<()> {
use std::fs::OpenOptions;
use tracing::metadata::LevelFilter;
use tracing_subscriber::prelude::*;
use tracing_subscriber::EnvFilter;
let file = OpenOptions::new()
.create(true)
.append(true)
.open(log_file)
.with_context(|| format!("Couldnt open {log_file}"))?;
let fmt_layer = tracing_subscriber::fmt::layer()
.compact()
.with_ansi(false)
.with_writer(file);
let env_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::WARN.into())
.with_env_var("IRONRDP_LOG_LEVEL")
.from_env_lossy();
let reg = tracing_subscriber::registry().with(fmt_layer).with(env_filter);
tracing::subscriber::set_global_default(reg).context("Failed to set tracing global subscriber")?;
Ok(())
}
async fn run(config: Config) -> Result<(), RdpError> {
let addr = ironrdp::session::connection_sequence::Address::lookup_addr(&config.addr)?;
let stream = TcpStream::connect(addr.sock).await.map_err(RdpError::Connection)?;
let (connection_sequence_result, reader, writer) = process_connection_sequence(
stream.compat(),
&addr,
&config.input,
establish_tls,
Box::new(RequestClientFactory),
)
.await?;
let writer = Arc::new(Mutex::new(writer));
let image = DecodedImage::new(
PixelFormat::RgbA32,
connection_sequence_result.desktop_size.width,
connection_sequence_result.desktop_size.height,
);
launch_client(config, connection_sequence_result, image, reader, writer).await
}
async fn launch_client(
config: Config,
connection_sequence_result: ironrdp::session::connection_sequence::ConnectionSequenceResult,
image: DecodedImage,
reader: ironrdp::session::FramedReader,
writer: Arc<Mutex<ErasedWriter>>,
) -> Result<(), RdpError> {
let (sender, receiver) = sync_channel::<ServerPdu>(1);
let handler = MessagePassingGfxHandler::new(sender);
let active_stage = ActiveStageProcessor::new(
config.input.clone(),
Some(Box::new(handler)),
connection_sequence_result,
);
let gui = gui::UiContext::new(config.input.width, config.input.height);
let active_stage_writer = writer.clone();
let active_stage_handle = tokio::spawn(async move {
match process_active_stage(reader, active_stage, image, active_stage_writer).await {
Ok(()) => Ok(()),
Err(error) => {
error!(?error, "Active stage failed");
process::exit(-1);
}
}
});
gui::launch_gui(gui, config.gfx_dump_file, receiver, writer.clone())?;
active_stage_handle.await.map_err(|e| RdpError::Io(e.into()))?
}
async fn process_active_stage(
mut reader: ironrdp::session::FramedReader,
mut active_stage: ActiveStageProcessor,
mut image: DecodedImage,
writer: Arc<Mutex<ErasedWriter>>,
) -> Result<(), RdpError> {
'outer: loop {
let frame = reader.read_frame().await?.ok_or(RdpError::AccessDenied)?.freeze();
let outputs = active_stage.process(&mut image, frame)?;
for out in outputs {
match out {
ActiveStageOutput::ResponseFrame(frame) => {
let mut writer = writer.lock().await;
writer.write_all(&frame).await?
}
ActiveStageOutput::GraphicsUpdate(_region) => {}
ActiveStageOutput::Terminate => break 'outer,
}
}
}
Ok(())
}
// TODO: this can be refactored into a separate `ironrdp-tls` crate (all native clients will do the same TLS dance)
async fn establish_tls(stream: tokio_util::compat::Compat<TcpStream>) -> Result<UpgradedStream<TlsStream>, RdpError> {
let stream = stream.into_inner();
#[cfg(all(feature = "native-tls", not(feature = "rustls")))]
let mut tls_stream = {
let connector = async_native_tls::TlsConnector::new()
.danger_accept_invalid_certs(true)
.use_sni(false);
// domain is an empty string because client accepts IP address in the cli
match connector.connect("", stream).await {
Ok(tls) => tls,
Err(err) => return Err(RdpError::TlsHandshake(err)),
}
};
#[cfg(feature = "rustls")]
let mut tls_stream = {
let mut client_config = tokio_rustls::rustls::client::ClientConfig::builder()
.with_safe_defaults()
.with_custom_certificate_verifier(std::sync::Arc::new(danger::NoCertificateVerification))
.with_no_client_auth();
// This adds support for the SSLKEYLOGFILE env variable (https://wiki.wireshark.org/TLS#using-the-pre-master-secret)
client_config.key_log = std::sync::Arc::new(tokio_rustls::rustls::KeyLogFile::new());
let rc_config = std::sync::Arc::new(client_config);
let example_com = "stub_string".try_into().unwrap();
let connector = tokio_rustls::TlsConnector::from(rc_config);
connector.connect(example_com, stream).await?
};
tls_stream.flush().await?;
#[cfg(all(feature = "native-tls", not(feature = "rustls")))]
let server_public_key = {
let cert = tls_stream
.peer_certificate()
.map_err(RdpError::TlsConnector)?
.ok_or(RdpError::MissingPeerCertificate)?;
get_tls_peer_pubkey(cert.to_der().map_err(RdpError::DerEncode)?)?
};
#[cfg(feature = "rustls")]
let server_public_key = {
let cert = tls_stream
.get_ref()
.1
.peer_certificates()
.ok_or(RdpError::MissingPeerCertificate)?[0]
.as_ref();
get_tls_peer_pubkey(cert.to_vec())?
};
Ok(UpgradedStream {
stream: tls_stream.compat(),
server_public_key,
})
}
pub fn get_tls_peer_pubkey(cert: Vec<u8>) -> io::Result<Vec<u8>> {
let res = X509Certificate::from_der(&cert[..])
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid der certificate."))?;
let public_key = res.1.tbs_certificate.subject_pki.subject_public_key;
Ok(public_key.data.to_vec())
}

View file

@ -0,0 +1,94 @@
[package]
name = "ironrdp-client"
version = "0.1.0"
readme = "README.md"
description = "Portable RDP client without GPU acceleration"
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
authors.workspace = true
keywords.workspace = true
categories.workspace = true
default-run = "ironrdp-client"
# Not publishing for now.
publish = false
[lib]
doctest = false
test = false
[[bin]]
name = "ironrdp-client"
test = false
[features]
default = ["rustls"]
rustls = ["ironrdp-tls/rustls", "tokio-tungstenite/rustls-tls-native-roots", "ironrdp-mstsgu/rustls"]
native-tls = ["ironrdp-tls/native-tls", "tokio-tungstenite/native-tls", "ironrdp-mstsgu/native-tls"]
qoi = ["ironrdp/qoi"]
qoiz = ["ironrdp/qoiz"]
[dependencies]
# Protocols
ironrdp = { path = "../ironrdp", version = "0.14", features = [
"session",
"input",
"graphics",
"dvc",
"svc",
"rdpdr",
"rdpsnd",
"cliprdr",
"displaycontrol",
"connector",
] }
ironrdp-core = { path = "../ironrdp-core", version = "0.1", features = ["alloc"] }
ironrdp-cliprdr-native = { path = "../ironrdp-cliprdr-native", version = "0.5" }
ironrdp-rdpsnd-native = { path = "../ironrdp-rdpsnd-native", version = "0.4" }
ironrdp-tls = { path = "../ironrdp-tls", version = "0.2" }
ironrdp-mstsgu = { path = "../ironrdp-mstsgu" }
ironrdp-tokio = { path = "../ironrdp-tokio", version = "0.8", features = ["reqwest"] }
ironrdp-rdcleanpath.path = "../ironrdp-rdcleanpath"
ironrdp-dvc-pipe-proxy.path = "../ironrdp-dvc-pipe-proxy"
ironrdp-propertyset.path = "../ironrdp-propertyset"
ironrdp-rdpfile.path = "../ironrdp-rdpfile"
ironrdp-cfg.path = "../ironrdp-cfg"
# Windowing and rendering
winit = { version = "0.30", features = ["rwh_06"] }
softbuffer = "0.4"
# CLI
clap = { version = "4.5", features = ["derive", "cargo"] }
proc-exit = "2"
inquire = "0.9"
# Logging
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Async, futures
tokio = { version = "1", features = ["full"] }
tokio-util = { version = "0.7" }
tokio-tungstenite = "0.28"
transport = { git = "https://github.com/Devolutions/devolutions-gateway", rev = "06e91dfe82751a6502eaf74b6a99663f06f0236d" }
futures-util = { version = "0.3", features = ["sink"] }
# Utils
whoami = "1.6"
anyhow = "1"
smallvec = "1.15"
tap = "1"
semver = "1"
raw-window-handle = "0.6"
uuid = { version = "1.19" }
x509-cert = { version = "0.2", default-features = false, features = ["std"] }
url = "2"
[target.'cfg(windows)'.dependencies]
windows = { version = "0.62", features = ["Win32_Foundation"] }
[lints]
workspace = true

View file

@ -0,0 +1,48 @@
# IronRDP client
Portable RDP client without GPU acceleration.
This is a a full-fledged RDP client based on IronRDP crates suite, and implemented using
non-blocking, asynchronous I/O. Portability is achieved by using softbuffer for rendering
and winit for windowing.
## Sample usage
```shell
ironrdp-client <HOSTNAME> --username <USERNAME> --password <PASSWORD>
```
## Configuring log filter directives
The `IRONRDP_LOG` environment variable is used to set the log filter directives.
```shell
IRONRDP_LOG="info,ironrdp_connector=trace" ironrdp-client <HOSTNAME> --username <USERNAME> --password <PASSWORD>
```
See [`tracing-subscriber`s documentation][tracing-doc] for more details.
[tracing-doc]: https://docs.rs/tracing-subscriber/0.3.17/tracing_subscriber/filter/struct.EnvFilter.html#directives
## Support for `SSLKEYLOGFILE`
This client supports reading the `SSLKEYLOGFILE` environment variable.
When set, the TLS encryption secrets for the session will be dumped to the file specified
by the environment variable.
This file can be read by Wireshark so that in can decrypt the packets.
### Example
```shell
SSLKEYLOGFILE=/tmp/tls-secrets ironrdp-client <HOSTNAME> --username <USERNAME> --password <PASSWORD>
```
### Usage in Wireshark
See this [awakecoding's repository][awakecoding-repository] explaining how to use the file in wireshark.
This crate is part of the [IronRDP] project.
[IronRDP]: https://github.com/Devolutions/IronRDP
[awakecoding-repository]: https://github.com/awakecoding/wireshark-rdp#sslkeylogfile

View file

@ -0,0 +1,414 @@
#![allow(clippy::print_stderr, clippy::print_stdout)] // allowed in this module only
use core::num::NonZeroU32;
use core::time::Duration;
use std::sync::Arc;
use std::time::Instant;
use anyhow::Context as _;
use raw_window_handle::{DisplayHandle, HasDisplayHandle as _};
use tokio::sync::mpsc;
use tracing::{debug, error, trace, warn};
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalPosition, PhysicalSize};
use winit::event::{self, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::platform::scancode::PhysicalKeyExtScancode as _;
use winit::window::{CursorIcon, CustomCursor, Window, WindowAttributes};
use crate::rdp::{RdpInputEvent, RdpOutputEvent};
type WindowSurface = (Arc<Window>, softbuffer::Surface<DisplayHandle<'static>, Arc<Window>>);
pub struct App {
input_event_sender: mpsc::UnboundedSender<RdpInputEvent>,
context: softbuffer::Context<DisplayHandle<'static>>,
window: Option<WindowSurface>,
buffer: Vec<u32>,
buffer_size: (u16, u16),
input_database: ironrdp::input::Database,
last_size: Option<PhysicalSize<u32>>,
resize_timeout: Option<Instant>,
}
impl App {
pub fn new(
event_loop: &EventLoop<RdpOutputEvent>,
input_event_sender: &mpsc::UnboundedSender<RdpInputEvent>,
) -> anyhow::Result<Self> {
// SAFETY: We drop the softbuffer context right before the event loop is stopped, thus making this safe.
// FIXME: This is not a sufficient proof and the API is actually unsound as-is.
let display_handle = unsafe {
core::mem::transmute::<DisplayHandle<'_>, DisplayHandle<'static>>(
event_loop.display_handle().context("get display handle")?,
)
};
let context = softbuffer::Context::new(display_handle)
.map_err(|e| anyhow::anyhow!("unable to initialize softbuffer context: {e}"))?;
let input_database = ironrdp::input::Database::new();
Ok(Self {
input_event_sender: input_event_sender.clone(),
context,
window: None,
buffer: Vec::new(),
buffer_size: (0, 0),
input_database,
last_size: None,
resize_timeout: None,
})
}
fn send_resize_event(&mut self) {
let Some(size) = self.last_size.take() else {
return;
};
let Some((window, _)) = self.window.as_mut() else {
return;
};
#[expect(clippy::as_conversions, reason = "casting f64 to u32")]
let scale_factor = (window.scale_factor() * 100.0) as u32;
let width = u16::try_from(size.width).expect("reasonable width");
let height = u16::try_from(size.height).expect("reasonable height");
let _ = self.input_event_sender.send(RdpInputEvent::Resize {
width,
height,
scale_factor,
// TODO: it should be possible to get the physical size here, however winit doesn't make it straightforward.
// FreeRDP does it based on DPI reading grabbed via [`SDL_GetDisplayDPI`](https://wiki.libsdl.org/SDL2/SDL_GetDisplayDPI):
// https://github.com/FreeRDP/FreeRDP/blob/ba8cf8cf2158018fb7abbedb51ab245f369be813/client/SDL/sdl_monitor.cpp#L250-L262
// See also: https://github.com/rust-windowing/winit/issues/826
physical_size: None,
});
}
fn draw(&mut self) {
if self.buffer.is_empty() {
return;
}
let Some((_, surface)) = self.window.as_mut() else {
return;
};
let mut sb_buffer = surface.buffer_mut().expect("surface buffer");
sb_buffer.copy_from_slice(self.buffer.as_slice());
sb_buffer.present().expect("buffer present");
}
}
impl ApplicationHandler<RdpOutputEvent> for App {
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
if let Some(timeout) = self.resize_timeout {
if let Some(timeout) = timeout.checked_duration_since(Instant::now()) {
event_loop.set_control_flow(ControlFlow::wait_duration(timeout));
} else {
self.send_resize_event();
self.resize_timeout = None;
event_loop.set_control_flow(ControlFlow::Wait);
}
}
}
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let window_attributes = WindowAttributes::default().with_title("IronRDP");
match event_loop.create_window(window_attributes) {
Ok(window) => {
let window = Arc::new(window);
let surface = softbuffer::Surface::new(&self.context, Arc::clone(&window)).expect("surface");
self.window = Some((window, surface));
}
Err(error) => {
error!(%error, "Failed to create window");
event_loop.exit();
}
}
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: winit::window::WindowId, event: WindowEvent) {
let Some((window, _)) = self.window.as_mut() else {
return;
};
if window_id != window.id() {
return;
}
match event {
WindowEvent::Resized(size) => {
self.last_size = Some(size);
self.resize_timeout = Some(Instant::now() + Duration::from_secs(1));
}
WindowEvent::CloseRequested => {
if self.input_event_sender.send(RdpInputEvent::Close).is_err() {
error!("Failed to send graceful shutdown event, closing the window");
event_loop.exit();
}
}
WindowEvent::DroppedFile(_) => {
// TODO(#110): File upload
}
// WindowEvent::ReceivedCharacter(_) => {
// Sadly, we can't use this winit event to send RDP unicode events because
// of the several reasons:
// 1. `ReceivedCharacter` event doesn't provide a way to distinguish between
// key press and key release, therefore the only way to use it is to send
// a key press + release events sequentially, which will not allow to
// handle long press and key repeat events.
// 2. This event do not fire for non-printable keys (e.g. Control, Alt, etc.)
// 3. This event fies BEFORE `KeyboardInput` event, so we can't make a
// reasonable workaround for `1` and `2` by collecting physical key press
// information first via `KeyboardInput` before processing `ReceivedCharacter`.
//
// However, all of these issues can be solved by updating `winit` to the
// newer version.
//
// TODO(#376): Update winit
// TODO(#376): Implement unicode input in native client
// }
WindowEvent::KeyboardInput { event, .. } => {
if let Some(scancode) = event.physical_key.to_scancode() {
let scancode = match u16::try_from(scancode) {
Ok(scancode) => scancode,
Err(_) => {
warn!("Unsupported scancode: `{scancode:#X}`; ignored");
return;
}
};
let scancode = ironrdp::input::Scancode::from_u16(scancode);
let operation = match event.state {
event::ElementState::Pressed => ironrdp::input::Operation::KeyPressed(scancode),
event::ElementState::Released => ironrdp::input::Operation::KeyReleased(scancode),
};
let input_events = self.input_database.apply(core::iter::once(operation));
send_fast_path_events(&self.input_event_sender, input_events);
}
}
WindowEvent::ModifiersChanged(modifiers) => {
const SHIFT_LEFT: ironrdp::input::Scancode = ironrdp::input::Scancode::from_u8(false, 0x2A);
const CONTROL_LEFT: ironrdp::input::Scancode = ironrdp::input::Scancode::from_u8(false, 0x1D);
const ALT_LEFT: ironrdp::input::Scancode = ironrdp::input::Scancode::from_u8(false, 0x38);
const LOGO_LEFT: ironrdp::input::Scancode = ironrdp::input::Scancode::from_u8(true, 0x5B);
let mut operations = smallvec::SmallVec::<[ironrdp::input::Operation; 4]>::new();
let mut add_operation = |pressed: bool, scancode: ironrdp::input::Scancode| {
let operation = if pressed {
ironrdp::input::Operation::KeyPressed(scancode)
} else {
ironrdp::input::Operation::KeyReleased(scancode)
};
operations.push(operation);
};
// NOTE: https://docs.rs/winit/0.30.12/src/winit/keyboard.rs.html#1737-1744
//
// We cant use state.lshift_state(), state.lcontrol_state(), etc, because on some platforms such as
// Linux, the modifiers change is hidden.
//
// > The exact modifier key is not used to represent modifiers state in the
// > first place due to a fact that modifiers state could be changed without any
// > key being pressed and on some platforms like Wayland/X11 which key resulted
// > in modifiers change is hidden, also, not that it really matters.
add_operation(modifiers.state().shift_key(), SHIFT_LEFT);
add_operation(modifiers.state().control_key(), CONTROL_LEFT);
add_operation(modifiers.state().alt_key(), ALT_LEFT);
add_operation(modifiers.state().super_key(), LOGO_LEFT);
let input_events = self.input_database.apply(operations);
send_fast_path_events(&self.input_event_sender, input_events);
}
WindowEvent::CursorMoved { position, .. } => {
let win_size = window.inner_size();
#[expect(clippy::as_conversions, reason = "casting f64 to u16")]
let x = (position.x / f64::from(win_size.width) * f64::from(self.buffer_size.0)) as u16;
#[expect(clippy::as_conversions, reason = "casting f64 to u16")]
let y = (position.y / f64::from(win_size.height) * f64::from(self.buffer_size.1)) as u16;
let operation = ironrdp::input::Operation::MouseMove(ironrdp::input::MousePosition { x, y });
let input_events = self.input_database.apply(core::iter::once(operation));
send_fast_path_events(&self.input_event_sender, input_events);
}
WindowEvent::MouseWheel { delta, .. } => {
let mut operations = smallvec::SmallVec::<[ironrdp::input::Operation; 2]>::new();
match delta {
event::MouseScrollDelta::LineDelta(delta_x, delta_y) => {
if delta_x.abs() > 0.001 {
operations.push(ironrdp::input::Operation::WheelRotations(
ironrdp::input::WheelRotations {
is_vertical: false,
#[expect(clippy::as_conversions, reason = "casting f32 to i16")]
rotation_units: (delta_x * 100.) as i16,
},
));
}
if delta_y.abs() > 0.001 {
operations.push(ironrdp::input::Operation::WheelRotations(
ironrdp::input::WheelRotations {
is_vertical: true,
#[expect(clippy::as_conversions, reason = "casting f32 to i16")]
rotation_units: (delta_y * 100.) as i16,
},
));
}
}
event::MouseScrollDelta::PixelDelta(delta) => {
if delta.x.abs() > 0.001 {
operations.push(ironrdp::input::Operation::WheelRotations(
ironrdp::input::WheelRotations {
is_vertical: false,
#[expect(clippy::as_conversions, reason = "casting f64 to i16")]
rotation_units: delta.x as i16,
},
));
}
if delta.y.abs() > 0.001 {
operations.push(ironrdp::input::Operation::WheelRotations(
ironrdp::input::WheelRotations {
is_vertical: true,
#[expect(clippy::as_conversions, reason = "casting f64 to i16")]
rotation_units: delta.y as i16,
},
));
}
}
};
let input_events = self.input_database.apply(operations);
send_fast_path_events(&self.input_event_sender, input_events);
}
WindowEvent::MouseInput { state, button, .. } => {
let mouse_button = match button {
event::MouseButton::Left => ironrdp::input::MouseButton::Left,
event::MouseButton::Right => ironrdp::input::MouseButton::Right,
event::MouseButton::Middle => ironrdp::input::MouseButton::Middle,
event::MouseButton::Back => ironrdp::input::MouseButton::X1,
event::MouseButton::Forward => ironrdp::input::MouseButton::X2,
event::MouseButton::Other(native_button) => {
if let Some(button) = ironrdp::input::MouseButton::from_native_button(native_button) {
button
} else {
return;
}
}
};
let operation = match state {
event::ElementState::Pressed => ironrdp::input::Operation::MouseButtonPressed(mouse_button),
event::ElementState::Released => ironrdp::input::Operation::MouseButtonReleased(mouse_button),
};
let input_events = self.input_database.apply(core::iter::once(operation));
send_fast_path_events(&self.input_event_sender, input_events);
}
WindowEvent::RedrawRequested => {
self.draw();
}
WindowEvent::ActivationTokenDone { .. }
| WindowEvent::Moved(_)
| WindowEvent::Destroyed
| WindowEvent::HoveredFile(_)
| WindowEvent::HoveredFileCancelled
| WindowEvent::Focused(_)
| WindowEvent::Ime(_)
| WindowEvent::CursorEntered { .. }
| WindowEvent::CursorLeft { .. }
| WindowEvent::PinchGesture { .. }
| WindowEvent::PanGesture { .. }
| WindowEvent::DoubleTapGesture { .. }
| WindowEvent::RotationGesture { .. }
| WindowEvent::TouchpadPressure { .. }
| WindowEvent::AxisMotion { .. }
| WindowEvent::Touch(_)
| WindowEvent::ScaleFactorChanged { .. }
| WindowEvent::ThemeChanged(_)
| WindowEvent::Occluded(_) => {
// ignore
}
}
}
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: RdpOutputEvent) {
let Some((window, surface)) = self.window.as_mut() else {
return;
};
match event {
RdpOutputEvent::Image { buffer, width, height } => {
trace!(width = ?width, height = ?height, "Received image with size");
trace!(window_physical_size = ?window.inner_size(), "Drawing image to the window with size");
self.buffer_size = (width.get(), height.get());
self.buffer = buffer;
surface
.resize(NonZeroU32::from(width), NonZeroU32::from(height))
.expect("surface resize");
window.request_redraw();
}
RdpOutputEvent::ConnectionFailure(error) => {
error!(?error);
eprintln!("Connection error: {}", error.report());
// TODO set proc_exit::sysexits::PROTOCOL_ERR.as_raw());
event_loop.exit();
}
RdpOutputEvent::Terminated(result) => {
let _exit_code = match result {
Ok(reason) => {
println!("Terminated gracefully: {reason}");
proc_exit::sysexits::OK
}
Err(error) => {
error!(?error);
eprintln!("Active session error: {}", error.report());
proc_exit::sysexits::PROTOCOL_ERR
}
};
// TODO set exit_code.as_raw());
event_loop.exit();
}
RdpOutputEvent::PointerHidden => {
window.set_cursor_visible(false);
}
RdpOutputEvent::PointerDefault => {
window.set_cursor(CursorIcon::default());
window.set_cursor_visible(true);
}
RdpOutputEvent::PointerPosition { x, y } => {
if let Err(error) = window.set_cursor_position(LogicalPosition::new(x, y)) {
error!(?error, "Failed to set cursor position");
}
}
RdpOutputEvent::PointerBitmap(pointer) => {
debug!(width = ?pointer.width, height = ?pointer.height, "Received pointer bitmap");
match CustomCursor::from_rgba(
pointer.bitmap_data.clone(),
pointer.width,
pointer.height,
pointer.hotspot_x,
pointer.hotspot_y,
) {
Ok(cursor) => window.set_cursor(event_loop.create_custom_cursor(cursor)),
Err(error) => error!(?error, "Failed to set cursor bitmap"),
}
window.set_cursor_visible(true);
}
}
}
}
fn send_fast_path_events(
input_event_sender: &mpsc::UnboundedSender<RdpInputEvent>,
input_events: smallvec::SmallVec<[ironrdp::pdu::input::fast_path::FastPathInputEvent; 2]>,
) {
if !input_events.is_empty() {
let _ = input_event_sender.send(RdpInputEvent::FastPath(input_events));
}
}

View file

@ -0,0 +1,25 @@
use ironrdp::cliprdr::backend::{ClipboardMessage, ClipboardMessageProxy};
use tokio::sync::mpsc;
use tracing::error;
use crate::rdp::RdpInputEvent;
/// Shim for sending and receiving CLIPRDR events as `RdpInputEvent`
#[derive(Clone, Debug)]
pub struct ClientClipboardMessageProxy {
tx: mpsc::UnboundedSender<RdpInputEvent>,
}
impl ClientClipboardMessageProxy {
pub fn new(tx: mpsc::UnboundedSender<RdpInputEvent>) -> Self {
Self { tx }
}
}
impl ClipboardMessageProxy for ClientClipboardMessageProxy {
fn send_clipboard_message(&self, message: ClipboardMessage) {
if self.tx.send(RdpInputEvent::Clipboard(message)).is_err() {
error!("Failed to send os clipboard message, receiver is closed");
}
}
}

View file

@ -0,0 +1,483 @@
#![allow(clippy::print_stdout)]
use core::num::ParseIntError;
use core::str::FromStr;
use std::path::PathBuf;
use anyhow::Context as _;
use clap::clap_derive::ValueEnum;
use clap::Parser;
use ironrdp::connector::{self, Credentials};
use ironrdp::pdu::rdp::capability_sets::{client_codecs_capabilities, MajorPlatformType};
use ironrdp::pdu::rdp::client_info::{PerformanceFlags, TimezoneInfo};
use ironrdp_mstsgu::GwConnectTarget;
use tap::prelude::*;
use url::Url;
const DEFAULT_WIDTH: u16 = 1920;
const DEFAULT_HEIGHT: u16 = 1080;
#[derive(Clone, Debug)]
pub struct Config {
pub log_file: Option<String>,
pub gw: Option<GwConnectTarget>,
pub destination: Destination,
pub connector: connector::Config,
pub clipboard_type: ClipboardType,
pub rdcleanpath: Option<RDCleanPathConfig>,
/// DVC channel <-> named pipe proxy configuration.
///
/// Each configured proxy enables IronRDP to connect to DVC channel and create a named pipe
/// server, which will be used for proxying DVC messages to/from user-defined DVC logic
/// implemented as named pipe clients (either in the same process or in a different process).
pub dvc_pipe_proxies: Vec<DvcProxyInfo>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
pub enum ClipboardType {
Default,
Stub,
#[cfg(windows)]
Windows,
None,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum KeyboardType {
IbmPcXt,
OlivettiIco,
IbmPcAt,
IbmEnhanced,
Nokia1050,
Nokia9140,
Japanese,
}
impl KeyboardType {
fn parse(keyboard_type: KeyboardType) -> ironrdp::pdu::gcc::KeyboardType {
match keyboard_type {
KeyboardType::IbmEnhanced => ironrdp::pdu::gcc::KeyboardType::IbmEnhanced,
KeyboardType::IbmPcAt => ironrdp::pdu::gcc::KeyboardType::IbmPcAt,
KeyboardType::IbmPcXt => ironrdp::pdu::gcc::KeyboardType::IbmPcXt,
KeyboardType::OlivettiIco => ironrdp::pdu::gcc::KeyboardType::OlivettiIco,
KeyboardType::Nokia1050 => ironrdp::pdu::gcc::KeyboardType::Nokia1050,
KeyboardType::Nokia9140 => ironrdp::pdu::gcc::KeyboardType::Nokia9140,
KeyboardType::Japanese => ironrdp::pdu::gcc::KeyboardType::Japanese,
}
}
}
fn parse_hex(input: &str) -> Result<u32, ParseIntError> {
if input.starts_with("0x") {
u32::from_str_radix(input.get(2..).unwrap_or(""), 16)
} else {
input.parse::<u32>()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Destination {
name: String,
port: u16,
}
impl Destination {
pub fn new(addr: impl Into<String>) -> anyhow::Result<Self> {
const RDP_DEFAULT_PORT: u16 = 3389;
let addr = addr.into();
if let Some(addr_split) = addr.rsplit_once(':') {
if let Ok(sock_addr) = addr.parse::<core::net::SocketAddr>() {
Ok(Self {
name: sock_addr.ip().to_string(),
port: sock_addr.port(),
})
} else if addr.parse::<core::net::Ipv6Addr>().is_ok() {
Ok(Self {
name: addr,
port: RDP_DEFAULT_PORT,
})
} else {
Ok(Self {
name: addr_split.0.to_owned(),
port: addr_split.1.parse().context("invalid port")?,
})
}
} else {
Ok(Self {
name: addr,
port: RDP_DEFAULT_PORT,
})
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn port(&self) -> u16 {
self.port
}
}
impl FromStr for Destination {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
impl From<Destination> for connector::ServerName {
fn from(value: Destination) -> Self {
Self::new(value.name)
}
}
impl From<&Destination> for connector::ServerName {
fn from(value: &Destination) -> Self {
Self::new(&value.name)
}
}
#[derive(Clone, Debug)]
pub struct RDCleanPathConfig {
pub url: Url,
pub auth_token: String,
}
#[derive(Clone, Debug)]
pub struct DvcProxyInfo {
pub channel_name: String,
pub pipe_name: String,
}
impl FromStr for DvcProxyInfo {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split('=');
let channel_name = parts
.next()
.ok_or_else(|| anyhow::anyhow!("missing DVC channel name"))?
.to_owned();
let pipe_name = parts
.next()
.ok_or_else(|| anyhow::anyhow!("missing DVC proxy pipe name"))?
.to_owned();
Ok(Self {
channel_name,
pipe_name,
})
}
}
/// Devolutions IronRDP client
#[derive(Parser, Debug)]
#[clap(author = "Devolutions", about = "Devolutions-IronRDP client")]
#[clap(version, long_about = None)]
struct Args {
/// A file with IronRDP client logs
#[clap(short, long, value_parser)]
log_file: Option<String>,
#[clap(long, value_parser)]
gw_endpoint: Option<String>,
#[clap(long, value_parser)]
gw_user: Option<String>,
#[clap(long, value_parser)]
gw_pass: Option<String>,
/// An address on which the client will connect.
destination: Option<Destination>,
/// Path to a .rdp file to read the configuration from.
#[clap(long)]
rdp_file: Option<PathBuf>,
/// A target RDP server user name
#[clap(short, long)]
username: Option<String>,
/// An optional target RDP server domain name
#[clap(short, long)]
domain: Option<String>,
/// A target RDP server user password
#[clap(short, long)]
password: Option<String>,
/// Proxy URL to connect to for the RDCleanPath
#[clap(long, requires("rdcleanpath_token"))]
rdcleanpath_url: Option<Url>,
/// Authentication token to insert in the RDCleanPath packet
#[clap(long, requires("rdcleanpath_url"))]
rdcleanpath_token: Option<String>,
/// The keyboard type
#[clap(long, value_enum, default_value_t = KeyboardType::IbmEnhanced)]
keyboard_type: KeyboardType,
/// The keyboard subtype (an original equipment manufacturer-dependent value)
#[clap(long, default_value_t = 0)]
keyboard_subtype: u32,
/// The number of function keys on the keyboard
#[clap(long, default_value_t = 12)]
keyboard_functional_keys_count: u32,
/// The input method editor (IME) file name associated with the active input locale
#[clap(long, default_value_t = String::from(""))]
ime_file_name: String,
/// Contains a value that uniquely identifies the client
#[clap(long, default_value_t = String::from(""))]
dig_product_id: String,
/// Enable thin client
#[clap(long)]
thin_client: bool,
/// Enable small cache
#[clap(long)]
small_cache: bool,
/// Set required color depth. Currently only 32 and 16 bit color depths are supported
#[clap(long)]
color_depth: Option<u32>,
/// Ignore mouse pointer messages sent by the server. Increases performance when enabled, as the
/// client could skip costly software rendering of the pointer with alpha blending
#[clap(long)]
no_server_pointer: bool,
/// Enabled capability versions. Each bit represents enabling a capability version
/// starting from V8 to V10_7
#[clap(long, value_parser = parse_hex, default_value_t = 0)]
capabilities: u32,
/// Automatically logon to the server by passing the INFO_AUTOLOGON flag
///
/// This flag is ignored if CredSSP authentication is used.
/// You can use `--no-credssp` to ensure its not.
#[clap(long)]
autologon: bool,
/// Disable TLS + Graphical login (legacy authentication method)
///
/// Disabling this in order to enforce usage of CredSSP (NLA) is recommended.
#[clap(long)]
no_tls: bool,
/// Disable TLS + Network Level Authentication (NLA) using CredSSP
///
/// NLA is used to authenticates RDP clients and servers before sending credentials over the network.
/// Its not recommended to disable this.
#[clap(long, alias = "no-nla")]
no_credssp: bool,
/// The clipboard type
#[clap(long, value_enum, default_value_t = ClipboardType::Default)]
clipboard_type: ClipboardType,
/// The bitmap codecs to use (remotefx:on, ...)
#[clap(long, num_args = 1.., value_delimiter = ',')]
codecs: Vec<String>,
/// Add DVC channel named pipe proxy
///
/// The format is `<name>=<pipe>`, e.g., `ChannelName=PipeName` where `ChannelName` is the name of the channel,
/// and `PipeName` is the name of the named pipe to connect to (without OS-specific prefix).
/// `<pipe>` will automatically be prefixed with `\\.\pipe\` on Windows.
#[clap(long)]
dvc_proxy: Vec<DvcProxyInfo>,
}
impl Config {
pub fn parse_args() -> anyhow::Result<Self> {
use ironrdp_cfg::PropertySetExt as _;
let args = Args::parse();
let mut properties = ironrdp_propertyset::PropertySet::new();
if let Some(rdp_file) = args.rdp_file {
let input =
std::fs::read_to_string(&rdp_file).with_context(|| format!("failed to read {}", rdp_file.display()))?;
if let Err(errors) = ironrdp_rdpfile::load(&mut properties, &input) {
for e in errors {
#[expect(clippy::print_stderr)]
{
eprintln!("Error when reading {}: {e}", rdp_file.display())
}
}
}
}
let mut gw: Option<GwConnectTarget> = None;
if let Some(gw_addr) = args.gw_endpoint {
gw = Some(GwConnectTarget {
gw_endpoint: gw_addr,
gw_user: String::new(),
gw_pass: String::new(),
server: String::new(), // TODO: non-standard port? also dont use here?
});
}
if let Some(ref mut gw) = gw {
gw.gw_user = if let Some(gw_user) = args.gw_user {
gw_user
} else {
inquire::Text::new("Gateway username:")
.prompt()
.context("Username prompt")?
};
gw.gw_pass = if let Some(gw_pass) = args.gw_pass {
gw_pass
} else {
inquire::Password::new("Gateway password:")
.without_confirmation()
.prompt()
.context("Password prompt")?
};
};
let destination = if let Some(destination) = args.destination {
destination
} else if let Some(destination) = properties.full_address() {
if let Some(port) = properties.server_port() {
format!("{destination}:{port}").parse()
} else {
destination.parse()
}
.context("invalid destination")?
} else {
inquire::Text::new("Server address:")
.prompt()
.context("Address prompt")?
.pipe(Destination::new)?
};
if let Some(ref mut gw) = gw {
gw.server = destination.name.clone(); // TODO
}
let username = if let Some(username) = args.username {
username
} else if let Some(username) = properties.username() {
username.to_owned()
} else {
inquire::Text::new("Username:").prompt().context("Username prompt")?
};
let password = if let Some(password) = args.password {
password
} else if let Some(password) = properties.clear_text_password() {
password.to_owned()
} else {
inquire::Password::new("Password:")
.without_confirmation()
.prompt()
.context("Password prompt")?
};
let codecs: Vec<_> = args.codecs.iter().map(|s| s.as_str()).collect();
let codecs = match client_codecs_capabilities(&codecs) {
Ok(codecs) => codecs,
Err(help) => {
print!("{help}");
std::process::exit(0);
}
};
let mut bitmap = connector::BitmapConfig {
color_depth: 32,
lossy_compression: true,
codecs,
};
if let Some(color_depth) = args.color_depth {
if color_depth != 16 && color_depth != 32 {
anyhow::bail!("Invalid color depth. Only 16 and 32 bit color depths are supported.");
}
bitmap.color_depth = color_depth;
};
let clipboard_type = if args.clipboard_type == ClipboardType::Default {
#[cfg(windows)]
{
ClipboardType::Windows
}
#[cfg(not(windows))]
{
ClipboardType::None
}
} else {
args.clipboard_type
};
let connector = connector::Config {
credentials: Credentials::UsernamePassword { username, password },
domain: args.domain,
enable_tls: !args.no_tls,
enable_credssp: !args.no_credssp,
keyboard_type: KeyboardType::parse(args.keyboard_type),
keyboard_subtype: args.keyboard_subtype,
keyboard_layout: 0, // the server SHOULD use the default active input locale identifier
keyboard_functional_keys_count: args.keyboard_functional_keys_count,
ime_file_name: args.ime_file_name,
dig_product_id: args.dig_product_id,
desktop_size: connector::DesktopSize {
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
},
desktop_scale_factor: 0, // Default to 0 per FreeRDP
bitmap: Some(bitmap),
client_build: semver::Version::parse(env!("CARGO_PKG_VERSION"))
.map_or(0, |version| version.major * 100 + version.minor * 10 + version.patch)
.pipe(u32::try_from)
.context("cargo package version")?,
client_name: whoami::fallible::hostname().unwrap_or_else(|_| "ironrdp".to_owned()),
// NOTE: hardcode this value like in freerdp
// https://github.com/FreeRDP/FreeRDP/blob/4e24b966c86fdf494a782f0dfcfc43a057a2ea60/libfreerdp/core/settings.c#LL49C34-L49C70
client_dir: "C:\\Windows\\System32\\mstscax.dll".to_owned(),
platform: match whoami::platform() {
whoami::Platform::Windows => MajorPlatformType::WINDOWS,
whoami::Platform::Linux => MajorPlatformType::UNIX,
whoami::Platform::MacOS => MajorPlatformType::MACINTOSH,
whoami::Platform::Ios => MajorPlatformType::IOS,
whoami::Platform::Android => MajorPlatformType::ANDROID,
_ => MajorPlatformType::UNSPECIFIED,
},
hardware_id: None,
license_cache: None,
enable_server_pointer: !args.no_server_pointer,
autologon: args.autologon,
enable_audio_playback: true,
request_data: None,
pointer_software_rendering: false,
performance_flags: PerformanceFlags::default(),
timezone_info: TimezoneInfo::default(),
};
let rdcleanpath = args
.rdcleanpath_url
.zip(args.rdcleanpath_token)
.map(|(url, auth_token)| RDCleanPathConfig { url, auth_token });
Ok(Self {
log_file: args.log_file,
gw,
destination,
connector,
clipboard_type,
rdcleanpath,
dvc_pipe_proxies: args.dvc_proxy,
})
}
}

View file

@ -0,0 +1,17 @@
#![cfg_attr(doc, doc = include_str!("../README.md"))]
#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
#![allow(unused_crate_dependencies)] // false positives because there is both a library and a binary
// No need to be as strict as in production libraries
#![allow(clippy::arithmetic_side_effects)]
#![allow(clippy::cast_lossless)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_possible_wrap)]
#![allow(clippy::cast_sign_loss)]
pub mod app;
pub mod clipboard;
pub mod config;
pub mod rdp;
mod ws;

View file

@ -0,0 +1,122 @@
#![allow(unused_crate_dependencies)] // false positives because there is both a library and a binary
use anyhow::Context as _;
use ironrdp_client::app::App;
use ironrdp_client::config::{ClipboardType, Config};
use ironrdp_client::rdp::{DvcPipeProxyFactory, RdpClient, RdpInputEvent, RdpOutputEvent};
use tokio::runtime;
use tracing::debug;
use winit::event_loop::EventLoop;
fn main() -> anyhow::Result<()> {
let mut config = Config::parse_args().context("CLI arguments parsing")?;
setup_logging(config.log_file.as_deref()).context("unable to initialize logging")?;
debug!("Initialize App");
let event_loop = EventLoop::<RdpOutputEvent>::with_user_event().build()?;
let event_loop_proxy = event_loop.create_proxy();
let (input_event_sender, input_event_receiver) = RdpInputEvent::create_channel();
let mut app = App::new(&event_loop, &input_event_sender).context("unable to initialize App")?;
// TODO: get window size & scale factor from GUI/App
let window_size = (1024, 768);
config.connector.desktop_scale_factor = 0;
config.connector.desktop_size.width = window_size.0;
config.connector.desktop_size.height = window_size.1;
let rt = runtime::Builder::new_multi_thread()
.enable_all()
.build()
.context("unable to create tokio runtime")?;
// NOTE: we need to keep `win_clipboard` alive, otherwise it will be dropped before IronRDP
// starts and clipboard functionality will not be available.
#[cfg(windows)]
let _win_clipboard;
let cliprdr_factory = match config.clipboard_type {
ClipboardType::Stub => {
use ironrdp_cliprdr_native::StubClipboard;
let cliprdr = StubClipboard::new();
let factory = cliprdr.backend_factory();
Some(factory)
}
#[cfg(windows)]
ClipboardType::Windows => {
use ironrdp_client::clipboard::ClientClipboardMessageProxy;
use ironrdp_cliprdr_native::WinClipboard;
let cliprdr = WinClipboard::new(ClientClipboardMessageProxy::new(input_event_sender.clone()))?;
let factory = cliprdr.backend_factory();
_win_clipboard = cliprdr;
Some(factory)
}
_ => None,
};
let dvc_pipe_proxy_factory = DvcPipeProxyFactory::new(input_event_sender);
let client = RdpClient {
config,
event_loop_proxy,
input_event_receiver,
cliprdr_factory,
dvc_pipe_proxy_factory,
};
debug!("Start RDP thread");
std::thread::spawn(move || {
rt.block_on(client.run());
});
debug!("Run App");
event_loop.run_app(&mut app)?;
Ok(())
}
fn setup_logging(log_file: Option<&str>) -> anyhow::Result<()> {
use std::fs::OpenOptions;
use tracing::metadata::LevelFilter;
use tracing_subscriber::prelude::*;
use tracing_subscriber::EnvFilter;
let env_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::WARN.into())
.with_env_var("IRONRDP_LOG")
.from_env_lossy();
if let Some(log_file) = log_file {
let file = OpenOptions::new()
.create(true)
.append(true)
.open(log_file)
.with_context(|| format!("couldn't open {log_file}"))?;
let fmt_layer = tracing_subscriber::fmt::layer()
.with_ansi(false)
.with_writer(file)
.compact();
tracing_subscriber::registry()
.with(env_filter)
.with(fmt_layer)
.try_init()
.context("failed to set tracing global subscriber")?;
} else {
let fmt_layer = tracing_subscriber::fmt::layer()
.compact()
.with_file(true)
.with_line_number(true)
.with_thread_ids(true)
.with_target(false);
tracing_subscriber::registry()
.with(env_filter)
.with(fmt_layer)
.try_init()
.context("failed to set tracing global subscriber")?;
};
Ok(())
}

View file

@ -0,0 +1,691 @@
use core::num::NonZeroU16;
use std::sync::Arc;
use ironrdp::cliprdr::backend::{ClipboardMessage, CliprdrBackendFactory};
use ironrdp::connector::connection_activation::ConnectionActivationState;
use ironrdp::connector::{ConnectionResult, ConnectorResult};
use ironrdp::displaycontrol::client::DisplayControlClient;
use ironrdp::displaycontrol::pdu::MonitorLayoutEntry;
use ironrdp::graphics::image_processing::PixelFormat;
use ironrdp::graphics::pointer::DecodedPointer;
use ironrdp::pdu::input::fast_path::FastPathInputEvent;
use ironrdp::pdu::{pdu_other_err, PduResult};
use ironrdp::session::image::DecodedImage;
use ironrdp::session::{fast_path, ActiveStage, ActiveStageOutput, GracefulDisconnectReason, SessionResult};
use ironrdp::svc::SvcMessage;
use ironrdp::{cliprdr, connector, rdpdr, rdpsnd, session};
use ironrdp_core::WriteBuf;
use ironrdp_dvc_pipe_proxy::DvcNamedPipeProxy;
use ironrdp_rdpsnd_native::cpal;
use ironrdp_tokio::reqwest::ReqwestNetworkClient;
use ironrdp_tokio::{single_sequence_step_read, split_tokio_framed, FramedWrite};
use rdpdr::NoopRdpdrBackend;
use smallvec::SmallVec;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::net::TcpStream;
use tokio::sync::mpsc;
use tracing::{debug, error, info, trace, warn};
use winit::event_loop::EventLoopProxy;
use crate::config::{Config, RDCleanPathConfig};
#[derive(Debug)]
pub enum RdpOutputEvent {
Image {
buffer: Vec<u32>,
width: NonZeroU16,
height: NonZeroU16,
},
ConnectionFailure(connector::ConnectorError),
PointerDefault,
PointerHidden,
PointerPosition {
x: u16,
y: u16,
},
PointerBitmap(Arc<DecodedPointer>),
Terminated(SessionResult<GracefulDisconnectReason>),
}
#[derive(Debug)]
pub enum RdpInputEvent {
Resize {
width: u16,
height: u16,
scale_factor: u32,
/// The physical size of the display in millimeters (width, height).
physical_size: Option<(u32, u32)>,
},
FastPath(SmallVec<[FastPathInputEvent; 2]>),
Close,
Clipboard(ClipboardMessage),
SendDvcMessages {
channel_id: u32,
messages: Vec<SvcMessage>,
},
}
impl RdpInputEvent {
pub fn create_channel() -> (mpsc::UnboundedSender<Self>, mpsc::UnboundedReceiver<Self>) {
mpsc::unbounded_channel()
}
}
pub struct DvcPipeProxyFactory {
rdp_input_sender: mpsc::UnboundedSender<RdpInputEvent>,
}
impl DvcPipeProxyFactory {
pub fn new(rdp_input_sender: mpsc::UnboundedSender<RdpInputEvent>) -> Self {
Self { rdp_input_sender }
}
pub fn create(&self, channel_name: String, pipe_name: String) -> DvcNamedPipeProxy {
let rdp_input_sender = self.rdp_input_sender.clone();
DvcNamedPipeProxy::new(&channel_name, &pipe_name, move |channel_id, messages| {
rdp_input_sender
.send(RdpInputEvent::SendDvcMessages { channel_id, messages })
.map_err(|_error| pdu_other_err!("send DVC messages to the event loop",))?;
Ok(())
})
}
}
pub type WriteDvcMessageFn = Box<dyn Fn(u32, SvcMessage) -> PduResult<()> + Send + 'static>;
pub struct RdpClient {
pub config: Config,
pub event_loop_proxy: EventLoopProxy<RdpOutputEvent>,
pub input_event_receiver: mpsc::UnboundedReceiver<RdpInputEvent>,
pub cliprdr_factory: Option<Box<dyn CliprdrBackendFactory + Send>>,
pub dvc_pipe_proxy_factory: DvcPipeProxyFactory,
}
impl RdpClient {
pub async fn run(mut self) {
loop {
let (connection_result, framed) = if let Some(rdcleanpath) = self.config.rdcleanpath.as_ref() {
match connect_ws(
&self.config,
rdcleanpath,
self.cliprdr_factory.as_deref(),
&self.dvc_pipe_proxy_factory,
)
.await
{
Ok(result) => result,
Err(e) => {
let _ = self.event_loop_proxy.send_event(RdpOutputEvent::ConnectionFailure(e));
break;
}
}
} else {
match connect(
&self.config,
self.cliprdr_factory.as_deref(),
&self.dvc_pipe_proxy_factory,
)
.await
{
Ok(result) => result,
Err(e) => {
let _ = self.event_loop_proxy.send_event(RdpOutputEvent::ConnectionFailure(e));
break;
}
}
};
match active_session(
framed,
connection_result,
&self.event_loop_proxy,
&mut self.input_event_receiver,
)
.await
{
Ok(RdpControlFlow::ReconnectWithNewSize { width, height }) => {
self.config.connector.desktop_size.width = width;
self.config.connector.desktop_size.height = height;
}
Ok(RdpControlFlow::TerminatedGracefully(reason)) => {
let _ = self.event_loop_proxy.send_event(RdpOutputEvent::Terminated(Ok(reason)));
break;
}
Err(e) => {
let _ = self.event_loop_proxy.send_event(RdpOutputEvent::Terminated(Err(e)));
break;
}
}
}
}
}
enum RdpControlFlow {
ReconnectWithNewSize { width: u16, height: u16 },
TerminatedGracefully(GracefulDisconnectReason),
}
trait AsyncReadWrite: AsyncRead + AsyncWrite {}
impl<T> AsyncReadWrite for T where T: AsyncRead + AsyncWrite {}
type UpgradedFramed = ironrdp_tokio::TokioFramed<Box<dyn AsyncReadWrite + Unpin + Send + Sync>>;
async fn connect(
config: &Config,
cliprdr_factory: Option<&(dyn CliprdrBackendFactory + Send)>,
dvc_pipe_proxy_factory: &DvcPipeProxyFactory,
) -> ConnectorResult<(ConnectionResult, UpgradedFramed)> {
let dest = format!("{}:{}", config.destination.name(), config.destination.port());
let (client_addr, stream) = if let Some(ref gw_config) = config.gw {
let (gw, client_addr) = ironrdp_mstsgu::GwClient::connect(gw_config, &config.connector.client_name)
.await
.map_err(|e| connector::custom_err!("GW Connect", e))?;
(client_addr, tokio_util::either::Either::Left(gw))
} else {
let stream = TcpStream::connect(dest)
.await
.map_err(|e| connector::custom_err!("TCP connect", e))?;
let client_addr = stream
.local_addr()
.map_err(|e| connector::custom_err!("get socket local address", e))?;
(client_addr, tokio_util::either::Either::Right(stream))
};
let mut framed = ironrdp_tokio::TokioFramed::new(stream);
let mut drdynvc =
ironrdp::dvc::DrdynvcClient::new().with_dynamic_channel(DisplayControlClient::new(|_| Ok(Vec::new())));
// Instantiate all DVC proxies
for proxy in config.dvc_pipe_proxies.iter() {
let channel_name = proxy.channel_name.clone();
let pipe_name = proxy.pipe_name.clone();
trace!(%channel_name, %pipe_name, "Creating DVC proxy");
drdynvc = drdynvc.with_dynamic_channel(dvc_pipe_proxy_factory.create(channel_name, pipe_name));
}
let mut connector = connector::ClientConnector::new(config.connector.clone(), client_addr)
.with_static_channel(drdynvc)
.with_static_channel(rdpsnd::client::Rdpsnd::new(Box::new(cpal::RdpsndBackend::new())))
.with_static_channel(rdpdr::Rdpdr::new(Box::new(NoopRdpdrBackend {}), "IronRDP".to_owned()).with_smartcard(0));
if let Some(builder) = cliprdr_factory {
let backend = builder.build_cliprdr_backend();
let cliprdr = cliprdr::Cliprdr::new(backend);
connector.attach_static_channel(cliprdr);
}
let should_upgrade = ironrdp_tokio::connect_begin(&mut framed, &mut connector).await?;
debug!("TLS upgrade");
// Ensure there is no leftover
let (initial_stream, leftover_bytes) = framed.into_inner();
let (upgraded_stream, tls_cert) = ironrdp_tls::upgrade(initial_stream, config.destination.name())
.await
.map_err(|e| connector::custom_err!("TLS upgrade", e))?;
let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, &mut connector);
let erased_stream: Box<dyn AsyncReadWrite + Unpin + Send + Sync> = Box::new(upgraded_stream);
let mut upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes);
let server_public_key = ironrdp_tls::extract_tls_server_public_key(&tls_cert)
.ok_or_else(|| connector::general_err!("unable to extract tls server public key"))?;
let connection_result = ironrdp_tokio::connect_finalize(
upgraded,
connector,
&mut upgraded_framed,
&mut ReqwestNetworkClient::new(),
(&config.destination).into(),
server_public_key.to_owned(),
None,
)
.await?;
debug!(?connection_result);
Ok((connection_result, upgraded_framed))
}
async fn connect_ws(
config: &Config,
rdcleanpath: &RDCleanPathConfig,
cliprdr_factory: Option<&(dyn CliprdrBackendFactory + Send)>,
dvc_pipe_proxy_factory: &DvcPipeProxyFactory,
) -> ConnectorResult<(ConnectionResult, UpgradedFramed)> {
let hostname = rdcleanpath
.url
.host_str()
.ok_or_else(|| connector::general_err!("host missing from the URL"))?;
let port = rdcleanpath.url.port_or_known_default().unwrap_or(443);
let socket = TcpStream::connect((hostname, port))
.await
.map_err(|e| connector::custom_err!("TCP connect", e))?;
socket
.set_nodelay(true)
.map_err(|e| connector::custom_err!("set TCP_NODELAY", e))?;
let client_addr = socket
.local_addr()
.map_err(|e| connector::custom_err!("get socket local address", e))?;
let (ws, _) = tokio_tungstenite::client_async_tls(rdcleanpath.url.as_str(), socket)
.await
.map_err(|e| connector::custom_err!("WS connect", e))?;
let ws = crate::ws::websocket_compat(ws);
let mut framed = ironrdp_tokio::TokioFramed::new(ws);
let mut drdynvc =
ironrdp::dvc::DrdynvcClient::new().with_dynamic_channel(DisplayControlClient::new(|_| Ok(Vec::new())));
// Instantiate all DVC proxies
for proxy in config.dvc_pipe_proxies.iter() {
let channel_name = proxy.channel_name.clone();
let pipe_name = proxy.pipe_name.clone();
trace!(%channel_name, %pipe_name, "Creating DVC proxy");
drdynvc = drdynvc.with_dynamic_channel(dvc_pipe_proxy_factory.create(channel_name, pipe_name));
}
let mut connector = connector::ClientConnector::new(config.connector.clone(), client_addr)
.with_static_channel(drdynvc)
.with_static_channel(rdpsnd::client::Rdpsnd::new(Box::new(cpal::RdpsndBackend::new())))
.with_static_channel(rdpdr::Rdpdr::new(Box::new(NoopRdpdrBackend {}), "IronRDP".to_owned()).with_smartcard(0));
if let Some(builder) = cliprdr_factory {
let backend = builder.build_cliprdr_backend();
let cliprdr = cliprdr::Cliprdr::new(backend);
connector.attach_static_channel(cliprdr);
}
let destination = format!("{}:{}", config.destination.name(), config.destination.port());
let (upgraded, server_public_key) = connect_rdcleanpath(
&mut framed,
&mut connector,
destination,
rdcleanpath.auth_token.clone(),
None,
)
.await?;
let connection_result = ironrdp_tokio::connect_finalize(
upgraded,
connector,
&mut framed,
&mut ReqwestNetworkClient::new(),
(&config.destination).into(),
server_public_key,
None,
)
.await?;
let (ws, leftover_bytes) = framed.into_inner();
let erased_stream: Box<dyn AsyncReadWrite + Unpin + Send + Sync> = Box::new(ws);
let upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes);
Ok((connection_result, upgraded_framed))
}
async fn connect_rdcleanpath<S>(
framed: &mut ironrdp_tokio::Framed<S>,
connector: &mut connector::ClientConnector,
destination: String,
proxy_auth_token: String,
pcb: Option<String>,
) -> ConnectorResult<(ironrdp_tokio::Upgraded, Vec<u8>)>
where
S: ironrdp_tokio::FramedRead + FramedWrite,
{
use ironrdp::connector::Sequence as _;
use x509_cert::der::Decode as _;
#[derive(Clone, Copy, Debug)]
struct RDCleanPathHint;
const RDCLEANPATH_HINT: RDCleanPathHint = RDCleanPathHint;
impl ironrdp::pdu::PduHint for RDCleanPathHint {
fn find_size(&self, bytes: &[u8]) -> ironrdp::core::DecodeResult<Option<(bool, usize)>> {
match ironrdp_rdcleanpath::RDCleanPathPdu::detect(bytes) {
ironrdp_rdcleanpath::DetectionResult::Detected { total_length, .. } => Ok(Some((true, total_length))),
ironrdp_rdcleanpath::DetectionResult::NotEnoughBytes => Ok(None),
ironrdp_rdcleanpath::DetectionResult::Failed => Err(ironrdp::core::other_err!(
"RDCleanPathHint",
"detection failed (invalid PDU)"
)),
}
}
}
let mut buf = WriteBuf::new();
info!("Begin connection procedure");
{
// RDCleanPath request
let connector::ClientConnectorState::ConnectionInitiationSendRequest = connector.state else {
return Err(connector::general_err!("invalid connector state (send request)"));
};
debug_assert!(connector.next_pdu_hint().is_none());
let written = connector.step_no_input(&mut buf)?;
let x224_pdu_len = written.size().expect("written size");
debug_assert_eq!(x224_pdu_len, buf.filled_len());
let x224_pdu = buf.filled().to_vec();
let rdcleanpath_req =
ironrdp_rdcleanpath::RDCleanPathPdu::new_request(x224_pdu, destination, proxy_auth_token, pcb)
.map_err(|e| connector::custom_err!("new RDCleanPath request", e))?;
debug!(message = ?rdcleanpath_req, "Send RDCleanPath request");
let rdcleanpath_req = rdcleanpath_req
.to_der()
.map_err(|e| connector::custom_err!("RDCleanPath request encode", e))?;
framed
.write_all(&rdcleanpath_req)
.await
.map_err(|e| connector::custom_err!("couldn't write RDCleanPath request", e))?;
}
{
// RDCleanPath response
let rdcleanpath_res = framed
.read_by_hint(&RDCLEANPATH_HINT)
.await
.map_err(|e| connector::custom_err!("read RDCleanPath request", e))?;
let rdcleanpath_res = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_res)
.map_err(|e| connector::custom_err!("RDCleanPath response decode", e))?;
debug!(message = ?rdcleanpath_res, "Received RDCleanPath PDU");
let (x224_connection_response, server_cert_chain) = match rdcleanpath_res
.into_enum()
.map_err(|e| connector::custom_err!("invalid RDCleanPath PDU", e))?
{
ironrdp_rdcleanpath::RDCleanPath::Request { .. } => {
return Err(connector::general_err!(
"received an unexpected RDCleanPath type (request)",
));
}
ironrdp_rdcleanpath::RDCleanPath::Response {
x224_connection_response,
server_cert_chain,
server_addr: _,
} => (x224_connection_response, server_cert_chain),
ironrdp_rdcleanpath::RDCleanPath::GeneralErr(error) => {
return Err(connector::custom_err!("received an RDCleanPath error", error));
}
ironrdp_rdcleanpath::RDCleanPath::NegotiationErr {
x224_connection_response,
} => {
// Try to decode as X.224 Connection Confirm to extract negotiation failure details.
if let Ok(x224_confirm) = ironrdp_core::decode::<
ironrdp::pdu::x224::X224<ironrdp::pdu::nego::ConnectionConfirm>,
>(&x224_connection_response)
{
if let ironrdp::pdu::nego::ConnectionConfirm::Failure { code } = x224_confirm.0 {
// Convert to negotiation failure instead of generic RDCleanPath error.
let negotiation_failure = connector::NegotiationFailure::from(code);
return Err(connector::ConnectorError::new(
"RDP negotiation failed",
connector::ConnectorErrorKind::Negotiation(negotiation_failure),
));
}
}
// Fallback to generic error if we can't decode the negotiation failure.
return Err(connector::general_err!("received an RDCleanPath negotiation error"));
}
};
let connector::ClientConnectorState::ConnectionInitiationWaitConfirm { .. } = connector.state else {
return Err(connector::general_err!("invalid connector state (wait confirm)"));
};
debug_assert!(connector.next_pdu_hint().is_some());
buf.clear();
let written = connector.step(x224_connection_response.as_bytes(), &mut buf)?;
debug_assert!(written.is_nothing());
let server_cert = server_cert_chain
.into_iter()
.next()
.ok_or_else(|| connector::general_err!("server cert chain missing from rdcleanpath response"))?;
let cert = x509_cert::Certificate::from_der(server_cert.as_bytes())
.map_err(|e| connector::custom_err!("server cert chain missing from rdcleanpath response", e))?;
let server_public_key = cert
.tbs_certificate
.subject_public_key_info
.subject_public_key
.as_bytes()
.ok_or_else(|| connector::general_err!("subject public key BIT STRING is not aligned"))?
.to_owned();
let should_upgrade = ironrdp_tokio::skip_connect_begin(connector);
// At this point, proxy established the TLS session.
let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, connector);
Ok((upgraded, server_public_key))
}
}
async fn active_session(
framed: UpgradedFramed,
connection_result: ConnectionResult,
event_loop_proxy: &EventLoopProxy<RdpOutputEvent>,
input_event_receiver: &mut mpsc::UnboundedReceiver<RdpInputEvent>,
) -> SessionResult<RdpControlFlow> {
let (mut reader, mut writer) = split_tokio_framed(framed);
let mut image = DecodedImage::new(
PixelFormat::RgbA32,
connection_result.desktop_size.width,
connection_result.desktop_size.height,
);
let mut active_stage = ActiveStage::new(connection_result);
let disconnect_reason = 'outer: loop {
let outputs = tokio::select! {
frame = reader.read_pdu() => {
let (action, payload) = frame.map_err(|e| session::custom_err!("read frame", e))?;
trace!(?action, frame_length = payload.len(), "Frame received");
active_stage.process(&mut image, action, &payload)?
}
input_event = input_event_receiver.recv() => {
let input_event = input_event.ok_or_else(|| session::general_err!("GUI is stopped"))?;
match input_event {
RdpInputEvent::Resize { width, height, scale_factor, physical_size } => {
trace!(width, height, "Resize event");
let width = u32::from(width);
let height = u32::from(height);
// TODO: Make adjust_display_size take and return width and height as u16.
// From the function's doc comment, the width and height values must be less than or equal to 8192 pixels.
// Therefore, we can remove unnecessary casts from u16 to u32 and back.
let (width, height) = MonitorLayoutEntry::adjust_display_size(width, height);
debug!(width, height, "Adjusted display size");
if let Some(response_frame) = active_stage.encode_resize(width, height, Some(scale_factor), physical_size) {
vec![ActiveStageOutput::ResponseFrame(response_frame?)]
} else {
// TODO(#271): use the "auto-reconnect cookie": https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/15b0d1c9-2891-4adb-a45e-deb4aeeeab7c
debug!("Reconnecting with new size");
let width = u16::try_from(width).expect("always in the range");
let height = u16::try_from(height).expect("always in the range");
return Ok(RdpControlFlow::ReconnectWithNewSize { width, height })
}
},
RdpInputEvent::FastPath(events) => {
trace!(?events);
active_stage.process_fastpath_input(&mut image, &events)?
}
RdpInputEvent::Close => {
active_stage.graceful_shutdown()?
}
RdpInputEvent::Clipboard(event) => {
if let Some(cliprdr) = active_stage.get_svc_processor::<cliprdr::CliprdrClient>() {
if let Some(svc_messages) = match event {
ClipboardMessage::SendInitiateCopy(formats) => {
Some(cliprdr.initiate_copy(&formats)
.map_err(|e| session::custom_err!("CLIPRDR", e))?)
}
ClipboardMessage::SendFormatData(response) => {
Some(cliprdr.submit_format_data(response)
.map_err(|e| session::custom_err!("CLIPRDR", e))?)
}
ClipboardMessage::SendInitiatePaste(format) => {
Some(cliprdr.initiate_paste(format)
.map_err(|e| session::custom_err!("CLIPRDR", e))?)
}
ClipboardMessage::Error(e) => {
error!("Clipboard backend error: {}", e);
None
}
} {
let frame = active_stage.process_svc_processor_messages(svc_messages)?;
// Send the messages to the server
vec![ActiveStageOutput::ResponseFrame(frame)]
} else {
// No messages to send to the server
Vec::new()
}
} else {
warn!("Clipboard event received, but Cliprdr is not available");
Vec::new()
}
}
RdpInputEvent::SendDvcMessages { channel_id, messages } => {
trace!(channel_id, ?messages, "Send DVC messages");
let frame = active_stage.encode_dvc_messages(messages)?;
vec![ActiveStageOutput::ResponseFrame(frame)]
}
}
}
};
for out in outputs {
match out {
ActiveStageOutput::ResponseFrame(frame) => writer
.write_all(&frame)
.await
.map_err(|e| session::custom_err!("write response", e))?,
ActiveStageOutput::GraphicsUpdate(_region) => {
let buffer: Vec<u32> = image
.data()
.chunks_exact(4)
.map(|pixel| {
let r = pixel[0];
let g = pixel[1];
let b = pixel[2];
u32::from_be_bytes([0, r, g, b])
})
.collect();
event_loop_proxy
.send_event(RdpOutputEvent::Image {
buffer,
width: NonZeroU16::new(image.width())
.ok_or_else(|| session::general_err!("width is zero"))?,
height: NonZeroU16::new(image.height())
.ok_or_else(|| session::general_err!("height is zero"))?,
})
.map_err(|e| session::custom_err!("event_loop_proxy", e))?;
}
ActiveStageOutput::PointerDefault => {
event_loop_proxy
.send_event(RdpOutputEvent::PointerDefault)
.map_err(|e| session::custom_err!("event_loop_proxy", e))?;
}
ActiveStageOutput::PointerHidden => {
event_loop_proxy
.send_event(RdpOutputEvent::PointerHidden)
.map_err(|e| session::custom_err!("event_loop_proxy", e))?;
}
ActiveStageOutput::PointerPosition { x, y } => {
event_loop_proxy
.send_event(RdpOutputEvent::PointerPosition { x, y })
.map_err(|e| session::custom_err!("event_loop_proxy", e))?;
}
ActiveStageOutput::PointerBitmap(pointer) => {
event_loop_proxy
.send_event(RdpOutputEvent::PointerBitmap(pointer))
.map_err(|e| session::custom_err!("event_loop_proxy", e))?;
}
ActiveStageOutput::DeactivateAll(mut connection_activation) => {
// Execute the Deactivation-Reactivation Sequence:
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/dfc234ce-481a-4674-9a5d-2a7bafb14432
debug!("Received Server Deactivate All PDU, executing Deactivation-Reactivation Sequence");
let mut buf = WriteBuf::new();
'activation_seq: loop {
let written = single_sequence_step_read(&mut reader, &mut *connection_activation, &mut buf)
.await
.map_err(|e| session::custom_err!("read deactivation-reactivation sequence step", e))?;
if written.size().is_some() {
writer.write_all(buf.filled()).await.map_err(|e| {
session::custom_err!("write deactivation-reactivation sequence step", e)
})?;
}
if let ConnectionActivationState::Finalized {
io_channel_id,
user_channel_id,
desktop_size,
enable_server_pointer,
pointer_software_rendering,
} = connection_activation.connection_activation_state()
{
debug!(?desktop_size, "Deactivation-Reactivation Sequence completed");
// Update image size with the new desktop size.
image = DecodedImage::new(PixelFormat::RgbA32, desktop_size.width, desktop_size.height);
// Update the active stage with the new channel IDs and pointer settings.
active_stage.set_fastpath_processor(
fast_path::ProcessorBuilder {
io_channel_id,
user_channel_id,
enable_server_pointer,
pointer_software_rendering,
}
.build(),
);
active_stage.set_enable_server_pointer(enable_server_pointer);
break 'activation_seq;
}
}
}
ActiveStageOutput::Terminate(reason) => break 'outer reason,
}
}
};
Ok(RdpControlFlow::TerminatedGracefully(disconnect_reason))
}

View file

@ -0,0 +1,34 @@
use futures_util::{Sink, SinkExt as _, Stream, StreamExt as _};
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_tungstenite::tungstenite;
pub(crate) fn websocket_compat<S>(stream: S) -> impl AsyncRead + AsyncWrite + Unpin + Send + 'static
where
S: Stream<Item = Result<tungstenite::Message, tungstenite::Error>>
+ Sink<tungstenite::Message, Error = tungstenite::Error>
+ Unpin
+ Send
+ 'static,
{
let compat = stream
.filter_map(|item| {
let mapped = item
.map(|msg| match msg {
tungstenite::Message::Text(s) => Some(transport::WsReadMsg::Payload(tungstenite::Bytes::from(s))),
tungstenite::Message::Binary(data) => Some(transport::WsReadMsg::Payload(data)),
tungstenite::Message::Ping(_) | tungstenite::Message::Pong(_) => None,
tungstenite::Message::Close(_) => Some(transport::WsReadMsg::Close),
tungstenite::Message::Frame(_) => unreachable!("raw frames are never returned when reading"),
})
.transpose();
core::future::ready(mapped)
})
.with(|item| {
core::future::ready(Ok::<_, tungstenite::Error>(tungstenite::Message::Binary(
tungstenite::Bytes::from(item),
)))
});
transport::WsStream::new(compat)
}

View file

@ -0,0 +1,26 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [[0.1.4](https://github.com/Devolutions/IronRDP/compare/ironrdp-cliprdr-format-v0.1.3...ironrdp-cliprdr-format-v0.1.4)] - 2025-09-04
### <!-- 7 -->Build
- Bump png from 0.17.16 to 0.18.0 (#961) ([21fa028dff](https://github.com/Devolutions/IronRDP/commit/21fa028dffa5f9bb1498b4d48d063ea42929faf5))
## [[0.1.3](https://github.com/Devolutions/IronRDP/compare/ironrdp-cliprdr-format-v0.1.2...ironrdp-cliprdr-format-v0.1.3)] - 2025-03-12
### <!-- 7 -->Build
- Update dependencies (#695) ([c21fa44fd6](https://github.com/Devolutions/IronRDP/commit/c21fa44fd6f3c6a6b74788ff68e83133c1314caa))
## [[0.1.2](https://github.com/Devolutions/IronRDP/compare/ironrdp-cliprdr-format-v0.1.1...ironrdp-cliprdr-format-v0.1.2)] - 2025-01-28
### <!-- 6 -->Documentation
- Use CDN URLs instead of the blob storage URLs for Devolutions logo (#631) ([dd249909a8](https://github.com/Devolutions/IronRDP/commit/dd249909a894004d4f728d30b3a4aa77a0f8193b))

View file

@ -0,0 +1,23 @@
[package]
name = "ironrdp-cliprdr-format"
version = "0.1.4"
readme = "README.md"
description = "CLIPRDR format conversion library"
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
authors.workspace = true
keywords.workspace = true
categories.workspace = true
[lib]
doctest = false
test = false
[dependencies]
ironrdp-core = { path = "../ironrdp-core", version = "0.1", features = ["std"] } # public
png = "0.18"
[lints]
workspace = true

View file

@ -0,0 +1 @@
../../LICENSE-APACHE

View file

@ -0,0 +1 @@
../../LICENSE-MIT

View file

@ -0,0 +1,16 @@
# IronRDP CLIPRDR formats decoding/encoding library
This Library provides the conversion logic between RDP-specific clipboard formats and
widely used formats like PNG for images, plain string for HTML etc.
### Overflows
This crate has been audited by us and is guaranteed overflow-free on 32 and 64 bits architectures.
It would be easy to cause an overflow on a 16-bit architecture.
However, its hard to imagine an RDP client running on such machines.
Size of pointers on such architectures greatly limits the maximum size of the bitmap buffers.
Its likely the RDP client will choke on a big payload before overflowing because of this crate.
This crate is part of the [IronRDP] project.
[IronRDP]: https://github.com/Devolutions/IronRDP

View file

@ -0,0 +1,839 @@
use std::io::Cursor;
use ironrdp_core::{
cast_int, ensure_fixed_part_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, ReadCursor,
WriteCursor,
};
/// Maximum size of PNG image that could be placed on the clipboard.
const MAX_BUFFER_SIZE: usize = 64 * 1024 * 1024; // 64 MB
#[derive(Debug)]
pub enum BitmapError {
Decode(ironrdp_core::DecodeError),
Encode(ironrdp_core::EncodeError),
Unsupported(&'static str),
InvalidSize,
BufferTooBig,
WidthTooBig,
HeightTooBig,
PngEncode(png::EncodingError),
PngDecode(png::DecodingError),
}
impl core::fmt::Display for BitmapError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
BitmapError::Decode(_error) => write!(f, "decoding error"),
BitmapError::Encode(_error) => write!(f, "encoding error"),
BitmapError::Unsupported(s) => write!(f, "unsupported bitmap: {s}"),
BitmapError::InvalidSize => write!(f, "one of bitmap's dimensions is invalid"),
BitmapError::BufferTooBig => write!(f, "buffer size required for allocation is too big"),
BitmapError::WidthTooBig => write!(f, "image width is too big"),
BitmapError::HeightTooBig => write!(f, "image height is too big"),
BitmapError::PngEncode(_error) => write!(f, "PNG encoding error"),
BitmapError::PngDecode(_error) => write!(f, "PNG decoding error"),
}
}
}
impl core::error::Error for BitmapError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
BitmapError::Decode(error) => Some(error),
BitmapError::Encode(error) => Some(error),
BitmapError::Unsupported(_) => None,
BitmapError::InvalidSize => None,
BitmapError::BufferTooBig => None,
BitmapError::WidthTooBig => None,
BitmapError::HeightTooBig => None,
BitmapError::PngEncode(encoding_error) => Some(encoding_error),
BitmapError::PngDecode(decoding_error) => Some(decoding_error),
}
}
}
impl From<png::EncodingError> for BitmapError {
fn from(error: png::EncodingError) -> Self {
BitmapError::PngEncode(error)
}
}
impl From<png::DecodingError> for BitmapError {
fn from(error: png::DecodingError) -> Self {
BitmapError::PngDecode(error)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct BitmapCompression(u32);
#[expect(dead_code)]
impl BitmapCompression {
const RGB: Self = Self(0x0000);
const RLE8: Self = Self(0x0001);
const RLE4: Self = Self(0x0002);
const BITFIELDS: Self = Self(0x0003);
const JPEG: Self = Self(0x0004);
const PNG: Self = Self(0x0005);
const CMYK: Self = Self(0x000B);
const CMYKRLE8: Self = Self(0x000C);
const CMYKRLE4: Self = Self(0x000D);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct ColorSpace(u32);
#[expect(dead_code)]
impl ColorSpace {
const CALIBRATED_RGB: Self = Self(0x00000000);
const SRGB: Self = Self(0x73524742);
const WINDOWS: Self = Self(0x57696E20);
const PROFILE_LINKED: Self = Self(0x4C494E4B);
const PROFILE_EMBEDDED: Self = Self(0x4D424544);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct BitmapIntent(u32);
#[expect(dead_code)]
impl BitmapIntent {
const LCS_GM_ABS_COLORIMETRIC: Self = Self(0x00000008);
const LCS_GM_BUSINESS: Self = Self(0x00000001);
const LCS_GM_GRAPHICS: Self = Self(0x00000002);
const LCS_GM_IMAGES: Self = Self(0x00000004);
}
type Fxpt2Dot30 = u32; // (LONG)
#[derive(Default)]
struct Ciexyz {
x: Fxpt2Dot30,
y: Fxpt2Dot30,
z: Fxpt2Dot30,
}
#[derive(Default)]
struct CiexyzTriple {
red: Ciexyz,
green: Ciexyz,
blue: Ciexyz,
}
impl CiexyzTriple {
const NAME: &'static str = "CIEXYZTRIPLE";
const FIXED_PART_SIZE: usize = 4 * 3 * 3; // 4(LONG) * 3(xyz) * 3(red, green, blue)
}
impl<'a> Decode<'a> for CiexyzTriple {
fn decode(src: &mut ReadCursor<'a>) -> DecodeResult<Self> {
ensure_fixed_part_size!(in: src);
let red = Ciexyz {
x: src.read_u32(),
y: src.read_u32(),
z: src.read_u32(),
};
let green = Ciexyz {
x: src.read_u32(),
y: src.read_u32(),
z: src.read_u32(),
};
let blue = Ciexyz {
x: src.read_u32(),
y: src.read_u32(),
z: src.read_u32(),
};
Ok(Self { red, green, blue })
}
}
impl Encode for CiexyzTriple {
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
ensure_fixed_part_size!(in: dst);
dst.write_u32(self.red.x);
dst.write_u32(self.red.y);
dst.write_u32(self.red.z);
dst.write_u32(self.green.x);
dst.write_u32(self.green.y);
dst.write_u32(self.green.z);
dst.write_u32(self.blue.x);
dst.write_u32(self.blue.y);
dst.write_u32(self.blue.z);
Ok(())
}
fn name(&self) -> &'static str {
Self::NAME
}
fn size(&self) -> usize {
Self::FIXED_PART_SIZE
}
}
/// Header used in `CF_DIB` formats, part of [BITMAPINFO]
///
/// We don't use the optional `bmiColors` field, because it is only relevant for bitmaps with
/// bpp < 24, which are not supported yet, therefore only fixed part of the header is implemented.
///
/// [BITMAPINFO]: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfo
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct BitmapInfoHeader {
/// INVARIANT: `width.abs() <= 10_000`
width: i32,
/// INVARIANT: `height.abs() <= 10_000`
height: i32,
/// INVARIANT: `bit_count <= 32`
bit_count: u16,
compression: BitmapCompression,
size_image: u32,
x_pels_per_meter: i32,
y_pels_per_meter: i32,
clr_used: u32,
clr_important: u32,
}
impl BitmapInfoHeader {
const FIXED_PART_SIZE: usize = 4 // biSize (DWORD)
+ 4 // biWidth (LONG)
+ 4 // biHeight (LONG)
+ 2 // biPlanes (WORD)
+ 2 // biBitCount (WORD)
+ 4 // biCompression (DWORD)
+ 4 // biSizeImage (DWORD)
+ 4 // biXPelsPerMeter (LONG)
+ 4 // biYPelsPerMeter (LONG)
+ 4 // biClrUsed (DWORD)
+ 4; // biClrImportant (DWORD)
const NAME: &'static str = "BITMAPINFOHEADER";
fn encode_with_size(&self, dst: &mut WriteCursor<'_>, size: u32) -> EncodeResult<()> {
ensure_fixed_part_size!(in: dst);
dst.write_u32(size);
dst.write_i32(self.width);
dst.write_i32(self.height);
dst.write_u16(1); // biPlanes
dst.write_u16(self.bit_count);
dst.write_u32(self.compression.0);
dst.write_u32(self.size_image);
dst.write_i32(self.x_pels_per_meter);
dst.write_i32(self.y_pels_per_meter);
dst.write_u32(self.clr_used);
dst.write_u32(self.clr_important);
Ok(())
}
fn decode_with_size(src: &mut ReadCursor<'_>) -> DecodeResult<(Self, u32)> {
ensure_fixed_part_size!(in: src);
let size = src.read_u32();
// NOTE: .abs() could panic on i32::MIN, therefore we have a check for it first.
let width = src.read_i32();
check_invariant(width != i32::MIN && width.abs() <= 10_000)
.ok_or_else(|| invalid_field_err!("biWidth", "width is too big"))?;
let height = src.read_i32();
check_invariant(height != i32::MIN && height.abs() <= 10_000)
.ok_or_else(|| invalid_field_err!("biHeight", "height is too big"))?;
let planes = src.read_u16();
if planes != 1 {
return Err(invalid_field_err!("biPlanes", "invalid planes count"));
}
let bit_count = src.read_u16();
check_invariant(bit_count <= 32).ok_or_else(|| invalid_field_err!("biBitCount", "invalid bit count"))?;
let compression = BitmapCompression(src.read_u32());
let size_image = src.read_u32();
let x_pels_per_meter = src.read_i32();
let y_pels_per_meter = src.read_i32();
let clr_used = src.read_u32();
let clr_important = src.read_u32();
let header = Self {
width,
height,
bit_count,
compression,
size_image,
x_pels_per_meter,
y_pels_per_meter,
clr_used,
clr_important,
};
Ok((header, size))
}
// INVARIANT: output (width) <= 10_000
fn width(&self) -> u16 {
let abs = self.width.abs();
debug_assert!(abs <= 10_000);
u16::try_from(abs).expect("per the invariant on self.width, this cast is infallible")
}
// INVARIANT: output (height) <= 10_000
fn height(&self) -> u16 {
let abs = self.height.abs();
debug_assert!(abs <= 10_000);
u16::try_from(abs).expect("per the invariant on self.height, this cast is infallible")
}
fn is_bottom_up(&self) -> bool {
// When self.height is positive, the bitmap is defined as bottom-up.
self.height >= 0
}
}
impl Encode for BitmapInfoHeader {
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
let size = cast_int!("biSize", Self::FIXED_PART_SIZE)?;
self.encode_with_size(dst, size)
}
fn name(&self) -> &'static str {
Self::NAME
}
fn size(&self) -> usize {
Self::FIXED_PART_SIZE
}
}
impl<'a> Decode<'a> for BitmapInfoHeader {
fn decode(src: &mut ReadCursor<'a>) -> DecodeResult<Self> {
let (header, size) = Self::decode_with_size(src)?;
let size: usize = cast_int!("biSize", size)?;
if size != Self::FIXED_PART_SIZE {
return Err(invalid_field_err!("biSize", "invalid V1 bitmap info header size"));
}
Ok(header)
}
}
/// Header used in `CF_DIBV5` formats, defined as [BITMAPV5HEADER]
///
/// [BITMAPV5HEADER]: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapv5header
struct BitmapV5Header {
v1: BitmapInfoHeader,
red_mask: u32,
green_mask: u32,
blue_mask: u32,
alpha_mask: u32,
color_space: ColorSpace,
endpoints: CiexyzTriple,
gamma_red: u32,
gamma_green: u32,
gamma_blue: u32,
intent: BitmapIntent,
profile_data: u32,
profile_size: u32,
}
impl BitmapV5Header {
const FIXED_PART_SIZE: usize = BitmapInfoHeader::FIXED_PART_SIZE // BITMAPV5HEADER
+ 4 // bV5RedMask (DWORD)
+ 4 // bV5GreenMask (DWORD)
+ 4 // bV5BlueMask (DWORD)
+ 4 // bV5AlphaMask (DWORD)
+ 4 // bV5CSType (DWORD)
+ CiexyzTriple::FIXED_PART_SIZE // bV5Endpoints (CIEXYZTRIPLE)
+ 4 // bV5GammaRed (DWORD)
+ 4 // bV5GammaGreen (DWORD)
+ 4 // bV5GammaBlue (DWORD)
+ 4 // bV5Intent (DWORD)
+ 4 // bV5ProfileData (DWORD)
+ 4 // bV5ProfileSize (DWORD)
+ 4; // bV5Reserved (DWORD)
const NAME: &'static str = "BITMAPV5HEADER";
}
impl Encode for BitmapV5Header {
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
ensure_fixed_part_size!(in: dst);
let size = cast_int!("biSize", Self::FIXED_PART_SIZE)?;
self.v1.encode_with_size(dst, size)?;
dst.write_u32(self.red_mask);
dst.write_u32(self.green_mask);
dst.write_u32(self.blue_mask);
dst.write_u32(self.alpha_mask);
dst.write_u32(self.color_space.0);
self.endpoints.encode(dst)?;
dst.write_u32(self.gamma_red);
dst.write_u32(self.gamma_green);
dst.write_u32(self.gamma_blue);
dst.write_u32(self.intent.0);
dst.write_u32(self.profile_data);
dst.write_u32(self.profile_size);
dst.write_u32(0);
Ok(())
}
fn name(&self) -> &'static str {
Self::NAME
}
fn size(&self) -> usize {
Self::FIXED_PART_SIZE
}
}
impl<'a> Decode<'a> for BitmapV5Header {
fn decode(src: &mut ReadCursor<'a>) -> DecodeResult<Self> {
ensure_fixed_part_size!(in: src);
let (header_v1, size) = BitmapInfoHeader::decode_with_size(src)?;
let size: usize = cast_int!("biSize", size)?;
if size != Self::FIXED_PART_SIZE {
return Err(invalid_field_err!("biSize", "invalid V5 bitmap info header size"));
}
let red_mask = src.read_u32();
let green_mask = src.read_u32();
let blue_mask = src.read_u32();
let alpha_mask = src.read_u32();
let color_space_type = ColorSpace(src.read_u32());
let endpoints = CiexyzTriple::decode(src)?;
let gamma_red = src.read_u32();
let gamma_green = src.read_u32();
let gamma_blue = src.read_u32();
let intent = BitmapIntent(src.read_u32());
let profile_data = src.read_u32();
let profile_size = src.read_u32();
let _reserved = src.read_u32();
Ok(Self {
v1: header_v1,
red_mask,
green_mask,
blue_mask,
alpha_mask,
color_space: color_space_type,
endpoints,
gamma_red,
gamma_green,
gamma_blue,
intent,
profile_data,
profile_size,
})
}
}
fn validate_v1_header(header: &BitmapInfoHeader) -> Result<(), BitmapError> {
if header.width < 0 {
return Err(BitmapError::Unsupported("negative width"));
}
if header.width == 0 || header.height == 0 {
return Err(BitmapError::InvalidSize);
}
// In the modern world bitmaps with bpp < 24 are rare, and it is even more rare for the bitmaps
// which are placed on the clipboard as DIBs, therefore we could safely skip the support for
// such bitmaps.
const SUPPORTED_BIT_COUNT: &[u16] = &[24, 32];
if !SUPPORTED_BIT_COUNT.contains(&header.bit_count) {
return Err(BitmapError::Unsupported("unsupported bit count"));
}
// This is only relevant for bitmaps with bpp < 24, which are not supported.
if header.clr_used != 0 {
return Err(BitmapError::Unsupported("color table is not supported"));
}
Ok(())
}
fn validate_v5_header(header: &BitmapV5Header) -> Result<(), BitmapError> {
validate_v1_header(&header.v1)?;
// We support only uncompressed DIB bitmaps as it is the most common case for clipboard-copied bitmaps.
const DIBV5_SUPPORTED_COMPRESSION: &[BitmapCompression] = &[BitmapCompression::RGB, BitmapCompression::BITFIELDS];
if !DIBV5_SUPPORTED_COMPRESSION.contains(&header.v1.compression) {
return Err(BitmapError::Unsupported("unsupported compression"));
}
if header.v1.compression == BitmapCompression::BITFIELDS {
// Currently, we only support the standard order, BGRA, for the bitfields compression.
let is_bgr = header.red_mask == 0x00FF0000 && header.green_mask == 0x0000FF00 && header.blue_mask == 0x000000FF;
// Note: when there is no alpha channel, the mask is 0x00000000 and we support this too.
let is_supported_alpha = header.alpha_mask == 0 || header.alpha_mask == 0xFF000000;
if !is_bgr || !is_supported_alpha {
return Err(BitmapError::Unsupported(
"non-standard color masks for `BITFIELDS` compression are not supported",
));
}
}
const SUPPORTED_COLOR_SPACE: &[ColorSpace] = &[
ColorSpace::SRGB,
// Assume that Windows color space is sRGB, either way we don't have enough information on
// the clipboard to convert it to other color spaces.
ColorSpace::WINDOWS,
];
if !SUPPORTED_COLOR_SPACE.contains(&header.color_space) {
return Err(BitmapError::Unsupported("not supported color space"));
}
Ok(())
}
struct PngEncoderContext {
bitmap: Vec<u8>,
width: u16,
height: u16,
color_type: png::ColorType,
}
/// Computes the stride of an uncompressed RGB bitmap.
///
/// INVARIANT: `width <= output (stride) <= width * 4`
///
/// In an uncompressed bitmap, the stride is the number of bytes needed to go from the start of one
/// row of pixels to the start of the next row. The image format defines a minimum stride for an
/// image. In addition, the graphics hardware might require a larger stride for the surface that
/// contains the image.
///
/// For uncompressed RGB formats, the minimum stride is always the image width in bytes, rounded up
/// to the nearest DWORD (4 bytes). The following formula is used to calculate the stride:
///
/// ```
/// stride = ((((width * bit_count) + 31) & ~31) >> 3)
/// ```
///
/// From Microsoft doc: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
fn rgb_bmp_stride(width: u16, bit_count: u16) -> usize {
debug_assert!(bit_count <= 32);
// No side effects, because u16::MAX * 32 + 31 < u16::MAX * u16::MAX < u32::MAX
#[expect(clippy::arithmetic_side_effects)]
{
(((usize::from(width) * usize::from(bit_count)) + 31) & !31) >> 3
}
}
fn bgra_to_top_down_rgba(
header: &BitmapInfoHeader,
src_bitmap: &[u8],
preserve_alpha: bool,
) -> Result<PngEncoderContext, BitmapError> {
// DIB may be encoded bottom-up, but the format we target, PNG, is top-down.
let should_flip_vertically = header.is_bottom_up();
let width = header.width();
let height = header.height();
let src_n_samples = usize::from(header.bit_count / 8);
let src_stride = rgb_bmp_stride(width, header.bit_count);
let (dst_color_type, dst_n_samples) = if preserve_alpha {
(png::ColorType::Rgba, 4)
} else {
(png::ColorType::Rgb, 3)
};
// Per invariants: height * width * dst_n_samples <= 10_000 * 10_000 * 4 < u32::MAX
#[expect(clippy::arithmetic_side_effects)]
let dst_bitmap_len = usize::from(height) * usize::from(width) * dst_n_samples;
// Prevent allocation of huge buffers.
ensure(dst_bitmap_len <= MAX_BUFFER_SIZE).ok_or(BitmapError::BufferTooBig)?;
let mut rows_normal;
let mut rows_reversed;
let rows: &mut dyn Iterator<Item = &[u8]> = if should_flip_vertically {
rows_reversed = src_bitmap.chunks_exact(src_stride).rev();
&mut rows_reversed
} else {
rows_normal = src_bitmap.chunks_exact(src_stride);
&mut rows_normal
};
// DIB stores BGRA colors while PNG uses RGBA.
// DIBv1 (CF_DIB) does not have alpha channel, and the fourth byte is always set to 0xFF.
// DIBv5 (CF_DIBV5) supports alpha channel, so we should preserve it if it is present.
let transform: fn((&mut [u8], &[u8])) = match (header.bit_count, dst_color_type) {
(24 | 32, png::ColorType::Rgb) => |(pixel_out, pixel_in)| {
pixel_out[0] = pixel_in[2];
pixel_out[1] = pixel_in[1];
pixel_out[2] = pixel_in[0];
},
(24, png::ColorType::Rgba) => |(pixel_out, pixel_in)| {
pixel_out[0] = pixel_in[2];
pixel_out[1] = pixel_in[1];
pixel_out[2] = pixel_in[0];
pixel_out[3] = 0xFF;
},
(32, png::ColorType::Rgba) => |(pixel_out, pixel_in)| {
pixel_out[0] = pixel_in[2];
pixel_out[1] = pixel_in[1];
pixel_out[2] = pixel_in[0];
pixel_out[3] = pixel_in[3];
},
_ => unreachable!("possible values are restricted by header validation and logic above"),
};
// Per invariants: width * dst_n_samples <= 10_000 * 4 < u32::MAX
#[expect(clippy::arithmetic_side_effects)]
let dst_stride = usize::from(width) * dst_n_samples;
let mut dst_bitmap = vec![0u8; dst_bitmap_len];
dst_bitmap
.chunks_exact_mut(dst_stride)
.zip(rows)
.for_each(|(dst_row, src_row)| {
let dst_pixels = dst_row.chunks_exact_mut(dst_n_samples);
let src_pixels = src_row.chunks_exact(src_n_samples);
dst_pixels.zip(src_pixels).for_each(transform);
});
Ok(PngEncoderContext {
bitmap: dst_bitmap,
width,
height,
color_type: dst_color_type,
})
}
fn encode_png(ctx: &PngEncoderContext) -> Result<Vec<u8>, BitmapError> {
let mut output: Vec<u8> = Vec::new();
let width = u32::from(ctx.width);
let height = u32::from(ctx.height);
let mut encoder = png::Encoder::new(&mut output, width, height);
encoder.set_color(ctx.color_type);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder.write_header()?;
writer.write_image_data(&ctx.bitmap)?;
writer.finish()?;
Ok(output)
}
/// Converts `CF_DIB` to PNG.
pub fn dib_to_png(input: &[u8]) -> Result<Vec<u8>, BitmapError> {
let mut src = ReadCursor::new(input);
let header = BitmapInfoHeader::decode(&mut src).map_err(BitmapError::Decode)?;
validate_v1_header(&header)?;
// We support only uncompressed DIB bitmaps as it is the most common case for clipboard-copied bitmaps.
// However, for DIBv1 specifically, BitmapCompression::BITFIELDS is not supported even when the order is BGRA,
// because there is an additional variable-sized header holding the color masks that we dont support yet.
const DIBV1_SUPPORTED_COMPRESSION: &[BitmapCompression] = &[BitmapCompression::RGB];
if !DIBV1_SUPPORTED_COMPRESSION.contains(&header.compression) {
return Err(BitmapError::Unsupported("unsupported compression"));
}
let png_ctx = bgra_to_top_down_rgba(&header, src.remaining(), false)?;
encode_png(&png_ctx)
}
/// Converts `CF_DIB` to PNG.
pub fn dibv5_to_png(input: &[u8]) -> Result<Vec<u8>, BitmapError> {
let mut src = ReadCursor::new(input);
let header = BitmapV5Header::decode(&mut src).map_err(BitmapError::Decode)?;
validate_v5_header(&header)?;
let png_ctx = bgra_to_top_down_rgba(&header.v1, src.remaining(), true)?;
encode_png(&png_ctx)
}
fn top_down_rgba_to_bottom_up_bgra(
info: png::OutputInfo,
src_bitmap: &[u8],
) -> Result<(BitmapInfoHeader, Vec<u8>), BitmapError> {
let no_alpha = info.color_type != png::ColorType::Rgba;
let width = u16::try_from(info.width).map_err(|_| BitmapError::WidthTooBig)?;
let height = u16::try_from(info.height).map_err(|_| BitmapError::HeightTooBig)?;
#[expect(clippy::arithmetic_side_effects)] // width * 4 <= 10_000 * 4 < u32::MAX
let stride = usize::from(width) * 4;
let src_rows = src_bitmap.chunks_exact(stride);
// As per invariants: stride * height <= width * 4 * height <= 10_000 * 4 * 10_000 <= u32::MAX.
#[expect(clippy::arithmetic_side_effects)]
let dst_len = stride * usize::from(height);
let dst_len = u32::try_from(dst_len).map_err(|_| BitmapError::InvalidSize)?;
let header = BitmapInfoHeader {
width: i32::from(width),
height: i32::from(height),
bit_count: 32, // 4 samples * 8 bits
compression: BitmapCompression::RGB,
size_image: dst_len,
x_pels_per_meter: 0,
y_pels_per_meter: 0,
clr_used: 0,
clr_important: 0,
};
let dst_len = usize::try_from(dst_len).map_err(|_| BitmapError::InvalidSize)?;
let mut dst_bitmap = vec![0; dst_len];
// Reverse rows to draw the image from bottom to top.
let dst_rows = dst_bitmap.chunks_exact_mut(stride).rev();
let transform: fn((&mut [u8], &[u8])) = if no_alpha {
|(dst_pixel, src_pixel)| {
dst_pixel[0] = src_pixel[2];
dst_pixel[1] = src_pixel[1];
dst_pixel[2] = src_pixel[0];
dst_pixel[3] = 0xFF;
}
} else {
|(dst_pixel, src_pixel)| {
dst_pixel[0] = src_pixel[2];
dst_pixel[1] = src_pixel[1];
dst_pixel[2] = src_pixel[0];
dst_pixel[3] = src_pixel[3];
}
};
dst_rows.zip(src_rows).for_each(|(dst_row, src_row)| {
let dst_pixels = dst_row.chunks_exact_mut(4);
let src_pixels = src_row.chunks_exact(4);
dst_pixels.zip(src_pixels).for_each(transform);
});
Ok((header, dst_bitmap))
}
fn decode_png(mut input: &[u8]) -> Result<(png::OutputInfo, Vec<u8>), BitmapError> {
let mut decoder = png::Decoder::new(Cursor::new(&mut input));
// We need to produce 32-bit DIB, so we should expand the palette to 32-bit RGBA.
decoder.set_transformations(png::Transformations::ALPHA | png::Transformations::EXPAND);
let mut reader = decoder.read_info()?;
let Some(output_buffer_len) = reader.output_buffer_size() else {
return Err(BitmapError::BufferTooBig);
};
// Prevent allocation of huge buffers.
ensure(output_buffer_len <= MAX_BUFFER_SIZE).ok_or(BitmapError::BufferTooBig)?;
let mut buffer = vec![0; output_buffer_len];
let info = reader.next_frame(&mut buffer)?;
buffer.truncate(info.buffer_size());
Ok((info, buffer))
}
/// Converts PNG to `CF_DIB` format.
pub fn png_to_cf_dib(input: &[u8]) -> Result<Vec<u8>, BitmapError> {
// FIXME(perf): its possible to allocate a single array and to directly write both the header and the actual bitmap inside.
// Currently, the code is performing three allocations: one inside `decode_png`, one inside `top_down_rgba_to_bottom_up_bgra`
// and one in the body of this function.
let (png_info, rgba_bytes) = decode_png(input)?;
let (header, bgra_bytes) = top_down_rgba_to_bottom_up_bgra(png_info, &rgba_bytes)?;
let output_len = header
.size()
.checked_add(bgra_bytes.len())
.ok_or(BitmapError::BufferTooBig)?;
ensure(output_len <= MAX_BUFFER_SIZE).ok_or(BitmapError::BufferTooBig)?;
let mut output = vec![0; output_len];
{
let mut dst = WriteCursor::new(&mut output);
header.encode(&mut dst).map_err(BitmapError::Encode)?;
dst.write_slice(&bgra_bytes);
}
Ok(output)
}
/// Converts PNG to `CF_DIBV5` format.
pub fn png_to_cf_dibv5(input: &[u8]) -> Result<Vec<u8>, BitmapError> {
// FIXME(perf): its possible to allocate a single array and to directly write both the header and the actual bitmap inside.
// Currently, the code is performing three allocations: one inside `decode_png`, one inside `top_down_rgba_to_bottom_up_bgra`
// and one in the body of this function.
let (png_info, rgba_bytes) = decode_png(input)?;
let (header_v1, bgra_bytes) = top_down_rgba_to_bottom_up_bgra(png_info, &rgba_bytes)?;
let header = BitmapV5Header {
v1: header_v1,
// Windows sets these masks for 32-bit bitmaps even if BITFIELDS compression is not used.
red_mask: 0x00FF0000,
green_mask: 0x0000FF00,
blue_mask: 0x000000FF,
alpha_mask: 0xFF000000,
color_space: ColorSpace::SRGB,
endpoints: Default::default(),
gamma_red: 0,
gamma_green: 0,
gamma_blue: 0,
intent: BitmapIntent::LCS_GM_IMAGES,
profile_data: 0,
profile_size: 0,
};
let output_len = header
.size()
.checked_add(bgra_bytes.len())
.ok_or(BitmapError::BufferTooBig)?;
ensure(output_len <= MAX_BUFFER_SIZE).ok_or(BitmapError::BufferTooBig)?;
let mut output = vec![0; output_len];
{
let mut dst = WriteCursor::new(&mut output);
header.encode(&mut dst).map_err(BitmapError::Encode)?;
dst.write_slice(&bgra_bytes);
}
Ok(output)
}
/// Use this when establishing invariants.
#[inline]
#[must_use]
fn check_invariant(condition: bool) -> Option<()> {
condition.then_some(())
}
/// Returns `None` when the condition is unmet.
#[inline]
#[must_use]
fn ensure(condition: bool) -> Option<()> {
condition.then_some(())
}

View file

@ -0,0 +1,179 @@
#[derive(Debug)]
pub enum HtmlError {
InvalidFormat,
InvalidUtf8(core::str::Utf8Error),
InvalidInteger(core::num::ParseIntError),
InvalidConversion,
}
impl core::fmt::Display for HtmlError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
HtmlError::InvalidFormat => write!(f, "invalid CF_HTML format"),
HtmlError::InvalidUtf8(_error) => write!(f, "invalid UTF-8"),
HtmlError::InvalidInteger(_error) => write!(f, "failed to parse integer"),
HtmlError::InvalidConversion => write!(f, "invalid integer conversion"),
}
}
}
impl core::error::Error for HtmlError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
HtmlError::InvalidFormat => None,
HtmlError::InvalidUtf8(utf8_error) => Some(utf8_error),
HtmlError::InvalidInteger(parse_int_error) => Some(parse_int_error),
HtmlError::InvalidConversion => None,
}
}
}
impl From<core::str::Utf8Error> for HtmlError {
fn from(error: core::str::Utf8Error) -> Self {
HtmlError::InvalidUtf8(error)
}
}
impl From<core::num::ParseIntError> for HtmlError {
fn from(error: core::num::ParseIntError) -> Self {
HtmlError::InvalidInteger(error)
}
}
/// Converts `CF_HTML` format to plain HTML text.
///
/// Note that the `CF_HTML` format is using UTF-8, and the input is expected to be valid UTF-8.
/// However, there is no easy way to know the size of the `CF_HTML` payload:
/// 1) its typically not null-terminated, and
/// 2) reading the headers is already half of the work.
///
/// Because of that, this function takes the input as a byte slice and finds the end of the payload itself.
/// This is expected to be more convenient at the callsite.
pub fn cf_html_to_plain_html(input: &[u8]) -> Result<&str, HtmlError> {
const EOL_CONTROL_CHARS: &[u8] = b"\r\n";
let mut start_fragment = None;
let mut end_fragment = None;
// Well move the lower bound of this slice until all headers are read.
let mut cursor = input;
loop {
let line = {
// We use a custom logic for splitting lines, instead of something like `str::lines`.
// Thats because `str::lines` does not split at carriage return (`\r`) not followed by line feed (`\n`).
// In `CF_HTML` format, the line ending could be represented using `\r` alone.
let eol_pos = cursor
.iter()
.position(|byte| EOL_CONTROL_CHARS.contains(byte))
.ok_or(HtmlError::InvalidFormat)?;
core::str::from_utf8(&cursor[..eol_pos])?
};
match line.split_once(':') {
Some((key, value)) => match key {
"StartFragment" => {
start_fragment = Some(header_value_to_u32(value)?);
}
"EndFragment" => {
end_fragment = Some(header_value_to_u32(value)?);
}
_ => {
// We are not interested in other headers.
}
},
None => {
// At this point, we reached the end of the headers.
if let (Some(start), Some(end)) = (start_fragment, end_fragment) {
let start = usize::try_from(start).map_err(|_| HtmlError::InvalidConversion)?;
let end = usize::try_from(end).map_err(|_| HtmlError::InvalidConversion)?;
// Ensure start and end values are properly bounded.
if !(start < end && end < input.len()) {
return Err(HtmlError::InvalidFormat);
}
// Extract the fragment from the original buffer.
let fragment = core::str::from_utf8(&input[start..end])?;
return Ok(fragment);
} else {
// If required headers were not found, the input is considered invalid.
return Err(HtmlError::InvalidFormat);
}
}
};
// Skip EOL control characters and prepare for next line.
cursor = &cursor[line.len()..];
while let Some(b'\n' | b'\r') = cursor.first() {
cursor = &cursor[1..];
}
}
fn header_value_to_u32(value: &str) -> Result<u32, core::num::ParseIntError> {
value.trim_start_matches('0').parse::<u32>()
}
}
/// Converts plain HTML text to `CF_HTML` format.
pub fn plain_html_to_cf_html(fragment: &str) -> String {
const POS_PLACEHOLDER: &str = "0000000000";
let mut buffer = String::new();
let mut write_header = |key: &str, value: &str| {
// This relation holds: key.len() + value.len() + ":\r\n".len() < usize::MAX
// Rationale: we know all possible values (see code below), and they are much smaller than `usize::MAX`.
#[expect(clippy::arithmetic_side_effects)]
let size = key.len() + value.len() + ":\r\n".len();
buffer.reserve(size);
buffer.push_str(key);
buffer.push(':');
let value_pos = buffer.len();
buffer.push_str(value);
buffer.push_str("\r\n");
value_pos
};
write_header("Version", "0.9");
let start_html_header_value_pos = write_header("StartHTML", POS_PLACEHOLDER);
let end_html_header_value_pos = write_header("EndHTML", POS_PLACEHOLDER);
let start_fragment_header_value_pos = write_header("StartFragment", POS_PLACEHOLDER);
let end_fragment_header_value_pos = write_header("EndFragment", POS_PLACEHOLDER);
let start_html_pos = buffer.len();
buffer.push_str("<html>\r\n<body>\r\n<!--StartFragment-->");
let start_fragment_pos = buffer.len();
buffer.push_str(fragment);
let end_fragment_pos = buffer.len();
buffer.push_str("<!--EndFragment-->\r\n</body>\r\n</html>");
let end_html_pos = buffer.len();
let start_html_pos_value = format!("{start_html_pos:0>10}");
let end_html_pos_value = format!("{end_html_pos:0>10}");
let start_fragment_pos_value = format!("{start_fragment_pos:0>10}");
let end_fragment_pos_value = format!("{end_fragment_pos:0>10}");
let mut replace_placeholder = |value_begin_idx: usize, header_value: &str| {
// We know that: value_begin_idx + POS_PLACEHOLDER.len() < usize::MAX
// Rationale: the headers are written at the beginning, and were not indexing outside of the string.
#[expect(clippy::arithmetic_side_effects)]
let value_end_idx = value_begin_idx + POS_PLACEHOLDER.len();
buffer.replace_range(value_begin_idx..value_end_idx, header_value);
};
replace_placeholder(start_html_header_value_pos, &start_html_pos_value);
replace_placeholder(end_html_header_value_pos, &end_html_pos_value);
replace_placeholder(start_fragment_header_value_pos, &start_fragment_pos_value);
replace_placeholder(end_fragment_header_value_pos, &end_fragment_pos_value);
buffer
}

View file

@ -0,0 +1,5 @@
#![cfg_attr(doc, doc = include_str!("../README.md"))]
#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
pub mod bitmap;
pub mod html;

View file

@ -0,0 +1,62 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [[0.5.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-cliprdr-native-v0.4.0...ironrdp-cliprdr-native-v0.5.0)] - 2025-12-18
### <!-- 4 -->Bug Fixes
- Prevent window class registration error on multiple sessions ([#1047](https://github.com/Devolutions/IronRDP/issues/1047)) ([a2af587e60](https://github.com/Devolutions/IronRDP/commit/a2af587e60e869f0235703e21772d1fc6a7dadcd))
When starting a second clipboard session, `RegisterClassA` would fail
with `ERROR_CLASS_ALREADY_EXISTS` because window classes are global to
the process. Now checks if the class is already registered before
attempting registration, allowing multiple WinClipboard instances to
coexist.
### <!-- 7 -->Build
- Bump windows from 0.61.3 to 0.62.1 ([#1010](https://github.com/Devolutions/IronRDP/issues/1010)) ([79e71c4f90](https://github.com/Devolutions/IronRDP/commit/79e71c4f90ea68b14fe45241c1cf3953027b22a2))
## [[0.4.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-cliprdr-native-v0.3.0...ironrdp-cliprdr-native-v0.4.0)] - 2025-08-29
### <!-- 4 -->Bug Fixes
- Map `E_ACCESSDENIED` WinAPI error code to `ClipboardAccessDenied` error (#936) ([b0c145d0d9](https://github.com/Devolutions/IronRDP/commit/b0c145d0d9cf2f347e537c08ce9d6c35223823d5))
When the system clipboard updates, we receive an `Updated` event. Then
we try to open it, but we can get `AccessDenied` error because the
clipboard may still be locked for another window (like _Notepad_). To
handle this, we have special logic that attempts to open the clipboard
in the event of such errors.
The problem is that so far, the `ClipboardAccessDenied` error was not mapped.
## [[0.1.4](https://github.com/Devolutions/IronRDP/compare/ironrdp-cliprdr-native-v0.1.3...ironrdp-cliprdr-native-v0.1.4)] - 2025-03-12
### <!-- 7 -->Build
- Update dependencies (#695) ([c21fa44fd6](https://github.com/Devolutions/IronRDP/commit/c21fa44fd6f3c6a6b74788ff68e83133c1314caa))
## [[0.1.3](https://github.com/Devolutions/IronRDP/compare/ironrdp-cliprdr-native-v0.1.2...ironrdp-cliprdr-native-v0.1.3)] - 2025-02-03
### <!-- 4 -->Bug Fixes
- Handle `WM_ACTIVATEAPP` in `clipboard_subproc` ([#657](https://github.com/Devolutions/IronRDP/issues/657)) ([9b2926ea12](https://github.com/Devolutions/IronRDP/commit/9b2926ea1212d3f9dec9354334d5bdaa1bebd81e))
Previously, the function handled only `WM_ACTIVATE`.
## [[0.1.2](https://github.com/Devolutions/IronRDP/compare/ironrdp-cliprdr-native-v0.1.1...ironrdp-cliprdr-native-v0.1.2)] - 2025-01-28
### <!-- 6 -->Documentation
- Use CDN URLs instead of the blob storage URLs for Devolutions logo ([#631](https://github.com/Devolutions/IronRDP/issues/631)) ([dd249909a8](https://github.com/Devolutions/IronRDP/commit/dd249909a894004d4f728d30b3a4aa77a0f8193b))
## [[0.1.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-cliprdr-native-v0.1.0...ironrdp-cliprdr-native-v0.1.1)] - 2024-12-14
### Other
- Symlinks to license files in packages ([#604](https://github.com/Devolutions/IronRDP/pull/604)) ([6c2de344c2](https://github.com/Devolutions/IronRDP/commit/6c2de344c2dd93ce9621834e0497ed7c3bfaf91a))

View file

@ -0,0 +1,35 @@
[package]
name = "ironrdp-cliprdr-native"
version = "0.5.0"
readme = "README.md"
description = "Native CLIPRDR static channel backend implementations for IronRDP"
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
authors.workspace = true
keywords.workspace = true
categories.workspace = true
[lib]
doctest = false
test = false
[dependencies]
ironrdp-cliprdr = { path = "../ironrdp-cliprdr", version = "0.5" } # public
ironrdp-core = { path = "../ironrdp-core", version = "0.1" }
tracing = { version = "0.1", features = ["log"] }
[target.'cfg(windows)'.dependencies]
windows = { version = "0.62", features = [
"Win32_Foundation",
"Win32_Graphics_Gdi",
"Win32_System_DataExchange",
"Win32_System_LibraryLoader",
"Win32_System_Memory",
"Win32_UI_Shell",
"Win32_UI_WindowsAndMessaging",
] }
[lints]
workspace = true

View file

@ -0,0 +1 @@
../../LICENSE-APACHE

View file

@ -0,0 +1 @@
../../LICENSE-MIT

View file

@ -0,0 +1,7 @@
# IronRDP CLIPRDR native backends
Native CLIPRDR backend implementations. Currently only Windows is supported.
This crate is part of the [IronRDP] project.
[IronRDP]: https://github.com/Devolutions/IronRDP

Some files were not shown because too many files have changed in this diff Show more