Files
mxaccess/docs/F50-suspend-activate-evidence.md
T
Joseph Doherty 349e217ea3 [F50] live Suspend/Activate captures — Suspend wires opcode 0x2D, Activate client-side
Re-ran analysis/frida/mx-nmx-trace.js (with the F46 hooks for
LmxProxy.dll!CLMXProxyServer.Suspend / .Activate) against
MxTraceHarness on the local AVEVA install. Two captures landed:

- captures/123-frida-suspend-advised-instrumented/
  Scenario: --scenario=suspend-advised --tag=TestChildObject.ScanState

  After mx.suspend.begin/end at 17:23:51.949Z, NMX PutRequest fires
  ~140ms later with body:
    2d 01 00                                       command 0x2D, version 0x0001
    cd 2a ee ec b2 76 06 4f b4 58 5c a0 2d f7 a8 93 16-byte correlation_id (matches the prior AdviseSupervisory)
    01 00 05 00 01 00 02 00 01 00 69 00 0a 00      engine + handle + attribute / property ids
    47 92 00 00 03 00 00 00                        trailer

  TransferData wraps it; HRESULT 0 returned; ProcessDataReceived
  callback delivers a 50-byte op-status frame; LMX surfaces it
  through CUserConnectionCallback.OperationComplete. Suspend is
  unambiguously server-side wire op 0x2D.

- captures/124-frida-activate-advised-instrumented/
  Scenario: --scenario=activate-advised --tag=TestChildObject.ScanState

  Activate fires at 17:26:02.982Z and returns Success synchronously
  with no NMX traffic. The next NMX activity is 7+ seconds later
  (harness teardown). Activate against a non-suspended item is
  client-side only on this build.

The harness's activate-advised scenario doesn't sequence
Suspend-then-Activate, so we don't have direct evidence for
Activate-after-Suspend. Circumstantial reasoning: since Suspend
goes server-side with a state change, Activate likely also does to
revert. If direct evidence becomes needed, add a new
suspend-then-activate scenario to MxTraceHarness/Program.cs and
re-run.

design/70-risks-and-open-questions.md R5 moves to "settled —
Suspend is wire op 0x2D, Activate behaviour is conditional",
severity downgraded P2 -> P3 (no public Session::suspend /
Session::activate API exists today; if added later, 0x2D is the
encoder target).

design/followups.md F50 marked resolved.

docs/F50-suspend-activate-evidence.md: per-capture byte-level
evidence + repro recipe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 13:29:40 -04:00

5.3 KiB
Raw Blame History

F50 Suspend / Activate live evidence — 2026-05-06

Live re-run of analysis/frida/mx-nmx-trace.js (with the F46 hook additions for LmxProxy.dll!CLMXProxyServer.Suspend / .Activate) against MxTraceHarness.exe on the local AVEVA install. Two captures land:

# Path Scenario Tag
123 captures/123-frida-suspend-advised-instrumented/ --scenario=suspend-advised TestChildObject.ScanState
124 captures/124-frida-activate-advised-instrumented/ --scenario=activate-advised TestChildObject.ScanState

Capture 123 — Suspend IS server-side

After mx.suspend.begin fires at 17:23:51.949Z, NMX wire traffic appears within ~140ms:

17:23:51.949Z  mx.suspend.begin     (CLMXProxyServer.Suspend, serverHandle=1, itemHandle=1)
17:23:51.949Z  mx.suspend.end       (Status: Success=-1 / MxCategoryPending / MxSourceRequestingLmx / Detail=0)
17:23:52.089Z  nmx.enter PutRequest body=
                 2d 01 00                                          ← command 0x2D, version 0x0001
                 cd 2a ee ee b2 76 06 4f b4 58 5c a0 2d f7 a8 93   ← 16-byte correlation_id (matches the prior AdviseSupervisory)
                 01 00 05 00 01 00 02 00 01 00                     ← reserved / engine + handle context
                 69 00 0a 00 47 92 00 00                           ← attribute / property ids
                 03 00 00 00                                       ← trailer
17:23:52.089Z  nmx.enter TransferData (envelope wraps the above 41-byte body, target_galaxy=1, target_platform=1, target_engine=2)
17:23:52.090Z  nmx.leave TransferData (HRESULT 0 = success)
17:23:52.090Z  nmx.leave PutRequest   (HRESULT 0 = success)
17:23:52.123Z  nmx.enter ProcessDataReceived  (50-byte op-status frame back from engine)
17:23:52.183Z  call.enter CUserConnectionCallback.OperationComplete  (LMX surfaces the op-status to the client)

The 41-byte body has the same shape as AdviseSupervisory's body (1f 01 00 + correlation_id + ...) — same family of INmxService2 item-control ops. The opcode 0x2D is what LmxProxy.dll!CLMXProxyServer.Suspend puts on the wire.

Capture 124 — Activate against an already-active item is client-side

The activate-advised harness scenario does not call Suspend first — it just AdviseSupervisory + Activate. So the Activate is invoked on an already-active item.

After mx.activate.begin fires at 17:26:02.982Z, the next NMX traffic is at 17:26:10.20Z (7+ seconds later — that's the harness's UnAdvise / Unregister at scenario teardown). No wire op fires for the Activate itself.

17:26:02.982Z  mx.activate.begin    (CLMXProxyServer.Activate, serverHandle=1, itemHandle=1)
17:26:02.982Z  mx.activate.end      (Status: Success=-1 / category=175 / Detail=0)  ← returns instantly client-side
17:26:10.206Z  nmx.enter PutRequest  ← unrelated, harness teardown (UnAdvise / Unregister)

Verdict

  • Suspend is server-side with NMX command 0x2D. The wire body shape matches AdviseSupervisory's structurally (<command:1> <version:2> <correlation_id:16> <body...>). The full body decode (engine / handle / attribute id meanings of bytes 1940) is left for a future codec port — the M6 F50 deliverable is the existence + opnum + correlation-id evidence.
  • Activate (against a non-suspended item) is client-side only in this scenario — the LMX proxy returns success without emitting wire traffic. We don't have direct evidence for Activate-after-Suspend (the harness's activate-advised scenario doesn't sequence them); circumstantial reasoning is that since Suspend goes server-side, Activate likely also does when it has a suspension to revert. If a future capture is needed, add a suspend-then-activate scenario to MxTraceHarness/Program.cs.

What this changes

  • R5 in design/70-risks-and-open-questions.md moves to "settled — Suspend is wire op 0x2D, Activate behaviour is conditional."
  • A future codec follow-up could port the 0x2D body shape into a typed encoder/decoder under crates/mxaccess-codec/src/. Not blocking M6 / V1 — Session::suspend / Session::activate aren't part of the public API today; they'd be additions.
  • analysis/proxy/nmxsvcps-procedures.tsv could grow a row for opnum 0x2D once someone correlates the Frida capture against the INmxService2 MIDL. Out of scope for F50.

Reproducing

$frida   = "C:\Users\dohertj2\AppData\Local\Programs\Python\Python312\Scripts\frida.exe"
$harness = "C:\Users\dohertj2\Desktop\mxaccess\src\MxTraceHarness\bin\Release\net481\MxTraceHarness.exe"
$script  = "C:\Users\dohertj2\Desktop\mxaccess\analysis\frida\mx-nmx-trace.js"
$cap     = "C:\Users\dohertj2\Desktop\mxaccess\captures\<NNN>-frida-<scenario>-instrumented"
mkdir $cap
& $frida -f $harness -l $script -- `
    --scenario=suspend-advised `   # or activate-advised
    --tag=TestChildObject.ScanState `
    --duration=8 `
    --log="$cap\harness.log" `
    --client="MxFridaTrace-<NNN>" `
  > "$cap\frida.stdout.jsonl" 2> "$cap\frida.stderr.txt"

The harness needs the local AVEVA Galaxy running with TestChildObject deployed. Frida 17.x; Python 3.12. The MxTraceHarness.exe is the x86 / net481 build at bin/Release/net481/dotnet build src/MxTraceHarness/MxTraceHarness.csproj /p:Configuration=Release produces it.