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
110
ARCHITECTURE.md
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
### Core Crates
|
||||||
|
|
||||||
|
- `crates/ironrdp`: meta crate re-exporting important crates.
|
||||||
|
- `crates/ironrdp-pdu`: PDU encoding and decoding (no I/O, trivial to fuzz). <!-- TODO: important types and traits (PduDecode, PduEncode…) -->
|
||||||
|
- `crates/ironrdp-graphics`: image processing primitives (no I/O, trivial to fuzz).
|
||||||
|
- `crates/ironrdp-connector`: state machines to drive an RDP connection sequence (no I/O, not _too_ hard to fuzz).
|
||||||
|
- `crates/ironrdp-session`: state machines to drive an RDP session (no I/O, not _too_ hard to fuzz).
|
||||||
|
- `crates/ironrdp-input`: utilities to manage and build input packets (no I/O).
|
||||||
|
- `crates/ironrdp-rdcleanpath`: RDCleanPath PDU structure used by IronRDP web client and Devolutions Gateway.
|
||||||
|
|
||||||
|
### Utility Crates
|
||||||
|
|
||||||
|
- `crates/ironrdp-async`: provides `Future`s wrapping the state machines conveniently.
|
||||||
|
- `crates/ironrdp-tokio`: `Framed*` traits implementation above `tokio`’s traits.
|
||||||
|
- `crates/ironrdp-futures`: `Framed*` traits implementation above `futures`’s traits.
|
||||||
|
- `crates/ironrdp-tls`: TLS boilerplate common with most IronRDP clients.
|
||||||
|
|
||||||
|
### Client Crates
|
||||||
|
|
||||||
|
- `crates/ironrdp-client`: portable RDP client without GPU acceleration using softbuffer and winit for windowing.
|
||||||
|
- `crates/ironrdp-web`: WebAssembly high-level bindings targeting web browsers.
|
||||||
|
- `crates/ironrdp-glutin-renderer`: `glutin` primitives for OpenGL rendering.
|
||||||
|
- `crates/ironrdp-client-glutin`: GPU-accelerated RDP client using glutin.
|
||||||
|
- `crates/ironrdp-replay-client`: utility tool to replay RDP graphics pipeline for debugging purposes.
|
||||||
|
- `web-client/iron-remote-gui`: core frontend UI used by `iron-svelte-client` as a Web Component.
|
||||||
|
- `web-client/iron-svelte-client`: web-based frontend using `Svelte` and `Material` frameworks.
|
||||||
|
|
||||||
|
### Private Crates
|
||||||
|
|
||||||
|
Crates that are only used inside the IronRDP project, not meant to be published.
|
||||||
|
|
||||||
|
- `crates/ironrdp-pdu-generators`: `proptest` generators for `ironrdp-pdu` types.
|
||||||
|
- `crates/ironrdp-session-generators`: `proptest` generators for `ironrdp-session` types.
|
||||||
|
- `fuzz`: fuzz targets for core crates.
|
||||||
|
- `xtask`: IronRDP’s free-form automation using Rust code.
|
||||||
|
|
||||||
|
## 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 crates (no system call such as `gethostname`)
|
||||||
|
- Keep non-portable code out of core 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 crates must never interact with the outside world. Only client and utility 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
|
||||||
|
|
||||||
|
#### 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.
|
29
Cargo.lock
generated
|
@ -1509,12 +1509,9 @@ name = "ironrdp-async"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
|
||||||
"ironrdp-connector",
|
"ironrdp-connector",
|
||||||
"ironrdp-pdu",
|
"ironrdp-pdu",
|
||||||
"tap",
|
"tap",
|
||||||
"tokio",
|
|
||||||
"tokio-util",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1526,18 +1523,16 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"exitcode",
|
"exitcode",
|
||||||
"futures-util",
|
|
||||||
"inquire",
|
"inquire",
|
||||||
"ironrdp",
|
"ironrdp",
|
||||||
"ironrdp-async",
|
|
||||||
"ironrdp-tls",
|
"ironrdp-tls",
|
||||||
|
"ironrdp-tokio",
|
||||||
"semver",
|
"semver",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"softbuffer",
|
"softbuffer",
|
||||||
"sspi",
|
"sspi",
|
||||||
"tap",
|
"tap",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"whoami",
|
"whoami",
|
||||||
|
@ -1556,6 +1551,15 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ironrdp-futures"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"ironrdp-async",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ironrdp-graphics"
|
name = "ironrdp-graphics"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -1669,6 +1673,16 @@ dependencies = [
|
||||||
"x509-cert",
|
"x509-cert",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ironrdp-tokio"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"ironrdp-async",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ironrdp-web"
|
name = "ironrdp-web"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -1681,7 +1695,7 @@ dependencies = [
|
||||||
"getrandom 0.2.8",
|
"getrandom 0.2.8",
|
||||||
"gloo-net",
|
"gloo-net",
|
||||||
"ironrdp",
|
"ironrdp",
|
||||||
"ironrdp-async",
|
"ironrdp-futures",
|
||||||
"ironrdp-rdcleanpath",
|
"ironrdp-rdcleanpath",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"semver",
|
"semver",
|
||||||
|
@ -3798,7 +3812,6 @@ checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
70
Cargo.toml
|
@ -1,52 +1,14 @@
|
||||||
[package]
|
|
||||||
name = "ironrdp"
|
|
||||||
version = "0.5.0"
|
|
||||||
readme = "README.md"
|
|
||||||
description = "A Rust implementation of the Microsoft Remote Desktop Protocol (RDP)"
|
|
||||||
edition.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
homepage.workspace = true
|
|
||||||
repository.workspace = true
|
|
||||||
authors.workspace = true
|
|
||||||
keywords.workspace = true
|
|
||||||
categories.workspace = true
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["pdu", "connector", "session"]
|
|
||||||
pdu = ["dep:ironrdp-pdu"]
|
|
||||||
connector = ["dep:ironrdp-connector"]
|
|
||||||
session = ["dep:ironrdp-session"]
|
|
||||||
graphics = ["dep:ironrdp-graphics"]
|
|
||||||
input = ["dep:ironrdp-input"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
ironrdp-pdu = { workspace = true, optional = true }
|
|
||||||
ironrdp-connector = { workspace = true, optional = true }
|
|
||||||
ironrdp-session = { workspace = true, optional = true }
|
|
||||||
ironrdp-graphics = { workspace = true, optional = true }
|
|
||||||
ironrdp-input = { workspace = true, optional = true }
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"crates/*",
|
"crates/*",
|
||||||
"xtask",
|
"xtask",
|
||||||
]
|
]
|
||||||
default-members = [
|
|
||||||
"crates/pdu",
|
|
||||||
"crates/connector",
|
|
||||||
"crates/session",
|
|
||||||
"crates/graphics",
|
|
||||||
"crates/input",
|
|
||||||
"crates/async",
|
|
||||||
"crates/client",
|
|
||||||
"crates/web",
|
|
||||||
]
|
|
||||||
|
|
||||||
# FIXME: fix compilation
|
# FIXME: fix compilation
|
||||||
exclude = [
|
exclude = [
|
||||||
"crates/client-glutin",
|
"crates/ironrdp-client-glutin",
|
||||||
"crates/glutin-renderer",
|
"crates/ironrdp-glutin-renderer",
|
||||||
"crates/replay-client",
|
"crates/ironrdp-replay-client",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
@ -60,18 +22,20 @@ categories = ["network-programming"]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
expect-test = "1"
|
expect-test = "1"
|
||||||
ironrdp = { version = "0.5", path = "." }
|
ironrdp-async = { version = "0.1", path = "crates/ironrdp-async" }
|
||||||
ironrdp-async = { version = "0.1", path = "crates/async" }
|
ironrdp-connector = { version = "0.1", path = "crates/ironrdp-connector" }
|
||||||
ironrdp-connector = { version = "0.1", path = "crates/connector" }
|
ironrdp-futures = { version = "0.1", path = "crates/ironrdp-futures" }
|
||||||
ironrdp-graphics = { version = "0.1", path = "crates/graphics" }
|
ironrdp-graphics = { version = "0.1", path = "crates/ironrdp-graphics" }
|
||||||
ironrdp-input = { version = "0.1", path = "crates/input" }
|
ironrdp-input = { version = "0.1", path = "crates/ironrdp-input" }
|
||||||
ironrdp-pdu = { version = "0.1", path = "crates/pdu" }
|
ironrdp-pdu-generators = { path = "crates/ironrdp-pdu-generators" }
|
||||||
ironrdp-pdu-generators = { path = "crates/pdu-generators" }
|
ironrdp-pdu-samples = { path = "crates/ironrdp-pdu-samples" }
|
||||||
ironrdp-pdu-samples = { path = "crates/pdu-samples" }
|
ironrdp-pdu = { version = "0.1", path = "crates/ironrdp-pdu" }
|
||||||
ironrdp-rdcleanpath = { version = "0.1", path = "crates/rdcleanpath" }
|
ironrdp-rdcleanpath = { version = "0.1", path = "crates/ironrdp-rdcleanpath" }
|
||||||
ironrdp-session = { version = "0.1", path = "crates/session" }
|
ironrdp-session-generators = { path = "crates/ironrdp-session-generators" }
|
||||||
ironrdp-session-generators = { path = "crates/session-generators" }
|
ironrdp-session = { version = "0.1", path = "crates/ironrdp-session" }
|
||||||
ironrdp-tls = { version = "0.1", path = "crates/tls" }
|
ironrdp-tls = { version = "0.1", path = "crates/ironrdp-tls" }
|
||||||
|
ironrdp-tokio = { version = "0.1", path = "crates/ironrdp-tokio" }
|
||||||
|
ironrdp = { version = "0.5", path = "crates/ironrdp" }
|
||||||
proptest = "1.1.0"
|
proptest = "1.1.0"
|
||||||
rstest = "0.17.0"
|
rstest = "0.17.0"
|
||||||
sspi = "0.8.1"
|
sspi = "0.8.1"
|
||||||
|
|
38
README.md
|
@ -35,40 +35,6 @@ Alternatively, you may change a few group policies using `gpedit.msc`:
|
||||||
|
|
||||||
5. Reboot.
|
5. Reboot.
|
||||||
|
|
||||||
## Architecture (Work In Progress…)
|
## Architecture
|
||||||
|
|
||||||
- `ironrdp` (root package): meta crate re-exporting important crates,
|
See the [ARCHITECTURE.md](./ARCHITECTURE.md) document.
|
||||||
- `ironrdp-pdu` (`crates/pdu`): PDU encoding and decoding (no I/O, trivial to fuzz),
|
|
||||||
- `ironrdp-graphics` (`crates/graphics`): image processing primitives (no I/O, trivial to fuzz),
|
|
||||||
- `ironrdp-connector` (`crates/connector`): state machines to drive an RDP connection sequence (no I/O, not _too_ hard to fuzz),
|
|
||||||
- `ironrdp-session` (`crates/session`): state machines to drive an RDP session (no I/O, not _too_ hard to fuzz),
|
|
||||||
- `ironrdp-input` (`crates/input`): utilities to manage and build input packets (no I/O),
|
|
||||||
- `ironrdp-async` (`crates/async`): provides `Future`s wrapping the state machines conveniently,
|
|
||||||
- `ironrdp-tls` (`crates/tls`): TLS boilerplate common with most IronRDP clients,
|
|
||||||
- `ironrdp-rdcleanpath` (`crates/rdcleanpath`): RDCleanPath PDU structure used by IronRDP web client and Devolutions Gateway,
|
|
||||||
- `ironrdp-client` (`crates/client`): Portable RDP client without GPU acceleration using softbuffer and winit for windowing,
|
|
||||||
- `ironrdp-web` (`crates/web`): WebAssembly high-level bindings targeting web browsers,
|
|
||||||
- `ironrdp-glutin-renderer` (`crates/glutin-renderer`): `glutin` primitives for OpenGL rendering,
|
|
||||||
- `ironrdp-client-glutin` (`crates/client-glutin`): GPU-accelerated RDP client using glutin,
|
|
||||||
- `ironrdp-replay-client` (`crates/replay-client`): utility tool to replay RDP graphics pipeline for debugging purposes,
|
|
||||||
- `ironrdp-pdu-generators` (`crates/pdu-generators`): `proptest` generators for `ironrdp-pdu` types,
|
|
||||||
- `ironrdp-session-generators` (`crates/session-generators`): `proptest` generators for `ironrdp-session` types,
|
|
||||||
- `iron-remote-gui` (`web-client/iron-remote-gui`): core frontend UI used by `iron-svelte-client` as a Web Component,
|
|
||||||
- `iron-svelte-client` (`web-client/iron-svelte-client`): web-based frontend using `Svelte` and `Material` frameworks,
|
|
||||||
- and finally, `ironrdp-fuzz` (`fuzz`): fuzz targets for core crates.
|
|
||||||
|
|
||||||
## General design
|
|
||||||
|
|
||||||
- Avoid I/O wherever possible
|
|
||||||
- Dependency injection when runtime information is necessary in core crates (no system call such as `gethostname`)
|
|
||||||
- Keep non-portable code out of core 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
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
|
@ -1,307 +0,0 @@
|
||||||
use std::io;
|
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
|
||||||
use ironrdp_pdu::PduHint;
|
|
||||||
|
|
||||||
// TODO: use static async fn / return position impl trait in traits where stabiziled (https://github.com/rust-lang/rust/issues/91611)
|
|
||||||
|
|
||||||
pub trait FramedRead: private::Sealed {
|
|
||||||
/// Reads from stream and fills internal buffer
|
|
||||||
fn read<'a>(
|
|
||||||
&'a mut self,
|
|
||||||
buf: &'a mut BytesMut,
|
|
||||||
) -> Pin<Box<dyn std::future::Future<Output = io::Result<usize>> + 'a>>
|
|
||||||
where
|
|
||||||
Self: 'a;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait FramedWrite: private::Sealed {
|
|
||||||
/// Writes an entire buffer into this stream.
|
|
||||||
fn write_all<'a>(&'a mut self, buf: &'a [u8]) -> Pin<Box<dyn std::future::Future<Output = io::Result<()>> + 'a>>
|
|
||||||
where
|
|
||||||
Self: 'a;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Framed<S> {
|
|
||||||
stream: S,
|
|
||||||
buf: BytesMut,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> Framed<S> {
|
|
||||||
pub fn new(stream: S) -> Self {
|
|
||||||
Self {
|
|
||||||
stream,
|
|
||||||
buf: BytesMut::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "tokio")]
|
|
||||||
impl<S> Framed<TokioCompat<S>> {
|
|
||||||
pub fn tokio_new(stream: S) -> Self {
|
|
||||||
Self {
|
|
||||||
stream: TokioCompat { inner: stream },
|
|
||||||
buf: BytesMut::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tokio_into_inner(self) -> (S, BytesMut) {
|
|
||||||
(self.stream.inner, self.buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tokio_into_inner_no_leftover(self) -> S {
|
|
||||||
let (stream, leftover) = self.tokio_into_inner();
|
|
||||||
assert_eq!(leftover.len(), 0, "unexpected leftover");
|
|
||||||
stream
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tokio_get_inner(&self) -> (&S, &BytesMut) {
|
|
||||||
(&self.stream.inner, &self.buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tokio_get_inner_mut(&mut self) -> (&mut S, &mut BytesMut) {
|
|
||||||
(&mut self.stream.inner, &mut self.buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "futures")]
|
|
||||||
impl<S> Framed<FuturesCompat<S>> {
|
|
||||||
pub fn futures_new(stream: S) -> Self {
|
|
||||||
Self {
|
|
||||||
stream: FuturesCompat { inner: stream },
|
|
||||||
buf: BytesMut::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn futures_into_inner(self) -> (S, BytesMut) {
|
|
||||||
(self.stream.inner, self.buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn futures_into_inner_no_leftover(self) -> S {
|
|
||||||
let (stream, leftover) = self.futures_into_inner();
|
|
||||||
debug_assert_eq!(leftover.len(), 0, "unexpected leftover");
|
|
||||||
stream
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn futures_get_inner(&self) -> (&S, &BytesMut) {
|
|
||||||
(&self.stream.inner, &self.buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn futures_get_inner_mut(&mut self) -> (&mut S, &mut BytesMut) {
|
|
||||||
(&mut self.stream.inner, &mut self.buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> Framed<S>
|
|
||||||
where
|
|
||||||
S: FramedRead,
|
|
||||||
{
|
|
||||||
/// Reads from stream and fills internal buffer
|
|
||||||
pub async fn read(&mut self) -> io::Result<usize> {
|
|
||||||
self.stream.read(&mut self.buf).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn read_exact(&mut self, length: usize) -> io::Result<Bytes> {
|
|
||||||
loop {
|
|
||||||
if self.buf.len() >= length {
|
|
||||||
return Ok(self.buf.split_to(length).freeze());
|
|
||||||
} else {
|
|
||||||
self.buf.reserve(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"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn read_pdu(&mut self) -> io::Result<(ironrdp_pdu::Action, Bytes)> {
|
|
||||||
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::new(io::ErrorKind::Other, e)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn read_by_hint(&mut self, hint: &dyn PduHint) -> io::Result<Bytes> {
|
|
||||||
loop {
|
|
||||||
match hint
|
|
||||||
.find_size(self.peek())
|
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?
|
|
||||||
{
|
|
||||||
Some(length) => {
|
|
||||||
return self.read_exact(length).await;
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let len = self.read().await?;
|
|
||||||
|
|
||||||
// Handle EOF
|
|
||||||
if len == 0 {
|
|
||||||
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "not enough bytes"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> Framed<S>
|
|
||||||
where
|
|
||||||
S: FramedWrite,
|
|
||||||
{
|
|
||||||
/// Reads from stream and fills internal buffer
|
|
||||||
pub async fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
|
|
||||||
self.stream.write_all(buf).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "tokio")]
|
|
||||||
pub struct TokioCompat<S> {
|
|
||||||
inner: S,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "tokio")]
|
|
||||||
mod tokio_impl {
|
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
impl<S> private::Sealed for TokioCompat<S> {}
|
|
||||||
|
|
||||||
impl<S> FramedRead for TokioCompat<S>
|
|
||||||
where
|
|
||||||
S: Unpin + AsyncRead,
|
|
||||||
{
|
|
||||||
fn read<'a>(
|
|
||||||
&'a mut self,
|
|
||||||
buf: &'a mut BytesMut,
|
|
||||||
) -> Pin<Box<dyn std::future::Future<Output = io::Result<usize>> + 'a>>
|
|
||||||
where
|
|
||||||
Self: 'a,
|
|
||||||
{
|
|
||||||
use tokio::io::AsyncReadExt as _;
|
|
||||||
|
|
||||||
Box::pin(async { self.inner.read_buf(buf).await })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> FramedWrite for TokioCompat<S>
|
|
||||||
where
|
|
||||||
S: Unpin + AsyncWrite,
|
|
||||||
{
|
|
||||||
fn write_all<'a>(&'a mut self, buf: &'a [u8]) -> Pin<Box<dyn std::future::Future<Output = io::Result<()>> + 'a>>
|
|
||||||
where
|
|
||||||
Self: 'a,
|
|
||||||
{
|
|
||||||
use tokio::io::AsyncWriteExt as _;
|
|
||||||
|
|
||||||
Box::pin(async {
|
|
||||||
self.inner.write_all(buf).await?;
|
|
||||||
self.inner.flush().await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "futures")]
|
|
||||||
pub struct FuturesCompat<S> {
|
|
||||||
pub(super) inner: S,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "futures")]
|
|
||||||
mod futures_impl {
|
|
||||||
pub use futures_util::io::{AsyncRead, AsyncWrite};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
impl<S> private::Sealed for FuturesCompat<S> {}
|
|
||||||
|
|
||||||
impl<S> FramedRead for FuturesCompat<S>
|
|
||||||
where
|
|
||||||
S: Unpin + AsyncRead,
|
|
||||||
{
|
|
||||||
fn read<'a>(
|
|
||||||
&'a mut self,
|
|
||||||
buf: &'a mut BytesMut,
|
|
||||||
) -> Pin<Box<dyn std::future::Future<Output = io::Result<usize>> + 'a>>
|
|
||||||
where
|
|
||||||
Self: 'a,
|
|
||||||
{
|
|
||||||
use futures_util::io::AsyncReadExt as _;
|
|
||||||
|
|
||||||
Box::pin(async {
|
|
||||||
// NOTE(perf): tokio implementation is more efficient
|
|
||||||
let mut read_bytes = [0u8; 1024];
|
|
||||||
let len = self.inner.read(&mut read_bytes[..]).await?;
|
|
||||||
buf.extend_from_slice(&read_bytes[..len]);
|
|
||||||
|
|
||||||
Ok(len)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> FramedWrite for FuturesCompat<S>
|
|
||||||
where
|
|
||||||
S: Unpin + AsyncWrite,
|
|
||||||
{
|
|
||||||
fn write_all<'a>(&'a mut self, buf: &'a [u8]) -> Pin<Box<dyn std::future::Future<Output = io::Result<()>> + 'a>>
|
|
||||||
where
|
|
||||||
Self: 'a,
|
|
||||||
{
|
|
||||||
use futures_util::io::AsyncWriteExt as _;
|
|
||||||
|
|
||||||
Box::pin(async {
|
|
||||||
self.inner.write_all(buf).await?;
|
|
||||||
self.inner.flush().await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod private {
|
|
||||||
pub trait Sealed {}
|
|
||||||
}
|
|
|
@ -11,25 +11,10 @@ authors.workspace = true
|
||||||
keywords.workspace = true
|
keywords.workspace = true
|
||||||
categories.workspace = true
|
categories.workspace = true
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["tokio"]
|
|
||||||
tokio = ["dep:tokio", "dep:tokio-util"]
|
|
||||||
futures = ["dep:futures-util"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Protocols
|
bytes = "1"
|
||||||
ironrdp-pdu.workspace = true
|
|
||||||
ironrdp-connector.workspace = true
|
ironrdp-connector.workspace = true
|
||||||
|
ironrdp-pdu.workspace = true
|
||||||
# ironrdp-session.workspace = true
|
# ironrdp-session.workspace = true
|
||||||
|
tap = "1"
|
||||||
# Logging
|
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
|
|
||||||
# Utils
|
|
||||||
bytes = "1.4.0"
|
|
||||||
tap = "1.0.1"
|
|
||||||
|
|
||||||
# Async
|
|
||||||
tokio = { version = "1.25.0", features = ["io-util"], optional = true }
|
|
||||||
tokio-util = { version = "0.7.7", features = ["codec"], optional = true }
|
|
||||||
futures-util = { version = "0.3.26", features = ["io"], optional = true }
|
|
157
crates/ironrdp-async/src/framed.rs
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
use std::io;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use bytes::{Bytes, BytesMut};
|
||||||
|
use ironrdp_pdu::PduHint;
|
||||||
|
|
||||||
|
// TODO: use static async fn / return position impl trait in traits when stabiziled (https://github.com/rust-lang/rust/issues/91611)
|
||||||
|
|
||||||
|
pub trait FramedRead {
|
||||||
|
/// Reads from stream and fills internal buffer
|
||||||
|
fn read<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
buf: &'a mut BytesMut,
|
||||||
|
) -> Pin<Box<dyn std::future::Future<Output = io::Result<usize>> + 'a>>
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FramedWrite {
|
||||||
|
/// Writes an entire buffer into this stream.
|
||||||
|
fn write_all<'a>(&'a mut self, buf: &'a [u8]) -> Pin<Box<dyn std::future::Future<Output = io::Result<()>> + 'a>>
|
||||||
|
where
|
||||||
|
Self: '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 {
|
||||||
|
stream: S::from_inner(stream),
|
||||||
|
buf: BytesMut::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
{
|
||||||
|
/// Reads from stream and fills internal buffer
|
||||||
|
pub async fn read(&mut self) -> io::Result<usize> {
|
||||||
|
self.stream.read(&mut self.buf).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read_exact(&mut self, length: usize) -> io::Result<Bytes> {
|
||||||
|
loop {
|
||||||
|
if self.buf.len() >= length {
|
||||||
|
return Ok(self.buf.split_to(length).freeze());
|
||||||
|
} else {
|
||||||
|
self.buf.reserve(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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read_pdu(&mut self) -> io::Result<(ironrdp_pdu::Action, Bytes)> {
|
||||||
|
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::new(io::ErrorKind::Other, e)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read_by_hint(&mut self, hint: &dyn PduHint) -> io::Result<Bytes> {
|
||||||
|
loop {
|
||||||
|
match hint
|
||||||
|
.find_size(self.peek())
|
||||||
|
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?
|
||||||
|
{
|
||||||
|
Some(length) => {
|
||||||
|
return self.read_exact(length).await;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let len = self.read().await?;
|
||||||
|
|
||||||
|
// Handle EOF
|
||||||
|
if len == 0 {
|
||||||
|
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "not enough bytes"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Framed<S>
|
||||||
|
where
|
||||||
|
S: FramedWrite,
|
||||||
|
{
|
||||||
|
/// Reads from stream and fills internal buffer
|
||||||
|
pub async fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
|
||||||
|
self.stream.write_all(buf).await
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,7 +21,7 @@ native-tls = ["ironrdp-tls/native-tls"]
|
||||||
# Protocols
|
# Protocols
|
||||||
ironrdp = { workspace = true, features = ["input", "graphics"] }
|
ironrdp = { workspace = true, features = ["input", "graphics"] }
|
||||||
ironrdp-tls.workspace = true
|
ironrdp-tls.workspace = true
|
||||||
ironrdp-async = { workspace = true, features = ["tokio"] }
|
ironrdp-tokio.workspace = true
|
||||||
sspi = { workspace = true, features = ["network_client"] } # TODO: enable dns_resolver at some point
|
sspi = { workspace = true, features = ["network_client"] } # TODO: enable dns_resolver at some point
|
||||||
|
|
||||||
# GUI
|
# GUI
|
||||||
|
@ -39,8 +39,6 @@ tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
||||||
|
|
||||||
# Async, futures
|
# Async, futures
|
||||||
tokio = { version = "1", features = ["full"]}
|
tokio = { version = "1", features = ["full"]}
|
||||||
tokio-util = { version = "0.7.7", features = ["compat"] }
|
|
||||||
futures-util = "0.3"
|
|
||||||
|
|
||||||
# Utils
|
# Utils
|
||||||
chrono = "0.4.24"
|
chrono = "0.4.24"
|
|
@ -78,7 +78,7 @@ enum RdpControlFlow {
|
||||||
TerminatedGracefully,
|
TerminatedGracefully,
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpgradedFramed = ironrdp_async::Framed<ironrdp_async::TokioCompat<ironrdp_tls::TlsStream<TcpStream>>>;
|
type UpgradedFramed = ironrdp_tokio::TokioFramed<ironrdp_tls::TlsStream<TcpStream>>;
|
||||||
|
|
||||||
async fn connect(config: &Config) -> connector::Result<(connector::ConnectionResult, UpgradedFramed)> {
|
async fn connect(config: &Config) -> connector::Result<(connector::ConnectionResult, UpgradedFramed)> {
|
||||||
let server_addr = config
|
let server_addr = config
|
||||||
|
@ -90,29 +90,29 @@ async fn connect(config: &Config) -> connector::Result<(connector::ConnectionRes
|
||||||
.await
|
.await
|
||||||
.map_err(|e| connector::Error::new("TCP connect").with_custom(e))?;
|
.map_err(|e| connector::Error::new("TCP connect").with_custom(e))?;
|
||||||
|
|
||||||
let mut framed = ironrdp_async::Framed::tokio_new(stream);
|
let mut framed = ironrdp_tokio::TokioFramed::new(stream);
|
||||||
|
|
||||||
let mut connector = connector::ClientConnector::new(config.connector.clone())
|
let mut connector = connector::ClientConnector::new(config.connector.clone())
|
||||||
.with_server_addr(server_addr)
|
.with_server_addr(server_addr)
|
||||||
.with_server_name(&config.destination)
|
.with_server_name(&config.destination)
|
||||||
.with_credssp_client_factory(Box::new(RequestClientFactory));
|
.with_credssp_client_factory(Box::new(RequestClientFactory));
|
||||||
|
|
||||||
let should_upgrade = ironrdp_async::connect_begin(&mut framed, &mut connector).await?;
|
let should_upgrade = ironrdp_tokio::connect_begin(&mut framed, &mut connector).await?;
|
||||||
|
|
||||||
debug!("TLS upgrade");
|
debug!("TLS upgrade");
|
||||||
|
|
||||||
// Ensure there is no leftover
|
// Ensure there is no leftover
|
||||||
let initial_stream = framed.tokio_into_inner_no_leftover();
|
let initial_stream = framed.into_inner_no_leftover();
|
||||||
|
|
||||||
let (upgraded_stream, server_public_key) = ironrdp_tls::upgrade(initial_stream, config.destination.name())
|
let (upgraded_stream, server_public_key) = ironrdp_tls::upgrade(initial_stream, config.destination.name())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| connector::Error::new("TLS upgrade").with_custom(e))?;
|
.map_err(|e| connector::Error::new("TLS upgrade").with_custom(e))?;
|
||||||
|
|
||||||
let upgraded = ironrdp_async::mark_as_upgraded(should_upgrade, &mut connector, server_public_key);
|
let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, &mut connector, server_public_key);
|
||||||
|
|
||||||
let mut upgraded_framed = ironrdp_async::Framed::tokio_new(upgraded_stream);
|
let mut upgraded_framed = ironrdp_tokio::TokioFramed::new(upgraded_stream);
|
||||||
|
|
||||||
let connection_result = ironrdp_async::connect_finalize(upgraded, &mut upgraded_framed, connector).await?;
|
let connection_result = ironrdp_tokio::connect_finalize(upgraded, &mut upgraded_framed, connector).await?;
|
||||||
|
|
||||||
Ok((connection_result, upgraded_framed))
|
Ok((connection_result, upgraded_framed))
|
||||||
}
|
}
|
17
crates/ironrdp-futures/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "ironrdp-futures"
|
||||||
|
version = "0.1.0"
|
||||||
|
readme = "README.md"
|
||||||
|
description = "`Framed*` traits implementation above futures’s traits"
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
keywords.workspace = true
|
||||||
|
categories.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bytes = "1"
|
||||||
|
futures-util = { version = "0.3.26", features = ["io"] }
|
||||||
|
ironrdp-async.workspace = true
|
76
crates/ironrdp-futures/src/lib.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
use std::io;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use futures_util::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
|
pub use ironrdp_async::*;
|
||||||
|
|
||||||
|
pub type FuturesFramed<S> = Framed<FuturesStream<S>>;
|
||||||
|
|
||||||
|
pub struct FuturesStream<S> {
|
||||||
|
inner: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> StreamWrapper for FuturesStream<S> {
|
||||||
|
type InnerStream = S;
|
||||||
|
|
||||||
|
fn from_inner(stream: Self::InnerStream) -> Self {
|
||||||
|
Self { inner: stream }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_inner(self) -> Self::InnerStream {
|
||||||
|
self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_inner(&self) -> &Self::InnerStream {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_inner_mut(&mut self) -> &mut Self::InnerStream {
|
||||||
|
&mut self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> FramedRead for FuturesStream<S>
|
||||||
|
where
|
||||||
|
S: Unpin + AsyncRead,
|
||||||
|
{
|
||||||
|
fn read<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
buf: &'a mut BytesMut,
|
||||||
|
) -> Pin<Box<dyn std::future::Future<Output = io::Result<usize>> + 'a>>
|
||||||
|
where
|
||||||
|
Self: 'a,
|
||||||
|
{
|
||||||
|
use futures_util::io::AsyncReadExt as _;
|
||||||
|
|
||||||
|
Box::pin(async {
|
||||||
|
// NOTE(perf): tokio implementation is more efficient
|
||||||
|
let mut read_bytes = [0u8; 1024];
|
||||||
|
let len = self.inner.read(&mut read_bytes[..]).await?;
|
||||||
|
buf.extend_from_slice(&read_bytes[..len]);
|
||||||
|
|
||||||
|
Ok(len)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> FramedWrite for FuturesStream<S>
|
||||||
|
where
|
||||||
|
S: Unpin + AsyncWrite,
|
||||||
|
{
|
||||||
|
fn write_all<'a>(&'a mut self, buf: &'a [u8]) -> Pin<Box<dyn std::future::Future<Output = io::Result<()>> + 'a>>
|
||||||
|
where
|
||||||
|
Self: 'a,
|
||||||
|
{
|
||||||
|
use futures_util::io::AsyncWriteExt as _;
|
||||||
|
|
||||||
|
Box::pin(async {
|
||||||
|
self.inner.write_all(buf).await?;
|
||||||
|
self.inner.flush().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |