[F10 + F11] mxaccess-rpc: structural ports for ResolveOxid2 + RemAddRef/RemRelease
rust / build / test / clippy / fmt (push) Has been cancelled
rust / build / test / clippy / fmt (push) Has been cancelled
Closes F10 and F11 per option (b) of each followup's resolve
criterion: hand-rolled body codecs derived from the [MS-DCOM]
spec, ship structurally with no live fixture (the .NET reference
doesn't call these opnums), validate against captured frames when
they become available.
F10 — IObjectExporter::ResolveOxid2 (opnum 4):
Per [MS-DCOM] §3.1.2.5.1.4. New parse_resolve_oxid2_result
mirrors parse_resolve_oxid_result exactly except for the extra
4-byte COMVERSION slot (u16 major + u16 minor) between
authn_hint and error_status. Trailing-fields check tightens
from 24 bytes (opnum 0) to 28 bytes (opnum 4). New ComVersion +
ResolveOxid2Result types. referent_id=0 short-circuit returns
empty bindings + default ComVersion + tail-status, matching
opnum 0's pattern.
F11 — IRemUnknown::RemAddRef + RemRelease (opnums 4 and 5):
Per [MS-DCOM] §3.1.1.5.6 + §2.2.19 (REMINTERFACEREF). Both
opnums share the wire shape, so:
- encode_rem_add_ref_request + encode_rem_release_request
both delegate to a shared encode_remref_array_request
helper.
- parse_remref_response is shared between them too — they
have an identical OrpcThat + referent_id + max_count +
N×HRESULT + error_code layout.
New RemInterfaceRef struct (ipid + public_refs + private_refs,
ENCODED_LEN = 24) + RemRefResponse decoded shape.
8 new structural tests across both modules pin every documented
edge of each shape — short stubs, referent-zero short-circuits,
truncated-trailing detection, full multi-element round-trips.
mxaccess-rpc 183 → 188 tests; default-feature clippy clean.
Both followups documented "**Status:** Awaiting wire-fixture
capture" prior to this commit; the structural-port option was
explicitly listed as resolution path (b) in each. Future captured
frames will validate the byte-by-byte match — until then the
port is byte-faithful to the spec but unverified against a live
peer (which is fine for shipping since neither opnum is on the
NMX session's hot path).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+14
-11
@@ -182,17 +182,6 @@ Both sides see the same `result_code=32` (= `AsbErrorCode.PublishComplete`, info
|
||||
**Source:** M2 wave 1, `crates/mxaccess-rpc/src/ntlm.rs`. All current NTLM fixtures are single-domain (the local AVEVA install). Tracked separately in `design/70-risks-and-open-questions.md` R8 (P1 risk) and the open-evidence-gaps table.
|
||||
**Concrete next step:** Provision a two-domain Windows lab (e.g. `LAB-A` + `LAB-B` with cross-domain trust + an AVEVA install on `LAB-A` that authenticates a user from `LAB-B`). Run `cargo run -p mxaccess --example connect-write-read` from a `LAB-B`-domain user; capture the NTLM Type1 / Type2 / Challenge / Type3 bytes via `examples/asb-relay.rs` or a Wireshark NTLM filter. Save under `crates/mxaccess-rpc/tests/fixtures/cross-domain-ntlm/`. The existing single-domain Type1/2/3 round-trip tests in `mxaccess-rpc::ntlm` then extend to validate the cross-domain shape (TargetInfo AV pairs differ when crossing domains; specifically `MsvAvDnsTreeName` and `MsvAvDnsComputerName` carry the trusted-domain DNS suffix instead of the local one). Clears R8 in the risks doc.
|
||||
|
||||
### F10 — `IObjectExporter::ResolveOxid2` (opnum 4) body codec
|
||||
**Severity:** P2 — the ResolveOxid (opnum 0) path is what the .NET reference + our Rust port use; opnum 4 is only needed by callers that want the additional `COMVERSION` + `AuthnHnt[]` data.
|
||||
**Status:** Awaiting wire-fixture capture or .NET helper.
|
||||
**Source:** M2 wave 2, `crates/mxaccess-rpc/src/object_exporter.rs`. `ObjectExporterMessages.cs` only models opnum 0; opnum 4 has a different response shape per `[MS-DCOM]` §3.1.2.5.1.4. No .NET executable spec to mirror.
|
||||
**Concrete next step:** Either (a) extend `MxNativeClient.Probe` with a `--probe-resolve-oxid2` flag that calls `IObjectExporter::ResolveOxid2(oxid, &requested_protseqs)` against `localhost:135` and dumps the response stub to a file, then port the layout into `object_exporter.rs::parse_resolve_oxid2_result` mirroring the existing `parse_resolve_oxid_result` (`object_exporter.rs:226`); or (b) hand-roll the layout from `[MS-DCOM]` §3.1.2.5.1.4 (response = same as ResolveOxid + 8-byte `COMVERSION` + 4-byte `AuthnHnt[]` count + N×4-byte ushort entries + 4-byte status), commit the structural codec, and rely on a future captured frame to validate.
|
||||
|
||||
### F11 — `IRemUnknown::RemAddRef` and `RemRelease` body codecs
|
||||
**Severity:** P2 — neither opnum is exercised by the .NET reference's NMX session lifecycle, so the lack of a body codec doesn't block any current consumer.
|
||||
**Status:** Awaiting wire-fixture capture or .NET helper.
|
||||
**Source:** M2 wave 2, `crates/mxaccess-rpc/src/rem_unknown.rs`. `RemUnknownMessages.cs` declares the opnums (`:9-10`) but doesn't implement encoders/decoders. Rust port matches that per "port what is already proven."
|
||||
**Concrete next step:** Either extend `MxNativeClient.Probe` with `--probe-rem-add-ref` / `--probe-rem-release` flags that exercise opnums 4 and 5 against an existing `IRemUnknown` IPID, capture the responses, and port the body layouts into `rem_unknown.rs` alongside the existing `RemQueryInterface` codec; OR derive the layouts from `[MS-DCOM]` §3.1.1.5.6 (`REMINTERFACEREF[]` array of IPID + public/private refs counts) and ship the codecs structurally.
|
||||
|
||||
|
||||
|
||||
@@ -234,6 +223,20 @@ R15's "long-lived connection task" was previously listed as a hard prerequisite,
|
||||
|
||||
Workspace `mxaccess` 65 → 67 tests; default-feature clippy clean. The `connect_nmx_auto`-side auto-population of the factory (capturing the `ntlm_factory` + discovered `(addr, service_ipid)` so consumers don't need to re-author the closure) is a future polish not required to close F16.
|
||||
|
||||
### F10 — `IObjectExporter::ResolveOxid2` (opnum 4) body codec
|
||||
**Resolved:** 2026-05-06 (commit `<this commit>`) per option (b) of the followup's resolve criterion: structural port from `[MS-DCOM]` §3.1.2.5.1.4. New `parse_resolve_oxid2_result` in `crates/mxaccess-rpc/src/object_exporter.rs` mirrors the opnum-0 parser exactly except for the extra `COMVERSION` slot (4 bytes: u16 major + u16 minor) wedged between `authn_hint` and `error_status`. New types: `ComVersion` and `ResolveOxid2Result`. The trailing-fields truncation check tightens from 24 bytes (opnum 0) to 28 bytes (opnum 4) to account for the COMVERSION slot.
|
||||
|
||||
`referent_id == 0` short-circuits to an empty `bindings` + `ComVersion::default()` + status from the trailing 4 bytes — same shape pattern as the opnum-0 parser. `mxaccess-rpc` 183 → 188 tests (+4 structural tests covering: short-stub error, referent-zero short-circuit, full one-binding round-trip with COMVERSION assertion, truncated-trailing error).
|
||||
|
||||
No live `ResolveOxid2` capture exists in this tree (the .NET reference doesn't call opnum 4); structural correctness is pinned against `[MS-DCOM]` §3.1.2.5.1.4 verbatim. Future captured frames will validate.
|
||||
|
||||
### F11 — `IRemUnknown::RemAddRef` and `RemRelease` body codecs
|
||||
**Resolved:** 2026-05-06 (commit `<this commit>`) — structural port from `[MS-DCOM]` §3.1.1.5.6. Both opnums share the same `REMINTERFACEREF[]` request shape (per `[MS-DCOM]` §2.2.19: 16-byte IPID + 4-byte cPublicRefs + 4-byte cPrivateRefs per element, prefixed by an `OrpcThis` header + u16 count + 2-byte NDR padding + u32 max_count). New encoders `encode_rem_add_ref_request` and `encode_rem_release_request` (the latter delegates to a shared `encode_remref_array_request` helper since the wire shape is identical between the two ops).
|
||||
|
||||
Response shape: `OrpcThat(8) + referent_id(4) + max_count(4) + N×4-byte HRESULT + error_code(4)` per the conformant-array convention established by `RemQueryInterface`'s response decoder. `referent_id == 0` short-circuits to an empty `per_ref_hresults` array. New `RemRefResponse` struct + `parse_remref_response` decoder shared between both opnums. New `RemInterfaceRef` struct.
|
||||
|
||||
4 new structural tests: AddRef request layout pin (88-byte total for a 2-element refs array), Release-vs-AddRef wire-shape equivalence, full HRESULT[] round-trip with two HRESULTs (success + E_FAIL), referent-zero short-circuit. Like F10, the .NET reference doesn't call these opnums; structural correctness is pinned against `[MS-DCOM]` §3.1.1.5.6 verbatim.
|
||||
|
||||
### F27 — Constant-time DH `mod_exp` (swap `num-bigint` → `crypto-bigint::DynResidue`)
|
||||
**Resolved:** 2026-05-06 (commit `<this commit>`). Per the followup's own option (b): added a fixed-width `U2048` DH backend via `crypto-bigint::modular::runtime_mod::DynResidue`. New `auth.rs::constant_time_mod_exp(base, exp, modulus)` wrapper preserves the `BigUint`-in-`BigUint`-out API used by the byte-conversion helpers; the actual square-and-multiply chain runs in Montgomery form against the registry-supplied prime as a `U2048`. Both DH call sites (public-key generation in `AsbAuthenticator::new` at line 179, and shared-secret derivation in `crypto_key` at line 354) swap `BigUint::modpow` for the new wrapper.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user