Files
mxaccess/rust
Joseph Doherty 1e59249662 [M5] mxaccess-asb: F25 step 4 — AsbClient async network loop
The first slice of F25 that actually moves bytes across a transport.
Wraps every M5 framing layer (F19-F25.3) into a single async client
generic over `AsyncRead + AsyncWrite + Unpin + Send`. Tested in-memory
via `tokio::io::duplex` — no live ASB endpoint required.

API:
* `AsbClient::new(stream, authenticator, via_uri)` — wraps a Tokio
  transport + F23 authenticator into a ready client.
* `send_preamble()` — writes the canonical preamble (Version 1.0 →
  Duplex → Via → BinaryWithDictionary → PreambleEnd) and reads the
  peer's PreambleAck. Surfaces Fault as `ClientError::Fault(msg)`.
* `send_envelope(env)` — frames `SoapEnvelope` in a SizedEnvelope NMF
  record, writes, reads the response SizedEnvelope, decodes back to
  `DecodedEnvelope`.
* `send_signed_envelope(action, body, force_hmac)` — calls F23
  authenticator's `sign` on the unsigned body bytes, attaches a
  ConnectionValidator header (base64'd MAC + IV), sends.
* `register_items` / `unregister_items` — thin per-operation wrappers
  threading body builder + response decoder.
* `send_end()` — writes record 0x07 + shutdowns the stream.

Async record reader: streaming decode of the multibyte-int31 length
prefix for SizedEnvelope (0x06) / Fault (0x08), plus a fallback path
for Version / Mode / KnownEncoding / etc.

`ClientError` covers I/O, NMF, NBFX, Envelope, Operation, Auth, plus
PreambleNotSent / AlreadyClosed / Fault / PeerClosed /
UnexpectedRecord guards.

6 new tests via in-memory `tokio::io::duplex`:
* Preamble round-trip with synthetic peer returning PreambleAck.
* Fault propagation through preamble exchange.
* End-to-end RegisterItems request → response with a peer that
  drains preamble, replies PreambleAck, drains the SizedEnvelope,
  responds with a synthetic RegisterItemsResponse body containing a
  binary-encoded ItemStatus array. Client decodes and asserts the
  recovered ItemIdentity name.
* `send_envelope` before preamble fails with PreambleNotSent.
* `send_end` writes record 0x07 to the wire.
* PreambleMode re-export keeps shape parity with `nmf::NmfMode`.

Known limitation: the signing path currently hashes the NBFX-encoded
body; .NET hashes the XML-text `request.ToXml()`. Functionally
present (validator built and attached) but MAC bytes won't match
.NET's MAC for the same payload until the live-probe iteration
reconciles which canonical form to sign.

Stubbed for next F25 iteration:
* `AsbClient::connect` — DH `Connect` + `AuthenticateMe` handshake
  flow. Needs ConnectRequest/Response builders (regular WCF XML, not
  the IAsbCustomSerializableType fast-path) and the
  `AsbAuthenticator::create_authentication_data` integration.
* Read / Write / Subscription operation wrappers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 11:37:48 -04:00
..

mxaccess (Rust port)

Native Rust replacement for AVEVA / Wonderware MXAccess. See ../design/ for the architectural specification, ../src/ for the .NET reference (the executable spec), and ../CLAUDE.md for project-wide rules.

Status

M0 — Workspace skeleton. Stub types compile; nothing is implemented yet. See ../design/60-roadmap.md for the M0M6 milestone plan.

Layout

rust/
  Cargo.toml                 workspace root
  rust-toolchain.toml        1.85 stable
  crates/
    mxaccess-codec/          pure protocol codec, no I/O
    mxaccess-galaxy/         Galaxy SQL resolver (tiberius)
    mxaccess-rpc/            DCE/RPC + NTLMv2 + OXID + OBJREF
    mxaccess-callback/       INmxSvcCallback RPC server
    mxaccess-nmx/            INmxService2 client
    mxaccess-asb-nettcp/     net.tcp framing (MC-NMF + MC-NBFX/NBFS)
    mxaccess-asb/            IASBIDataV2 client
    mxaccess/                async session + Transport trait + public API
    mxaccess-compat/         LMXProxyServer-shaped facade

Build

cargo build --workspace
cargo test --workspace
cargo clippy --workspace -- -D warnings
cargo fmt --check

Live probes

. ..\tools\Setup-LiveProbeEnv.ps1
cargo test -p mxaccess --features live -- --ignored

The setup script fetches credentials from Infisical via wwtools/secrets/Get-Secret.ps1. Never inline plaintext credentials.

License

MIT — see ../LICENSE.