# 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](https://github.com/microsoft/windows-rs). **Two-layer delivery target:** 1. **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. 2. **High-level safe async via Tokio is the end goal.** Above the raw layer, expose an idiomatic Rust API: `async`/`await` on Tokio, `Send + Sync` handles, typed errors instead of `HRESULT`, no `unsafe` in the public surface, structured subscription streams (e.g. `Stream`), and cancellation via `CancellationToken`/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): 1. **Managed DCOM/NDR directly to `NmxSvc.exe`** — full MXAccess-compatible LMX/NMX behavior. Bypasses the x86-only `NmxSvcps.dll` proxy/stub by speaking ORPC + NTLM packet-integrity on the wire. This is what `src/MxNativeClient` does. The Rust port should follow the same approach using `windows-rs` for COM activation/RPCSS bootstrap and a hand-rolled DCE/RPC + NDR codec for the service interfaces. 2. **ASB (`IASBIDataV2`) over `net.tcp` to MxDataProvider** — a higher-level data-service path that handles register/read/write/complete/subscribe without going through NMX at all. This is what `src/MxAsbClient` does. 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. Handles `MxReferenceHandle` (the 20-byte LMX automation/attribute handle, including CRC-16/IBM signatures over lowercase UTF-16LE names), the NMX `TransferData` envelope, item-control (advise/unadvise) bodies, normal/secured write bodies, `Write2` timestamped variants, subscription/status callback decoders (`0x32`, `0x33`), reference-registration (`0x10`/`0x11`), and the `MxStatus`/`MxValueKind`/`MxDataType` model. **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`, the `INmxService2` client (`ManagedNmxService2Client`), a managed callback exporter that serves `INmxSvcCallback`/`IRemUnknown` from a managed RPC endpoint, plus the high-level `MxNativeSession` and the MXAccess-shaped `MxNativeCompatibilityServer`. 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 console `Program.cs`, not a framework test project). Run them with `dotnet run --project ...`. - **`src/MxTraceHarness`** (net481/x86) and **`src/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/` and `analysis/native/` for native code. - `captures/0NN-frida-*` — controlled Frida captures, one per write/subscribe scenario. `frida-to-tcp-map.tsv` inside 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). ```powershell # 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. ```powershell # 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 `MxNativeCodec` deliberately 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.dll` proxy/stub. Do not introduce a dependency on the x86 stack from any replacement code; if x86 is unavoidable for capture/validation, isolate it in `MxTraceHarness`/`NmxComHarness`. - **COM in Rust → use `windows-rs`.** For COM activation, `IUnknown`, OBJREF marshaling, and registered type-library callback paths, prefer `windows`/`windows-rs` types over hand-rolled FFI. - **Galaxy Repository access is direct SQL** (see `GalaxyRepositoryTagResolver.cs`), not LMX. The Rust port should do the same — query the `dbo.gobject` / `dbo.instance` / `dbo.dynamic_attribute` / `dbo.attribute_definition` / `dbo.primitive_instance` / `dbo.package` tables, then walk the package-inheritance chain via `package.derived_from_package_id` (the recursive CTE in `GalaxyRepositoryTagResolver.cs:209-293`, named `deployed_package_chain`). Combine with CRC-16/IBM signature computation locally to reproduce the native handle without touching x86 LMX. Verified against `wwtools/grdb/` (the sibling Galaxy SQL exploration repo) and `wwtools/grdb/queries/attributes.sql`. Resolver input is `tag_name`-form (e.g. `DelmiaReceiver_001`), NOT `contained_name`-form (e.g. `TestMachine_001.DelmiaReceiver`) — the two are asymmetric in the schema. Earlier drafts of this file listed `aa_attribute` / `aa_object` / `mx_attribute_category` as 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.