feat!: extract sequence contract - #1753
Conversation
Create ironrdp-sequence, a new sans-I/O core-tier crate that owns the `Sequence` trait and its supporting contract types previously defined in ironrdp-connector: `State`, `Written`, `SequenceError`/ `SequenceErrorKind`/`SequenceResult`, the `general_err!`/`reason_err!`/ `custom_err!` macros, `ServerName`, and `DesktopSize`. Also move `MonotonicInstant` out of ironrdp-core into ironrdp-sequence, alongside `Sequence`. Per the PR #1530 audit, the clock read by a sequence's `step` is part of the sans-I/O contract, not a foundational encoding primitive, so it no longer belongs in ironrdp-core. `MonotonicInstant` and `DesktopSize` need no allocation and stay available unconditionally. Everything else needs `ironrdp-pdu`, so it is gated behind a default-disabled `state-machine` feature: this keeps ironrdp-rdpeudp, which depends on this crate only for `MonotonicInstant`, from acquiring `ironrdp-pdu`'s much larger dependency tree. A new xtask dependency-edge check enforces this. ironrdp-connector and ironrdp-rdpeudp both keep permanent facade re-exports of everything they used to define directly, so existing `ironrdp_connector::{Sequence, MonotonicInstant, ...}` and `ironrdp_rdpeudp::MonotonicInstant` paths are unaffected. Stacked on #1751 (branch cbenoit-introduce-sequenceerror-sspi-free-sequen). Closes #1426 once this stack lands on master. BREAKING CHANGE: `ironrdp_core::MonotonicInstant` is removed. Use `ironrdp_sequence::MonotonicInstant`, or the stable facade re-exports at `ironrdp_connector::MonotonicInstant` / `ironrdp_rdpeudp::MonotonicInstant`. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Enable `all-features` under `[package.metadata.docs.rs]` so docs.rs renders `ironrdp-sequence` with `state-machine`/`alloc` on instead of the default-disabled feature set, which left 12 of 14 public items undocumented and produced 17 broken intra-doc links. Restore the `$crate::__private::format!` indirection in `reason_err!` that a prior self-review mistakenly reverted to a bare `format!` call. Macro invocations inside a `macro_rules!` body resolve through the caller's scope, not the definition site, so a no_std consumer without `format!` imported could not compile this macro with the bare call. Add `tests/reason_err.rs`, a `#![no_implicit_prelude]` regression test that reproduces that caller shape and fails to compile without the fix. Register `sequence` as a canonical PR title scope in `xtask/src/pr.rs` (and the `commit-scope` skill), following the same pattern used for `rdpei` in bbef8d9, and restore the PR title to use it instead of the scope-less stopgap title. Condense the `ironrdp-sequence` ARCHITECTURE.md entry to sentence-per- line with the `**Architectural Invariant**:` prefix used elsewhere in the file, trim the redundant feature-split rationale restated in `time.rs`'s doc comment, and correct the README's claim that this extraction already stops `ironrdp-acceptor` from depending on `ironrdp-connector`: it still does, and that rewiring is a follow-up. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
fuzz/Cargo.lock never picked up the new ironrdp-sequence crate introduced by 5cfbfe7, so cargo xtask check locks -v only caught the drift once CI ran a fuzz-workspace command against it: the check itself only diffs the lock files after some other invocation touches them, it does not proactively validate each workspace. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Mechanical extraction of the Sequence contract (State, Written, SequenceError, ServerName, DesktopSize, NegotiationFailure, MonotonicInstant, macros) from ironrdp-connector/ironrdp-core into a new ironrdp-sequence crate, with permanent facade re-exports preserving all existing import paths. Types and logic are moved verbatim; feature gating (state-machine/alloc/std) is coherent and matches the stated dependency-avoidance goal, verified by a new forbidden-edge check and cargo-tree evidence in the PR body. The move also surfaces and fixes a real macro-hygiene bug (reason_err!'s bare format! call would break for no_std callers of the relocated macro), backed by a regression test. No wire-format, encode/decode, or state-transition behavior changes.
Protocol analysis: accepted — Independent diff review confirms the handoff's assessment: every moved item (SequenceError/Kind, ServerName's sanitize_server_name, NegotiationFailure's FailureCode match, State/Written, MonotonicInstant's arithmetic, the three macros) is byte-for-byte identical to its pre-move definition; only module paths, feature-gate cfg attributes, and doc comments changed. No PDU field, encode/decode path, or state machine transition is touched.
|
|
||
| `ironrdp-connector` and `ironrdp-rdpeudp` both re-export everything they previously defined directly from this crate, so existing `ironrdp_connector::{Sequence, MonotonicInstant, ...}` and `ironrdp_rdpeudp::MonotonicInstant` import paths remain stable. | ||
|
|
||
| **Architectural Invariant**: this crate must never depend on `ironrdp-connector`, `sspi`, or any RDP connection-flow logic; it is the generic contract that `ironrdp-connector` (and any future accept-side or session-side sequence) is built on top of, not the other way around. |
There was a problem hiding this comment.
non_blocking / low: ironrdp-sequence is placed in the Core Tier, which carries a 'must be fuzzed' architectural invariant, but ServerName::sanitize_server_name (which parses untrusted-shaped socket-address/hostname strings) has no fuzz target, and none is added by this PR. This is not a regression introduced here (the code was equally unfuzzed in ironrdp-connector), so it doesn't block this pure-extraction PR, but it's now a Core Tier crate carrying an unmet invariant worth a follow-up.
Stacked on #1751 (base branch:
cbenoit-introduce-sequenceerror-sspi-free-sequen). This targets that branch, notmaster; it should merge after #1751.Implements #1426. GitHub only auto-closes an issue when the closing PR lands on the repository's default branch, so
Closes #1426below will only take effect once this stack (starting with #1751) reachesmaster— not on merge of this PR alone.What
Creates
ironrdp-sequence, a new sans-I/O core-tier crate that owns theSequencetrait and its supporting contract types previously defined inironrdp-connector:State,Written,SequenceError/SequenceErrorKind/SequenceResult, thegeneral_err!/reason_err!/custom_err!macros,ServerName, andDesktopSize.Also moves
MonotonicInstantout ofironrdp-coreintoironrdp-sequence, alongsideSequence. Per the maintainer audit on #1530, the clock read by a sequence'sstepis part of the sans-I/O contract, not a foundational encoding primitive, so it no longer belongs inironrdp-core.Design decisions
MonotonicInstantandDesktopSizeneed no allocation and stay available unconditionally (no features required). Everything else needsironrdp-pdu(forPduHintand negotiation failure codes), so it is gated behind a default-disabledstate-machinefeature. This keepsironrdp-rdpeudp— which depends on this crate only forMonotonicInstant— from acquiringironrdp-pdu's much larger dependency tree (der-parser,x509-cert,pkcs1, ...) just for the clock type. A newxtask check dependenciesforbidden-edge entry (ironrdp-rdpeudp→ironrdp-pdu) enforces this permanently.allocvsstate-machine:ServerNameonly needsalloc::string::String, so it's gated on plainallocrather than the heavierstate-machine.ironrdp-connectorandironrdp-rdpeudpboth keep permanent re-exports of everything they previously defined directly, so existingironrdp_connector::{Sequence, MonotonicInstant, State, Written, SequenceError, ServerName, DesktopSize, ...}andironrdp_rdpeudp::MonotonicInstantimport paths are unaffected.ironrdp-sequenceisversion = "0.1.0"with nopublish = false, because published crates (ironrdp-connector) depend on it —cargo publishrequires all path dependencies to be resolvable from the registry.ironrdp-sequencehas no dependency onironrdp-connector,sspi, or any CredSSP/RDP connection-flow logic — verified viacargo treeand the newxtask check dependenciesguard.Breaking change
ironrdp_core::MonotonicInstantis removed. This breaks the path as it exists onmaster/unreleasedironrdp-core; the type was never part of a publishedironrdp-corerelease (it does not exist inironrdp-corev0.2.1 on crates.io), so no published consumer is affected. Direct consumers onmastermust switch toironrdp_sequence::MonotonicInstant, or use the stable facade re-exports atironrdp_connector::MonotonicInstant/ironrdp_rdpeudp::MonotonicInstant(unaffected by this change).Explicitly out of scope
This PR is strictly the extraction layer. It does not touch the FFI timestamp/API surface (e.g. how the C#/native FFI layer supplies
MonotonicInstantreadings) — that is a separate follow-up.Checks
cargo check --workspace(default features): clean.cargo xtask check fmt -v,check lints -v,check tests -v,check locks -v: all pass.cargo xtask check features --case workspace/powerset-foundation(now includesironrdp-sequence): 29/29 sub-checks pass.cargo xtask check dependencies: all forbidden edges (including the newironrdp-rdpeudp→ironrdp-pduone) confirmed absent.cargo tree -p ironrdp-rdpeudp -e no-dev -i ironrdp-pdu: confirmsironrdp-pduis absent fromironrdp-rdpeudp's dependency tree.cargo doc -p ironrdp-sequence --no-deps --all-features: clean, no broken intra-doc links.cargo test -p ironrdp-sequence --no-default-features --features state-machineand--features state-machine,std:reason_err!'s macro-hygiene regression test passes in both configurations.--all-featuresissues inironrdp-tls/ironrdp-rdpeusb(mutually-exclusive TLS backend features) reproduce identically on the base branch and are out of scope here.