Files
mxaccess/docs/F50-suspend-activate-evidence.md
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

73 lines
5.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
```text
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.
```text
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
```powershell
$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.