mxaccess + mxaccess-compat now carry #![warn(missing_docs)] at the crate root. Every public item has at least a one-line doc comment (struct fields, enum variants, trait methods all covered). Touched items: - mxaccess::lib: DataChange fields, SecurityContext fields, TransportKind variants, TransportCapabilities fields, RecoveryEvent variants + their inner fields, SessionOptions fields, the full Error / ConnectionError / AuthError / ProtocolError / ConfigError / SecurityError taxonomy + nested fields, Transport trait method docs. - mxaccess-compat::lib: DataChangeEvent / BufferedDataChangeEvent / WriteCompleteEvent / OperationCompleteEvent fields. Protocol crates (codec, rpc, galaxy, nmx, callback, asb, asb-nettcp) deliberately left without the lint per F53's strategy paragraph — their consumers (mxaccess + mxaccess-compat) already document the surfaces they re-export, and forcing one-liners on every transport-internal item adds noise without consumer value. Verification: - `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps` clean. - `cargo test --workspace` (824 tests) green. - `cargo clippy --workspace --all-targets -- -D warnings` clean. design/followups.md F53 marked partially resolved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AVEVA MXAccess reverse-engineering notes
This folder documents the local AVEVA/Wonderware MXAccess stack installed on this machine, using the primary runtime DLL:
C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll
Primary documentation:
- docs/MXAccess-Reverse-Engineering.md
- docs/MXAccess-Public-API.md
- docs/Managed-LMX-NMX-Capture-Plan.md
- docs/Capture-Run-2026-04-25.md
- docs/Loopback-Protocol-Findings.md
- docs/NMX-COM-Contracts.md
- docs/DotNet10-Native-Library-Plan.md
- docs/Ghidra-Headless-Analysis.md
- docs/Transport-Correlation.md
Current executable capture harness:
src\MxTraceHarness\bin\Release\net481\MxTraceHarness.exe
Managed codec work-in-progress:
src\MxNativeCodec\MxNativeCodec.csproj
.NET 10 x64 NMX client/probe scaffold:
src\MxNativeClient\MxNativeClient.csproj
src\MxNativeClient.Probe\MxNativeClient.Probe.csproj
src\MxNativeClient.Tests\MxNativeClient.Tests.csproj
This .NET 10 library currently implements a template-based encoder/decoder for
the observed CNmxAdapter::PutRequest write bodies. It preserves unknown
tag/session fields from a captured body and replaces the typed value slot plus
write index. The companion console test project round-trips the real Frida
bytes for bool, int, float, double, string, datetime, and the observed
int/bool/float/double/string array bodies. It also includes the first observed
INmxService2.TransferData envelope codec (46-byte header + PutRequest body).
The client scaffold now includes managed OBJREF parsing, NMX procedure metadata,
ORPC structures, IRemUnknown::RemQueryInterface message composition, and
DCE/RPC PDU primitives tested against captured bytes. It also has a managed
unauthenticated IObjectExporter::ResolveOxid probe that reaches RPCSS and
currently confirms the expected ERROR_ACCESS_DENIED auth blocker. A reference
Impacket probe now proves the packet-private DCOM path end to end: it activates
NmxSvc.NmxService, receives an INmxService2 IPID, and calls
GetPartnerVersion successfully from a 64-bit process without loading the
AVEVA x86 proxy. The managed scaffold now goes further: it implements managed
NTLMv2 packet-integrity signing, resolves the OXID, performs
IRemUnknown::RemQueryInterface, and calls INmxService2.GetPartnerVersion
successfully from .NET 10 x64. The first service-specific scalar codec is in
src\MxNativeClient\NmxService2Messages.cs.
Generated static-analysis artifacts are under analysis/:
analysis/decompiled-mxaccess/- decompiled C# forArchestrA.MXAccess.dll.analysis/interop/- imported interop assemblies generated from native type libraries.analysis/decompiled-interop/- decompiled type-library imports forLmx.dll,LmxProxy.dll,NmxAdptr.dll,NmxSvc.exe, andMXAccess32.tlb.
The main finding is that ArchestrA.MXAccess.dll is not the implementation of
the LMX/NMX protocol. It is a .NET COM interop assembly imported from
LMXPROXYLib. The real runtime path is a 32-bit COM/native stack:
ArchestrA.MXAccess.dll -> LmxProxy.dll -> Lmx.dll / NmxAdptr.dll -> NmxSvc.exe
That explains the current net48/x86 constraint and shapes the possible paths
to a modern .NET or Rust interface.
Current loopback captures show that the native path uses DCE/RPC on
::1:49704 plus at least one separate compact localhost binary stream around
write/write-complete activity. See docs/Loopback-Protocol-Findings.md for the
captured interface UUIDs, opnum shape, and write-window correlation.
Latest controlled captures:
captures\021-loopback-write-test-int-sequence-103-105
captures\023-frida-write-test-int-sequence-109-111
captures\024-frida-write-test-bool-sequence
captures\025-frida-write-test-float-sequence
captures\026-frida-write-test-double-sequence
captures\027-frida-write-test-string-sequence
captures\028-frida-write-test-datetime-sequence
captures\029-frida-write-test-int-array
captures\030-frida-write-test-bool-array
captures\031-frida-write-test-float-array
captures\032-frida-write-test-double-array
captures\033-frida-write-test-string-array
captures\035-frida-write-test-datetime-array-full
captures\040-frida-write-normal-secured-protectedvalue
captures\041-frida-write-normal-verified-protectedvalue1
captures\042-frida-write2-test-int-timestamp
captures\043-frida-loopback-write-test-int-115
captures\044-frida-loopback-write-test-int-123456789
captures\045-service-boundary-write-test-int-123456790
captures\046-service-boundary-write-test-int-123456791
The loopback int sequence writes TestChildObject.TestInt to 103, 104, and
105 in one advised session. The local stream framing is stable, but the
requested int values are not isolated as plain int32 payloads in the decoded
write-window pcap records. The Frida traces then find the values at the native
boundary: CLMXProxyServer receives the COM VARIANT, CNmxAdapter::PutRequest
carries the typed body, and CNmxAdapter::TransferData wraps that body.
The current scalar write matrix is saved at:
analysis\frida\write-body-matrix.tsv
The current array write matrix is saved at:
analysis\frida\write-array-body-matrix.tsv
The current write-mode matrix is saved at:
analysis\frida\write-mode-matrix.tsv
The decoded NmxSvcps.dll MIDL procedure table is saved at:
analysis\proxy\nmxsvcps-procedures.tsv
The .NET 10 x64 remote-style IUnknown OBJREF probe output is saved at:
analysis\proxy\nmxservice-objref-context2.txt
The reference DCOM activation and INmxService2.GetPartnerVersion probe output
is saved at:
analysis\proxy\dcom-inmxservice2-getpartner-probe.txt
The managed OXID, RemQueryInterface, and INmxService2.GetPartnerVersion probe
output is saved at:
analysis\proxy\managed-remqi-and-getpartner-probe.txt
The first Frida-to-TCP correlation output is:
captures\043-frida-loopback-write-test-int-115\frida-to-tcp-map.tsv
The non-ambiguous correlation capture is:
captures\044-frida-loopback-write-test-int-123456789\frida-to-tcp-map.tsv
The matrices cover bool, int, float, double, string, datetime, and the matching
array forms, plus timestamped Write2 and secured/verified public write
behavior. Capture 046 proves the same 86-byte write body reaches
CNmxService.TransferData inside NmxSvc.exe, but the .NET 10 x64 probe proves
ordinary COM interop fails on first method call because the only installed MIDL
proxy/stub is 32-bit (NmxSvcps.dll). The latest managed probe proves the
service can still be reached through managed packet-integrity ORPC. The next
blocker for the full managed x64 library is callback/interface-pointer
marshaling for RegisterEngine2, the TransferData byte-array method, then
status/error, add-item, advise, remove-item, and general tag metadata synthesis.
The TransferData byte-array method is now encoded and live-probed. The
service returns an NMX application HRESULT for the unregistered control probe,
which means the ORPC/NDR call shape is accepted. The callback marshal probe
confirms Windows cannot export INmxSvcCallback from x64 on this machine
because the proxy/stub is not registered for 64-bit; a managed callback object
exporter is required. The callback method codecs for DataReceived and
StatusReceived are now in src\MxNativeClient\NmxSvcCallbackMessages.cs; the
remaining callback work is the managed object endpoint/OBJREF exporter.
RegisterEngine2 marshaling is now decoded far enough for a managed .NET 10
x64 null-callback registration lifecycle. The x86 direct harness and Frida
captures show the required 0x72657355 BSTR user-marshal marker, the BSTR wire
format, and the callback MInterfacePointer wrapper. The live managed probe is:
analysis\proxy\managed-registerengine2-null-callback-probe.txt
It returns non-failing COM success codes for both register and unregister. The
remaining RegisterEngine2 work is generating a managed callback OBJREF and
serving INmxSvcCallback/IRemUnknown from a managed DCE/RPC endpoint.
A synthetic managed callback OBJREF currently fails before NmxSvc.exe connects
to the advertised listener, which points to missing RPCSS/OXID registration.
A COM-runtime-registered x64 IUnknown OBJREF patched to the callback IID is
accepted for non-null RegisterEngine2:
analysis\proxy\managed-registerengine2-callback-com-iunknown-objref-probe.txt
That proves the non-null callback argument can be accepted in .NET 10 x64, but
it does not yet prove callback method dispatch without the missing x64
NmxSvcps.dll stub.
The x64 callback marshal issue can be solved with a registered type library and the Windows standard automation proxy/stub:
analysis\scripts\register_x64_callback_typelib.ps1
After that setup, CoMarshalInterface(INmxSvcCallback) succeeds from .NET 10
x64 and NmxSvc.exe accepts the real marshaled callback OBJREF:
analysis\proxy\managed-registerengine2-callback-com-real-probe.txt
The same synthetic self-transfer path produces no callback in the x86 direct harness, so the next blocker is the NMX add/advise/session message bodies that actually trigger data/status callbacks.
That next layer is now being decoded from focused subscription capture:
captures\058-frida-subscribe-testint
The capture shows AdviseSupervisory sending a 39-byte CNmxAdapter.PutRequest
body wrapped in an 85-byte TransferData body. The corresponding unadvise body
uses the same item-control shape with command byte 0x21 instead of 0x1f,
wrapped in an 83-byte TransferData body whose envelope kind is 3. Incoming
ProcessDataReceived bodies use a related length-prefixed service-to-adapter
shape and carry status/data commands including 0x32 and 0x33. The generic
frame classifier is now checked into:
src\MxNativeCodec\NmxObservedFrame.cs
The callback codec now accepts both observed service-to-adapter forms: the
4-byte length-prefixed buffers and the direct 46-byte-header buffers seen in
the Write2 callback capture. It decodes 0x32 subscription status records and
0x33 data update records into typed status/detail, quality, FILETIME
timestamps, wire kind, correlation IDs, and scalar values for the observed bool,
int, float, double, string, scalar arrays, datetime arrays, and multi-record
date/status updates:
src\MxNativeCodec\NmxSubscriptionMessage.cs
The generated envelope encoder now reproduces the captured advise and unadvise
TransferData bodies exactly:
src\MxNativeCodec\NmxTransferEnvelope.cs
Follow-up captures for TestBool, TestString, and the array attributes show
that the item-control body contains an MxHandle projection. The
parser/encoder is:
src\MxNativeCodec\NmxItemControlMessage.cs
Capture 061 hooks Lmx.dll around IMxReference.GetMxHandle and
AccessManager.FixUpMxHandle. It proves the resolved TestInt handle is a
20-byte structure:
01 00 01 00 02 00 05 00 36 d7 02 00 9b 00 0a 00 3e da 00 00
The LMX typelib names this as MxAutomationObjectHandle followed by
MxAttributeHandle:
| Offset | Field | Value for TestChildObject.TestInt |
|---|---|---|
| 0 | galaxy |
1 |
| 2 | platform |
1 |
| 4 | engine |
2 |
| 6 | object |
5 |
| 8 | object signature | 0xd736 |
| 10 | primitive id | 2 |
| 12 | attribute id | 155 |
| 14 | property/category id | 10 |
| 16 | attribute signature | 0xda3e |
| 18 | attribute index | 0 (-1 for arrays) |
The NMX item-control request projects bytes 6 through 19 of this handle, then
appends tail 0x00000003. The signatures are reproducible in managed code:
CRC-16/IBM over the lowercase UTF-16LE object or attribute name
(testchildobject -> 0xd736, testint -> 0xda3e). The managed handle type and
synthesizer are:
src\MxNativeCodec\MxReferenceHandle.cs
Normal write bodies are also generated from the same handle projection plus
the GR data type. The generator reproduces the observed normal bool, int,
float, double, string, datetime, and array write bodies, plus the captured
timestamped int Write2 body. The GR resolver and generator also cover the
captured secured/verified test tags (security_classification 2 and 3);
their public route is still the normal write body shape:
src\MxNativeCodec\NmxWriteMessage.cs
The Galaxy Repository resolver is now live-tested against the local ZB
database and reproduces that same handle for TestChildObject.TestInt without
calling the x86 LMX resolver:
src\MxNativeClient\GalaxyRepositoryTagResolver.cs
The reusable .NET 10 x64 managed service client is now checked into:
src\MxNativeClient\ManagedNmxService2Client.cs
A first consumer-facing session facade now wraps the low-level pieces:
src\MxNativeClient\MxNativeSession.cs
It opens/registers a managed callback engine, resolves Galaxy Repository tag
metadata, exposes WriteAsync, timestamped Write2Async,
metadata browse, transient subscription ReadAsync,
SubscribeAsync/Unsubscribe, and raises typed callback records decoded by
NmxSubscriptionMessage.
The facade usage notes and probe commands are documented in:
docs\MxNativeSession-API.md
The remaining parity gaps against the full ILMXProxyServer5 MXAccess surface
are tracked method-by-method in:
docs\DotNet10-Native-Library-Plan.md
It moves the proven managed DCOM path out of the probe and exposes activation,
OXID resolution, IRemUnknown::RemQueryInterface, RegisterEngine2, Connect,
subscriber engine calls, TransferData, generated AdviseSupervisory and
UnAdvise bodies, generated normal Write and timestamped Write2 bodies,
GetPartnerVersion, and
unregister without loading the
AVEVA x86 proxy/stub. The probe now has a guarded managed subscribe path; SSPI
auth faults at ResolveOxid with 0x00000721, so the valid end-to-end
subscription probe remains the managed NTLM runtime-auth path. When callbacks
arrive, the probe prints the typed subscription records using the same managed
callback decoder.