[F12 wrapper + F32 close] Session::connect_nmx_auto + close M5 type-matrix DoD
rust / build / test / clippy / fmt (push) Has been cancelled
rust / build / test / clippy / fmt (push) Has been cancelled
Two related closures in one commit:
1. Session-level wrapper around F12: new
`mxaccess::Session::connect_nmx_auto(ntlm_factory, options,
resolver, recovery)` gated on a new `mxaccess/windows-com` feature
(which propagates `mxaccess-nmx/windows-com`). Drives
`NmxClient::create` (the F12 COM-activation factory) for the
`(host, port, service_ipid)` discovery, then funnels into the
shared post-NMX-bind orchestration. Refactored `connect_nmx` to
extract steps 1+2+4+5 into a private `from_nmx_client` helper —
both `connect_nmx` and `connect_nmx_auto` reuse it so the
`CallbackExporter` + router + `RegisterEngine2` + heartbeat policy
stays in one place. The .NET `MxNativeSession.Open` shape
(`MxNativeSession.cs:127-147`) is now reproduced end-to-end on
Windows with `windows-com` on — callers no longer pre-resolve
`(addr, service_ipid)` by hand.
`connect_nmx`'s doc comment updated to drop the stale "F12 not yet
wired" note. `parse_bracketed_host_port` in mxaccess-nmx gets a
`cfg_attr(not(...), allow(dead_code))` so the default-feature
build stays warning-clean.
2. F32 closed via option (b) of its own resolve criterion: the four
missing types (Float / Double / DateTime / Duration) are gated on
Galaxy-side template provisioning that's outside the Rust port's
scope. The deployed test Galaxy on this host only has
mx_data_type ∈ {1=Bool, 2=Int32, 5=String}; we cannot exercise
the missing types without authoring new template attributes in
the Aveva console (a manual platform-engineering task). The
three-type live verification at commit 9063f10 satisfies the M5
DoD bullet for what is deployable. F18's M5 status block updated
to reflect F32-resolved.
Workspace: 718 tests pass on default features (was 712 before F12,
+6 from new parse_bracketed_host_port tests). Default-feature
clippy + windows-com clippy both clean.
Closes F32 in design/followups.md and extends F12's resolution note
with the Session-level wrapper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -127,7 +127,11 @@ fn fresh_orpc_this() -> OrpcThis {
|
||||
/// expects (`cs:540-561`). The host is everything before the **last** `[`,
|
||||
/// the port is the decimal text between that `[` and the **last** `]`.
|
||||
///
|
||||
/// Used by [`NmxClient::create`] only.
|
||||
/// Used by [`NmxClient::create`] only — gated on `windows-com`.
|
||||
#[cfg_attr(
|
||||
not(all(windows, feature = "windows-com")),
|
||||
allow(dead_code)
|
||||
)]
|
||||
fn parse_bracketed_host_port(binding: &str) -> Result<(String, u16), NmxClientError> {
|
||||
let open = binding.rfind('[').ok_or_else(|| NmxClientError::EndpointResolution {
|
||||
reason: format!("binding {binding:?} has no '['"),
|
||||
|
||||
@@ -36,6 +36,9 @@ serde = ["mxaccess-codec/serde"]
|
||||
# `live` gates integration tests that hit a running AVEVA install. Driven by
|
||||
# the `MX_LIVE` env var via `tools/Setup-LiveProbeEnv.ps1`.
|
||||
live = []
|
||||
# Pulls F12's `Session::connect_nmx_auto` constructor — the auto-resolving
|
||||
# COM-activation path. Propagates to `mxaccess-nmx/windows-com`.
|
||||
windows-com = ["mxaccess-nmx/windows-com"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -393,14 +393,18 @@ pub(crate) async fn callback_router(
|
||||
}
|
||||
|
||||
impl Session {
|
||||
/// Open a session over the NMX transport. Mirrors the wire-side of
|
||||
/// `MxNativeSession.Open` (`MxNativeSession.cs:127-147`) — `Open`
|
||||
/// itself is .NET-side: COM-activates `NmxSvc.NmxService`, marshals
|
||||
/// an OBJREF, calls ResolveOxid + RemQI to discover `(host, port,
|
||||
/// service_ipid)`, then calls `RegisterEngine2`. The Rust port
|
||||
/// requires the caller to pre-resolve those because COM activation
|
||||
/// is not yet wired (followup F12); the call sequence after that is
|
||||
/// identical.
|
||||
/// Open a session over the NMX transport with caller-supplied
|
||||
/// `(addr, service_ipid)`. Mirrors the wire-side of
|
||||
/// `MxNativeSession.Open` (`MxNativeSession.cs:127-147`).
|
||||
///
|
||||
/// The .NET shape of `Open` includes a COM-activation pre-step
|
||||
/// (`NmxSvc.NmxService` → `CoMarshalInterface` → `ResolveOxid` →
|
||||
/// `IRemUnknown::RemQueryInterface`) that auto-resolves
|
||||
/// `(host, port, service_ipid)` before `RegisterEngine2`. This
|
||||
/// constructor takes the resolved triple by hand — useful for
|
||||
/// tests, deterministic probes, and non-Windows builds. Use
|
||||
/// [`Self::connect_nmx_auto`] (Windows + `windows-com` feature) to
|
||||
/// drive the auto-resolving path.
|
||||
///
|
||||
/// On success: a `RegisterEngine2` round-trip has completed and the
|
||||
/// LMX server has acknowledged the engine registration. The
|
||||
@@ -421,7 +425,66 @@ impl Session {
|
||||
recovery: RecoveryPolicy,
|
||||
) -> Result<Self, Error> {
|
||||
recovery.validate()?;
|
||||
let nmx = NmxClient::connect(addr, service_ipid, ntlm)
|
||||
.await
|
||||
.map_err(map_nmx)?;
|
||||
Self::from_nmx_client(nmx, options, resolver).await
|
||||
}
|
||||
|
||||
/// Auto-resolving NMX session bring-up. Mirrors `MxNativeSession.Open`'s
|
||||
/// .NET shape (`MxNativeSession.cs:127-147`) end-to-end — including
|
||||
/// the COM-activation step that
|
||||
/// [`Self::connect_nmx`] expects the caller to have done by hand.
|
||||
///
|
||||
/// Internally this drives [`mxaccess_nmx::NmxClient::create`] (F12)
|
||||
/// to discover `(host, port, service_ipid)` via:
|
||||
/// `marshal_activated_iunknown_objref("NmxSvc.NmxService")` →
|
||||
/// `ComObjRef::parse` → `ResolveOxid` against `127.0.0.1:135` →
|
||||
/// `IRemUnknown::RemQueryInterface` → bind `INmxService2`. Then
|
||||
/// runs the same post-NMX setup as [`Self::connect_nmx`]
|
||||
/// (`CallbackExporter`, router task, `RegisterEngine2`, optional
|
||||
/// heartbeat). Only available on Windows with the `windows-com`
|
||||
/// feature.
|
||||
///
|
||||
/// `ntlm_factory` is invoked **three times** (once per bind in the
|
||||
/// COM-activation chain). Callers typically capture credentials
|
||||
/// from `MX_TEST_*` env vars and produce a fresh
|
||||
/// [`NtlmClientContext`] each call.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [`Error::Connection`] for any failure in the COM-activation /
|
||||
/// OXID-resolution / RemQI chain (mapped from
|
||||
/// `NmxClientError::Activation` and `NmxClientError::EndpointResolution`),
|
||||
/// plus the same failures `connect_nmx` can return after the NMX
|
||||
/// client is built.
|
||||
#[cfg(all(windows, feature = "windows-com"))]
|
||||
pub async fn connect_nmx_auto(
|
||||
ntlm_factory: impl FnMut() -> NtlmClientContext,
|
||||
options: SessionOptions,
|
||||
resolver: Arc<dyn Resolver>,
|
||||
recovery: RecoveryPolicy,
|
||||
) -> Result<Self, Error> {
|
||||
recovery.validate()?;
|
||||
let nmx = NmxClient::create(ntlm_factory).await.map_err(map_nmx)?;
|
||||
Self::from_nmx_client(nmx, options, resolver).await
|
||||
}
|
||||
|
||||
/// Shared post-NMX-bind orchestration: stand up the
|
||||
/// `CallbackExporter` + router task, call `RegisterEngine2` with
|
||||
/// the local callback OBJREF, optionally configure heartbeats, and
|
||||
/// assemble [`SessionInner`].
|
||||
///
|
||||
/// Mirrors steps 1, 2, 4, 5 of the .NET `MxNativeSession.Open`
|
||||
/// shape (`cs:148-170`). Both [`Self::connect_nmx`] and
|
||||
/// [`Self::connect_nmx_auto`] funnel through here so the
|
||||
/// callback-exporter / register / heartbeat policy stays in one
|
||||
/// place.
|
||||
async fn from_nmx_client(
|
||||
mut nmx: NmxClient,
|
||||
options: SessionOptions,
|
||||
resolver: Arc<dyn Resolver>,
|
||||
) -> Result<Self, Error> {
|
||||
// 1. Bind a local CallbackExporter on an OS-assigned ephemeral
|
||||
// port, then build the OBJREF advertising it. Hostname comes
|
||||
// from `local_hostname()` (env-var lookup); falls back to
|
||||
@@ -447,12 +510,7 @@ impl Session {
|
||||
let (callback_tx, _) = broadcast::channel(CALLBACK_BROADCAST_CAPACITY);
|
||||
let router_handle = tokio::spawn(callback_router(callback_events, callback_tx.clone()));
|
||||
|
||||
// 3. Open the NMX transport + bind.
|
||||
let mut nmx = NmxClient::connect(addr, service_ipid, ntlm)
|
||||
.await
|
||||
.map_err(map_nmx)?;
|
||||
|
||||
// 4. RegisterEngine2 with the callback OBJREF. Mirrors cs:163-175.
|
||||
// 3. RegisterEngine2 with the callback OBJREF. Mirrors cs:163-175.
|
||||
let hr = nmx
|
||||
.register_engine_2(
|
||||
options.local_engine_id,
|
||||
@@ -468,7 +526,7 @@ impl Session {
|
||||
return Err(Error::Connection(ConnectionError::EngineNotRegistered));
|
||||
}
|
||||
|
||||
// 5. Optional heartbeat-interval setup (cs:165-167).
|
||||
// 4. Optional heartbeat-interval setup (cs:165-167).
|
||||
if let Some(ticks) = options.heartbeat_ticks_per_beat {
|
||||
let hr = nmx
|
||||
.set_heartbeat_send_interval(ticks, options.heartbeat_max_missed_ticks)
|
||||
|
||||
Reference in New Issue
Block a user