Skip to content

Commit 9e1ea0a

Browse files
feat(protocols): add NOW execution client (#82)
1 parent 42d84c1 commit 9e1ea0a

11 files changed

Lines changed: 2395 additions & 1 deletion

File tree

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ keywords = ["rdp", "remote-desktop", "network", "client", "protocol"]
1616
categories = ["network-programming"]
1717

1818
[workspace.dependencies]
19-
now-proto-pdu = { version = "0.1", path = "protocols/rust/now-proto-pdu" }
19+
now-proto-pdu = { version = "0.4", path = "protocols/rust/now-proto-pdu" }
2020
now-proto-fuzzing = { version = "0.1", path = "protocols/rust/now-proto-fuzzing" }
2121

2222
[profile.test.package.proptest]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "now-client"
3+
version = "0.1.0"
4+
readme = "README.md"
5+
description = "High-level Tokio client for NOW execution channels"
6+
edition.workspace = true
7+
license.workspace = true
8+
homepage.workspace = true
9+
repository.workspace = true
10+
authors.workspace = true
11+
keywords.workspace = true
12+
categories.workspace = true
13+
14+
[lints]
15+
workspace = true
16+
17+
[dependencies]
18+
now-proto-pdu = { workspace = true, features = ["std"] }
19+
thiserror = "2"
20+
tokio = { version = "1.52", features = ["io-util", "macros", "rt", "sync", "time"] }
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# NOW client
2+
3+
`now-client` is a high-level, transport-agnostic Rust client for the NOW execution
4+
channel. It accepts a caller-provided Tokio `AsyncRead + AsyncWrite` byte stream;
5+
consumers create DVCs, pipes, sockets, and replacement clients after reconnects.
6+
7+
Connect with `NowClient::connect`, then query negotiated `NowCapabilities` from the
8+
returned handle. `process`, `batch`, `win_ps`, and `pwsh` submit tracked executions;
9+
their `_detached` counterparts submit detached executions. `run` is submission-only.
10+
The client defensively negotiates capabilities, applies bounded frame/command/event
11+
queues, and permits **one tracked execution at a time per stream**. Tracked operations
12+
expose raw `Vec<u8>` stdout/stderr chunks, stdin forwarding, normal cancellation, and
13+
terminal status.
14+
15+
Run and detached requests are submission-only. Gateway may emit an immediate
16+
Started/Data/Result sequence for Run; the client records and discards those matching
17+
frames so they cannot affect the next tracked operation. This recent-session quarantine
18+
is bounded and evicts its oldest entry; evicted Run traffic remains harmless because
19+
session IDs are never reused. This crate deliberately does not provide Abort, Shell
20+
submission, DVC/pipe setup, reconnect policy, or retained operation output.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use core::time::Duration;
2+
3+
use now_proto_pdu::{NowChannelCapsetMsg, NowExecCapsetFlags, NowProtoVersion};
4+
5+
use crate::NowClientError;
6+
7+
/// Capabilities mutually supported by the client and its connected peer.
8+
///
9+
/// The value is always calculated as an intersection with the local advertised capset,
10+
/// even if the peer echoes unsupported flags.
11+
#[derive(Clone, Debug, PartialEq, Eq)]
12+
pub struct NowCapabilities {
13+
capset: NowChannelCapsetMsg,
14+
}
15+
16+
impl NowCapabilities {
17+
pub(crate) fn negotiate(
18+
requested: &NowChannelCapsetMsg,
19+
peer: &NowChannelCapsetMsg,
20+
) -> Result<Self, NowClientError> {
21+
if requested.version().major != peer.version().major {
22+
return Err(NowClientError::IncompatibleVersion {
23+
client: requested.version(),
24+
peer: peer.version(),
25+
});
26+
}
27+
28+
Ok(Self {
29+
capset: requested.downgrade(peer),
30+
})
31+
}
32+
33+
/// Returns the negotiated capability-set PDU.
34+
pub fn capset(&self) -> &NowChannelCapsetMsg {
35+
&self.capset
36+
}
37+
38+
/// Returns the negotiated NOW protocol version.
39+
pub fn version(&self) -> NowProtoVersion {
40+
self.capset.version()
41+
}
42+
43+
/// Returns the negotiated heartbeat interval, if either peer requested one.
44+
pub fn heartbeat_interval(&self) -> Option<Duration> {
45+
self.capset.heartbeat_interval()
46+
}
47+
48+
/// Returns whether the generic Run style is available.
49+
pub fn supports_run(&self) -> bool {
50+
self.has(NowExecCapsetFlags::STYLE_RUN)
51+
}
52+
53+
/// Returns whether CreateProcess execution is available.
54+
pub fn supports_process(&self) -> bool {
55+
self.has(NowExecCapsetFlags::STYLE_PROCESS)
56+
}
57+
58+
/// Returns whether Batch execution is available.
59+
pub fn supports_batch(&self) -> bool {
60+
self.has(NowExecCapsetFlags::STYLE_BATCH)
61+
}
62+
63+
/// Returns whether Windows PowerShell execution is available.
64+
pub fn supports_win_ps(&self) -> bool {
65+
self.has(NowExecCapsetFlags::STYLE_WINPS)
66+
}
67+
68+
/// Returns whether PowerShell 7 execution is available.
69+
pub fn supports_pwsh(&self) -> bool {
70+
self.has(NowExecCapsetFlags::STYLE_PWSH)
71+
}
72+
73+
/// Returns whether tracked I/O redirection is available.
74+
pub fn supports_io_redirection(&self) -> bool {
75+
self.has(NowExecCapsetFlags::IO_REDIRECTION) && self.at_least(1, 3)
76+
}
77+
78+
/// Returns whether UTF-8 and Unicode-console encoding controls are available.
79+
pub fn supports_unicode_console(&self) -> bool {
80+
self.has(NowExecCapsetFlags::UNICODE_CONSOLE) && self.version().supports_exec_unicode_console()
81+
}
82+
83+
pub(crate) fn supports_detached(&self) -> bool {
84+
self.at_least(1, 4)
85+
}
86+
87+
fn has(&self, capability: NowExecCapsetFlags) -> bool {
88+
self.capset.exec_capset().contains(capability)
89+
}
90+
91+
fn at_least(&self, major: u16, minor: u16) -> bool {
92+
self.version() >= NowProtoVersion { major, minor }
93+
}
94+
}

0 commit comments

Comments
 (0)