Adds `Guid::parse_str(&str) -> Result<Guid, RpcError>` to
`crates/mxaccess-rpc/src/guid.rs` as the inverse of the existing
`Display` impl. Accepts the canonical dashed-hex form, optionally
braced (.NET `B` format), case-insensitive, and tolerant of bare
32-char hex without dashes. Single-pass char-by-char nibble accumulator
avoids per-byte string allocation; applies the same byte-swap of
groups 1-3 that the `Display` impl reads.
Eight new tests cover round-trip against the existing `Display`
fixture (`crates/mxaccess-rpc/src/guid.rs:111-119`,
`b49f92f7-c748-4169-8eca-a0670b012746`), braces, uppercase, no-dashes,
zero-GUID, too-short, too-long, and non-hex rejection.
The five live-NMX examples (`connect-write-read`, `subscribe`,
`recovery`, `multi-tag`, `secured-write`) lose their per-file 15-line
`parse_guid` helpers in favour of the canonical implementation.
`asb-subscribe` and `subscribe-buffered` are unaffected — they don't
parse GUIDs.
Test count delta: 524 → 532 (+8)
Open followups touched: F17 resolved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure whitespace cleanup from running `cargo fmt --all` between
iterations; no semantic change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reduces open followups from 11 → 10 (back at the soft threshold).
Step 0 triage flagged F1 as resolvable now: M4's connect-path
example will need a from_env constructor anyway, and the hostname
lookup is portable enough not to need a native-libc dep.
New
- NtlmClientContext::from_env() -> Result<Self, NtlmError>: reads
MX_RPC_USER / MX_RPC_PASSWORD / MX_RPC_DOMAIN env vars. Empty
MX_RPC_DOMAIN is permitted (workgroup auth). Mirrors the .NET
ManagedNtlmClientContext.FromEnvironment() at cs:41-49.
- local_hostname() -> String public helper: checks COMPUTERNAME
(Windows) then HOSTNAME (POSIX) and returns the empty string when
neither is set — same "unavailable" semantics as
Environment.MachineName returning null. No gethostname(2) call,
no unsafe, no native-libc dep. Callers needing reliable POSIX
hostnames can pass workstation explicitly.
- NtlmError::MissingEnvVar { name: &'static str } variant.
Tests (8 new in ntlm; total 27)
- from_env three-var happy path
- from_env missing each of the three vars (3 tests)
- from_env accepts empty MX_RPC_DOMAIN
- local_hostname prefers COMPUTERNAME over HOSTNAME
- local_hostname falls back to HOSTNAME
- local_hostname returns empty when neither set
- All env-mutating tests serialize via a static ENV_LOCK Mutex inside
EnvScope, since std::env::set_var touches process-global state and
cargo runs #[test]s in parallel by default.
design/followups.md: F1 moved to Resolved.
Open followups: 11 → 10 (back at soft threshold).
Test count delta: 498 -> 506 (+8). All four DoD gates green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two units of work in one commit:
1. nmx_service2_messages.rs (~470 LoC, 18 tests) — port of
NmxService2Messages.cs. Encoders for all 9 INmxService2 opnums
(RegisterEngine, UnRegisterEngine, Connect, TransferData,
AddSubscriberEngine, RemoveSubscriberEngine, SetHeartbeatSendInterval,
RegisterEngine2, GetPartnerVersion) plus BSTR + InterfacePointer NDR
helpers used by RegisterEngine2 marshalling. Decoders for the
GetPartnerVersion result and the generic HRESULT response. M3 stream
B (NmxClient) will be a thin layer over these + the transport.
2. object_exporter_client.rs (~290 LoC, 6 tests including 2 real-socket
tokio tests) — resolves followup F9. Implements:
- resolve_oxid_unauthenticated (cs:14-30)
- resolve_oxid_with_managed_ntlm_packet_integrity (cs:66-81)
ResolveOxidOutcome enum disambiguates the two response shapes the
.NET reference parses (typed result vs 4-byte failure). The two SSPI
flavours (cs:32-47, cs:49-64) are permanently skipped — they wrap
.NET-only System.Net.Security.SspiClientContext.
design/followups.md: F9 moved to Resolved with this commit's hash.
Test count delta: 364 -> 389 (+25; mxaccess-rpc 137 -> 162; +18 from
nmx_service2_messages, +7 from object_exporter_client which includes
the +2 fall-through tests for the dual-shape response decoder).
Open followups touched: F9 resolved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lands the async DCE/RPC TCP client — the transport that bridges the M2
PDU codec to a real socket. Unblocks M3 stream B (mxaccess-nmx, the
NmxClient) and brings F9 (ResolveOxid wrappers) within reach.
New
- transport.rs (~700 LoC, 10 tests including 2 real-socket tokio tests)
— port of src/MxNativeClient/DceRpcTcpClient.cs.
- DceRpcTcpClient::connect/bind/bind_with_managed_ntlm_packet_integrity/
call/call_bound/call_bound_object — async over tokio::net::TcpStream.
- encode_packet_integrity_request: 4-byte 0xBB pad + 8-byte AuthTrailer
+ 16-byte NtlmClientContext::sign signature, frag_length and
auth_length rewritten in the embedded header per cs:201-250.
- encode_request_bytes: PFC_OBJECT_UUID flag (0x80) and inserted
16-byte object UUID slot per cs:269-278.
- TransportError enum unifies io / codec / NTLM / fault / not-connected
surfaces. Mirrors DceRpcFaultException as the typed Fault variant.
- NTLM_AUTH_CONTEXT_ID = 79232 = 0x13580 (cs:90,133) exposed publicly.
Deliberately skipped: BindWithNtlmConnect / BindWithNtlmPacketIntegrity
(SSPI flavours at cs:55-63,108-149) — those wrap .NET's
System.Net.Security.SspiClientContext, which has no portable analogue.
Managed-NTLM path covers what the production Rust client needs.
mxaccess-rpc/Cargo.toml: added tokio (workspace-pinned).
design/followups.md: F9 downgraded P1 → P2 (transport landed; only the
two pure-codec ResolveOxid wrappers remain).
Test count delta: 354 -> 364 (+10).
Open followups touched: F9 partially advanced.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lands the codec-only prerequisites for M2 wave 3 (callback exporter).
The TCP server itself (port of ManagedCallbackExporter.cs's TcpListener
+ accept loop) follows next iteration in the mxaccess-callback crate.
New modules
- nmx_metadata.rs (9 tests) — port of NmxProcedureMetadata.cs.
INmxService2 + INmxSvcCallback IIDs, NdrProcedureDescriptor with
per-opnum metadata for the 9 INmxService2 procedures (opnums 3..11)
and 2 INmxSvcCallback procedures (opnums 3, 4).
- nmx_callback_messages.rs (8 tests) — port of NmxSvcCallbackMessages.cs.
parse_callback_request decodes OrpcThis + i32 size + i32 max_count +
body bytes; encode_callback_response produces the 12-byte OrpcThat +
HRESULT response.
objref.rs additions
- ComObjRefBuilder::create_standard_objref (8 tests) — port of the
second class in ManagedCallbackExporter.cs:337-393. Pure-Rust OBJREF
emitter that builds 68-byte header + dual-string array. Note this is
*not* the Win32 CoMarshalInterface-based ComObjRefProvider.cs (still
open as F6); it's the higher-level emitter the callback exporter
uses to build OBJREF bytes from primitives.
- CALLBACK_OBJREF_AUTH_SERVICES const exposes the 7-entry auth-service
tower-id table (NTLM SSP through Kerberos extension) the .NET
reference advertises in every callback OBJREF.
Test count delta: 319 -> 344 (+25; mxaccess-rpc 102 -> 127, codec
unchanged at 215, parity unchanged at 2). All four DoD gates green.
Open followups touched: none new; F6 advances toward resolution but
the windows-rs Win32 wrapper part stays open.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lands M2 wave 2 — two pure-Rust body-codec modules under
crates/mxaccess-rpc, plus a small inline ORPC framing port and a
crate-level type consolidation. Resolves F7+F8 from wave 1.
New modules
- guid.rs (4 tests) — hoisted from objref::Guid; shared by all of
mxaccess-rpc. Resolves F7.
- error.rs — hoisted RpcError union (ShortRead, UnexpectedPacketType,
UnknownPacketType, InvalidFragmentLength, TruncatedBindBody,
InvalidAuthTrailer, MissingAuthValue, Decode). Resolves F8.
- orpc.rs (8 tests) — port of OrpcStructures.cs:1-141. ComVersion,
OrpcThis (32-byte header), OrpcThat (8-byte header),
MInterfacePointer (length-prefixed OBJREF), StdObjRef (40 bytes).
- object_exporter.rs (~530 LoC, 20 tests) — port of
ObjectExporterMessages.cs:1-141. IObjectExporter IID, opnums,
ResolveOxid request encoder + ResolveOxidResult/Failure parsers.
Owned-string protocol labels cleaned up via Cow upgrade rather than
Box::leak (ComDualStringEntry::protocol is now Cow<'static, str>).
- rem_unknown.rs (~340 LoC, 11 tests) — port of RemUnknownMessages.cs.
IRemUnknown IID, RemQueryInterface request/response, RemQiResult.
4-byte NDR pad in REMQIRESULT preserved as pad_after_hresult per
CLAUDE.md unknown-bytes rule.
Test count delta: 277 -> 319 (+42; codec 215 unchanged, mxaccess-rpc
60 -> 102, codec parity 2 unchanged).
Open followups touched: F7 + F8 resolved; F9, F10, F11 added.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lands M2 wave 1 — three pure-Rust modules under crates/mxaccess-rpc with
60 unit tests. Each is a 1:1 port of one .NET reference file:
- ntlm.rs (1137 LoC, 19 tests) — `ManagedNtlmClientContext.cs`. NTLMv2
challenge/response, Type1/Type3 builders, sign() with RC4-sealed checksum
and per-call sequence advance. Manual `Debug` impl that hides credentials;
not Clone (rc4 0.2 cipher state is non-Clone). Pure-Rust crypto via
hmac/md-5/md4/rc4 v0.2/rand v0.8 (rc4 0.2 chosen per design/review.md:78).
- pdu.rs (1573 LoC, 33 tests) — `DceRpcPdu.cs` + auth-trailer types from
`DceRpcAuthentication.cs`. Bind/AlterContext/Auth3/Request/Response/Fault
PDUs, NDR20 transfer syntax, auth_value with 4-byte alignment padding,
preserved-byte fields per CLAUDE.md unknown-bytes rule.
- objref.rs (~470 LoC, 11 tests including a 366-byte captured OBJREF
round-trip) — `ComObjRef.cs`. MEOW signature, OXID/OID/IPID, dual-string
array with printable-ASCII escaping and security-binding boundary.
ComObjRefProvider.cs deferred (windows-rs Win32 wrapper — see F6).
Every wire-byte claim cites src/MxNativeClient/<file>.cs:LINE per
CLAUDE.md "no fabricated protocol behaviour" rule.
Test count delta: 217 → 277 (+60)
Open followups touched: F1–F8 (new — see design/followups.md)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>