Layout:
- src/ .NET 10 x64 reference: MxNativeCodec, MxNativeClient,
MxAsbClient, probes, tests, harnesses. Executable spec.
- design/ Architectural plan for the Rust port (M0–M6), error
model, protocol invariants, risks (R1–R16), adversarial
review log (review.md).
- rust/ Rust workspace. M0 skeleton + M1 codec parity.
mxaccess-codec: 215 unit tests + 2 cross-implementation
parity tests (byte-identical against .NET reference).
Other crates are M0 stubs awaiting M2+.
- captures/ Frida + netsh + pcap evidence per CLAUDE.md
("captures are evidence, not throwaway logs").
- analysis/ Decompiled C# (frida/proxy/decompiled-*),
Ghidra exports for native DLLs (`exports/` only —
working state at `projects/` and AVEVA's input
binaries at `input/` are gitignored).
- docs/ Reverse-engineering reference docs.
- tools/ Setup-LiveProbeEnv.ps1 (Infisical credential fetcher),
Compute-Crc.ps1 (.NET parity helper).
- .github/workflows/ Rust CI: fmt + build + test + clippy on Windows.
- LICENSE MIT (Joseph Doherty, 2026).
Verified:
- cargo test --workspace → 217 passed (215 unit + 2 .NET parity), 0 failed
- cargo clippy --workspace -- -D warnings → clean
- cargo fmt --all -- --check → clean
- cargo publish --dry-run -p mxaccess-codec → packages cleanly
Excluded from history (see .gitignore):
- **/bin, **/obj, **/target — build artifacts
- analysis/ghidra/projects/ — Ghidra working state (regenerable)
- analysis/ghidra/input/ — AVEVA proprietary DLLs (vendor IP)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project goal
Build a native Rust interface that replaces the AVEVA/Wonderware MXAccess library, giving Rust applications an MXAccess-equivalent API for talking to the AVEVA System Platform. When COM bindings are needed, use microsoft/windows-rs.
Two-layer delivery target:
- Raw MXAccess replacement is key. The first deliverable is a faithful, byte-accurate Rust reimplementation of the MXAccess surface (register/advise/write/read/subscribe/callback dispatch) over the proven x64 transports. Parity with native MXAccess wire behavior comes before ergonomics — every operation must round-trip the same bytes and surface the same status as the captured native stack.
- High-level safe async via Tokio is the end goal. Above the raw layer, expose an idiomatic Rust API:
async/awaiton Tokio,Send + Synchandles, typed errors instead ofHRESULT, nounsafein the public surface, structured subscription streams (e.g.Stream<Item = DataChange>), and cancellation viaCancellationToken/drop. The raw layer should be usable on its own for power users; the Tokio layer is what most consumers should reach for.
The Rust port does not yet exist in this tree. What lives here today is the reverse-engineering reference: a working .NET 10 x64 managed implementation of the same protocol stack, plus the captures, decompiled artifacts, and wire-format docs that justify every byte of it. Treat the .NET code as the executable spec — do not invent protocol behavior; port what is already proven.
High-level architecture
The real MXAccess runtime is a 32-bit COM/native stack. The .NET assembly ArchestrA.MXAccess.dll is just an interop shim around LMXPROXYLib:
ArchestrA.MXAccess.dll -> LmxProxy.dll -> Lmx.dll / NmxAdptr.dll -> NmxSvc.exe
Two viable replacement paths have been proven from x64 (relevant to the Rust port):
- Managed DCOM/NDR directly to
NmxSvc.exe— full MXAccess-compatible LMX/NMX behavior. Bypasses the x86-onlyNmxSvcps.dllproxy/stub by speaking ORPC + NTLM packet-integrity on the wire. This is whatsrc/MxNativeClientdoes. The Rust port should follow the same approach usingwindows-rsfor COM activation/RPCSS bootstrap and a hand-rolled DCE/RPC + NDR codec for the service interfaces. - ASB (
IASBIDataV2) overnet.tcpto MxDataProvider — a higher-level data-service path that handles register/read/write/complete/subscribe without going through NMX at all. This is whatsrc/MxAsbClientdoes. Useful as a regular-tag data-plane behind a compatibility router; does not cover callback-only MXAccess semantics.
docs/ASB-Native-Integration-Decision.md records the decision to use ASB as the preferred data-plane and reserve NMX for callback-only semantics that ASB cannot reach.
Layering inside the .NET reference
src/MxNativeCodec— pure protocol codec, no I/O. HandlesMxReferenceHandle(the 20-byte LMX automation/attribute handle, including CRC-16/IBM signatures over lowercase UTF-16LE names), the NMXTransferDataenvelope, item-control (advise/unadvise) bodies, normal/secured write bodies,Write2timestamped variants, subscription/status callback decoders (0x32,0x33), reference-registration (0x10/0x11), and theMxStatus/MxValueKind/MxDataTypemodel. This is the layer most directly equivalent to what the Rust port needs to reproduce first.src/MxNativeClient— DCOM transport + session façade. Implements managed NTLMv2 (ManagedNtlmClientContext), DCE/RPC PDU framing (DceRpcPdu,DceRpcTcpClient),IObjectExporter::ResolveOxid,IRemUnknown::RemQueryInterface, theINmxService2client (ManagedNmxService2Client), a managed callback exporter that servesINmxSvcCallback/IRemUnknownfrom a managed RPC endpoint, plus the high-levelMxNativeSessionand the MXAccess-shapedMxNativeCompatibilityServer. Talks SQL directly to the Galaxy Repository (GalaxyRepositoryTagResolver,GalaxyRepositoryUserResolver) instead of calling the x86 LMX resolver.src/MxAsbClient— ASB/WCF data client (MxAsbDataClient) plus an MXAccess-shaped facade (MxAsbCompatibilityServer). Self-contained from NMX.src/*.Probe— runnable diagnostic CLIs that exercise individual protocol slices end-to-end against the live local AVEVA install. They are how new behavior gets validated; their CLI flags double as documentation of the supported operations.src/*.Tests— assertion-style runners (each is a consoleProgram.cs, not a framework test project). Run them withdotnet run --project ....src/MxTraceHarness(net481/x86) andsrc/NmxComHarness— instrumented harnesses that load the real x86 stack to capture ground-truth wire bytes.
Reverse-engineering artifacts (the spec, in priority order)
When in doubt about wire format, these are authoritative — read before guessing:
docs/MXAccess-Reverse-Engineering.md,docs/MXAccess-Public-API.md— overall map and the public surface to replicate.docs/Loopback-Protocol-Findings.md,docs/NMX-COM-Contracts.md,docs/Transport-Correlation.md— DCE/RPC interfaces, opnums, and how Frida-observed native calls map to TCP frames.docs/ASB-Variant-Wire-Format.md— ASB variant layout, scalar/array, timestamps, durations, quality/status.docs/DotNet10-Native-Library-Plan.md— current parity matrix vs.ILMXProxyServer5, what's proven, what's blocked.docs/MxNativeSession-API.md— the consumer-facing API surface the Rust port should converge on.analysis/frida/write-body-matrix.tsv,write-array-body-matrix.tsv,write-mode-matrix.tsv— per-type captured write bodies.analysis/proxy/nmxsvcps-procedures.tsv— decoded MIDL procedure table.analysis/decompiled-mxaccess/,analysis/decompiled-interop/,analysis/decompiled/— decompiled C# of the real assemblies.analysis/ghidra/andanalysis/native/for native code.captures/0NN-frida-*— controlled Frida captures, one per write/subscribe scenario.frida-to-tcp-map.tsvinside a capture correlates native calls to the wire.work_remain.md— current status of what's done vs. open across both managed paths.
Common commands
All .NET projects target net10.0-windows / x64 (except the x86 MxTraceHarness, which is net481).
# Build the protocol layers
dotnet build src\MxNativeCodec\MxNativeCodec.csproj
dotnet build src\MxNativeClient\MxNativeClient.csproj
dotnet build src\MxAsbClient\MxAsbClient.csproj
# Run the assertion-style test programs
dotnet run --project src\MxNativeCodec.Tests\MxNativeCodec.Tests.csproj
dotnet run --project src\MxNativeClient.Tests\MxNativeClient.Tests.csproj
dotnet run --project src\MxAsbClient.Tests\MxAsbClient.Tests.csproj
# Live probes against the local AVEVA install (require NTLM env + Galaxy SQL access)
dotnet run --project src\MxNativeClient.Probe\MxNativeClient.Probe.csproj -c Release -- --probe-session-write --tag=TestChildObject.TestInt --value=123 --objref-only
dotnet run --project src\MxNativeClient.Probe\MxNativeClient.Probe.csproj -c Release -- --probe-session-subscribe --tag=TestChildObject.TestInt --subscribe-hold-seconds=5 --objref-only
# Populate live-probe env vars from Infisical (via wwtools/secrets/Get-Secret.ps1).
# Sets MX_LIVE, MX_NMX_HOST, MX_GALAXY_DB, MX_TEST_USER, MX_TEST_DOMAIN,
# MX_TEST_PASSWORD, and (when configured) MX_ASB_SHARED_SECRET. Dot-source so
# the env vars persist in the calling session. Never inline plaintext.
. .\tools\Setup-LiveProbeEnv.ps1
. .\tools\Setup-LiveProbeEnv.ps1 -SkipAsbSecret # if ASB secret not yet in Infisical
.\tools\Setup-LiveProbeEnv.ps1 -DryRun # print what would be set
There is no solution file or workspace runner — build/test each project explicitly. There is no lint config; the projects use Nullable and ImplicitUsings enabled and rely on the compiler.
Rust workspace (M0 onwards)
The Rust port lives under rust/ (sibling of src/). Toolchain pinned to 1.85 stable via rust/rust-toolchain.toml; edition 2024; license MIT.
# All commands run from the rust/ workspace root
cd rust
# Workspace-wide
cargo build --workspace --all-targets
cargo test --workspace --no-fail-fast
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all -- --check
# Single crate
cargo build -p mxaccess-codec
cargo test -p mxaccess-codec
# Examples (live under crates/mxaccess/examples/, invoke with -p mxaccess)
cargo run -p mxaccess --example connect-write-read
cargo run -p mxaccess --example subscribe -- --tag TestChildObject.TestInt
cargo run -p mxaccess --example asb-subscribe -- --tag TestChildObject.TestInt
# Live integration tests (require AVEVA + Galaxy DB; gated by MX_LIVE)
. ..\tools\Setup-LiveProbeEnv.ps1 # dot-source — fetches creds from Infisical
cargo test -p mxaccess --features live -- --ignored
# Publish-check (M0 DoD)
cargo publish --dry-run -p mxaccess-codec
CI: .github/workflows/rust.yml runs fmt --check, build, test, clippy -D warnings on Windows.
Working rules specific to this project
- Do not fabricate protocol behavior. Every wire shape in the .NET reference is backed by a Frida capture, decompiled source, or live probe. When extending the Rust port, point to the same evidence — capture a new fixture if one doesn't exist.
- Preserve unknown bytes. The codecs in
MxNativeCodecdeliberately round-trip unknown tag/session/quality fields rather than zeroing them. Mirror this in Rust; consumers depend on byte-for-byte parity with native MXAccess for some flows. - x64 only for the replacement. The whole point is to escape the 32-bit
NmxSvcps.dllproxy/stub. Do not introduce a dependency on the x86 stack from any replacement code; if x86 is unavoidable for capture/validation, isolate it inMxTraceHarness/NmxComHarness. - COM in Rust → use
windows-rs. For COM activation,IUnknown, OBJREF marshaling, and registered type-library callback paths, preferwindows/windows-rstypes over hand-rolled FFI. - Galaxy Repository access is direct SQL (see
GalaxyRepositoryTagResolver.cs), not LMX. The Rust port should do the same — query thedbo.gobject/dbo.instance/dbo.dynamic_attribute/dbo.attribute_definition/dbo.primitive_instance/dbo.packagetables, then walk the package-inheritance chain viapackage.derived_from_package_id(the recursive CTE inGalaxyRepositoryTagResolver.cs:209-293, nameddeployed_package_chain). Combine with CRC-16/IBM signature computation locally to reproduce the native handle without touching x86 LMX. Verified againstwwtools/grdb/(the sibling Galaxy SQL exploration repo) andwwtools/grdb/queries/attributes.sql. Resolver input istag_name-form (e.g.DelmiaReceiver_001), NOTcontained_name-form (e.g.TestMachine_001.DelmiaReceiver) — the two are asymmetric in the schema. Earlier drafts of this file listedaa_attribute/aa_object/mx_attribute_categoryas the surface; those names do not exist as tables in the Galaxy DB and have been corrected. - Captures and probe outputs in
captures/,analysis/frida/,analysis/proxy/are evidence, not throwaway logs. Don't delete or rewrite them when refactoring.