Files
mxaccess/rust/crates/mxaccess-nmx/src/lib.rs
T
Joseph Doherty 12cb10c3a1 [M4] mxaccess: Session::connect_nmx + write_value + shutdown (wave 1 main)
First working M4 wave 1 slice. Adds session.rs with the connect /
write / shutdown path on top of NmxClient + Resolver, plus a tokio
test that exercises a full round-trip against a hand-rolled server.
Read, subscribe, recovery, and the long-lived connection task land
in wave 2.

Architecture
- Session holds Arc<SessionInner>; SessionInner wraps NmxClient
  behind a tokio::sync::Mutex. All RPC ops serialize on that mutex.
  Wave 2 will replace it with an mpsc::channel<Op> + dispatcher task
  per design/70-risks-and-open-questions.md R15 (drop-time async
  cleanup hazards).
- ensure_connected gate stops post-shutdown ops with
  Connection::EngineNotRegistered. Shutdown is idempotent via
  AtomicBool::swap.
- Manual Debug impl on SessionInner — neither dyn Resolver nor
  NmxClient impl Debug.

Public API
- Session::connect_nmx(addr, options, ntlm, service_ipid, resolver,
  recovery): validates the policy, opens NmxClient, runs
  RegisterEngine2 (no callback yet — wave 2), optionally configures
  heartbeat. Returns Error::Connection on non-zero HRESULT.
- Session::write_value(reference, value: WriteValue): resolves the
  tag through the configured Resolver, dispatches NmxClient::write.
- Session::resolve_write_kind / resolve_tag: convenience accessors.
- Session::shutdown_nmx: calls UnregisterEngine, idempotent.

Error mapping
- map_nmx / map_transport / map_resolver bridge the inner crate
  errors into the public Error enum. NonZeroHresult → InvalidArgument
  with the hex code; transport Fault → Status-shaped error;
  ResolverError::NotFound → Galaxy { reason: "tag not found: ..." }.
- All three matchers handle their #[non_exhaustive] sources with a
  generic catch-all so future variants don't silently break the map.

Tests (8 new in mxaccess; total mxaccess: 19)
- write_value round-trip via in-memory StaticResolver + hand-rolled
  unauthenticated DCE/RPC server.
- write_value propagates resolver not-found → Galaxy error.
- write_value propagates non-zero HRESULT → InvalidArgument.
- shutdown is idempotent (second call is a no-op).
- write after shutdown returns EngineNotRegistered.
- resolve_tag and resolve_write_kind work without RPC.
- envelope-kind constants used by Session match codec exports
  (sanity guard against codec rename).

mxaccess-nmx: WriteValue now re-exported at crate root.
mxaccess: deps gained mxaccess-nmx/galaxy/rpc + tokio + tracing,
plus async-trait as a dev-dep for the test resolver impl.

Test count delta: 479 -> 487 (+8). All four DoD gates green.

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

28 lines
1.0 KiB
Rust

//! `mxaccess-nmx` — `INmxService2` client + raw NMX session façade.
//!
//! M3 stream B landed: the [`client`] module ports the raw opnum surface
//! of `src/MxNativeClient/ManagedNmxService2Client.cs` (the 9
//! `INmxService2` procedures over `mxaccess_rpc::transport`). The
//! auto-resolving COM-activation factory and the high-level
//! `Write*`/`Advise*` wrappers are deferred — see the module-level docs
//! for what's deliberately out of scope for this iteration.
//!
//! Opnums (verified against `src/MxNativeClient/NmxComContracts.cs:55-73`,
//! and on the wire — sequential because `INmxService2 : INmxService` continues
//! the same vtable; see `design/40-protocol-invariants.md`):
//! - `3` RegisterEngine
//! - `4` UnRegisterEngine
//! - `5` Connect
//! - `6` TransferData
//! - `7` AddSubscriberEngine
//! - `8` RemoveSubscriberEngine
//! - `9` SetHeartbeatSendInterval
//! - `10` RegisterEngine2
//! - `11` GetPartnerVersion
#![forbid(unsafe_code)]
pub mod client;
pub use client::{NmxClient, NmxClientError, WriteValue};