[F55] Path C investigation: NmxSvc requires SCM-registered OXID for callbacks
Captured OBJREF byte structures from both paths via the .NET probe: - `--probe-callback-marshal`: DCOM-marshalled, 338 bytes, succeeds (when used inside `MxNativeSession.Open` → `CreateRegisteredService`). - `--probe-register-managed-callback`: hand-rolled, 162 bytes, fails with `RegisterEngine2 → 0x800706BA RPC_S_SERVER_UNAVAILABLE`. The structural diff: - `std_flags`: DCOM=`0x0A80` (SORF_OXRES4+6+8) vs hand-rolled=`0x280` (SORF_OXRES4+6). Bit `0x0800` (SORF_OXRES8) only set in DCOM. - ncacn_ip_tcp bindings: DCOM=4 with no ports; hand-rolled=1 with explicit `[port]`. - Total size: 338 vs 162 bytes. Tested the simplest fix (hand-rolled `std_flags = 0x0A80` to match DCOM): **still fails with the same 1722.** Reverted. **Diagnosis updated in F55:** NmxSvc on receiving RegisterEngine2 appears to call `IObjectExporter::ResolveOxid` against the local SCM (`127.0.0.1:135`) to resolve the callback OBJREF's OXID, then dial the resulting bindings. Our hand-rolled OXID is never registered with RPCSS, so the SCM-side resolution fails and NmxSvc returns RPC_S_SERVER_UNAVAILABLE — matching: - the symptom (1722), - the sub-second timing (no TCP dial-back to our listener attempted), - the fact that the .NET `ManagedCallbackExporter` (same hand-rolled approach) ALSO fails identically. DCOM marshalling fixes this because `CoMarshalInterface` internally registers the OXID with RPCSS. The bindings have no port because RPCSS returns the dynamic port from the DCOM stub layer. **Conclusion: Path A is the architecturally correct fix** — the callback exporter must be a DCOM-managed object (e.g. via `windows-rs` `#[implement]`) for NmxSvc to accept the callback. The hand-rolled-listener-with-explicit-port approach is fundamentally incompatible with NmxSvc's callback validation, in both Rust and the .NET reference. Path C (cheap investigation) is exhausted; F55 verdict updated to recommend Path A explicitly. `cargo test --workspace` 824 passing; clippy `-D warnings` clean across both feature configurations. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -210,9 +210,13 @@ impl CallbackExporter {
|
||||
/// Build a callback OBJREF to publish back to the AVEVA service.
|
||||
///
|
||||
/// Mirrors `ManagedCallbackExporter.CreateCallbackObjRef`
|
||||
/// (`cs:44-54`): the IID is `INmxSvcCallback`, `std_flags = 0x280`,
|
||||
/// (`cs:44-54`): the IID is `INmxSvcCallback`,
|
||||
/// `public_refs = 5`, OXID/OID/IPID come from `self.identities`, and
|
||||
/// the single string binding is `"<hostname>[<port>]"`.
|
||||
///
|
||||
/// `std_flags = 0x280` — `SORF_OXRES4 | SORF_OXRES6` (= `0x80 |
|
||||
/// 0x200`). Mirrors the .NET reference's `ManagedCallbackExporter`
|
||||
/// (`cs:48`).
|
||||
#[must_use]
|
||||
pub fn create_callback_objref(&self, hostname: &str) -> Vec<u8> {
|
||||
let binding = format!("{hostname}[{port}]", port = self.local_addr.port());
|
||||
|
||||
Reference in New Issue
Block a user