fe2a6db786
rust / build / test / clippy / fmt (push) Has been cancelled
Layout:
- src/ .NET 10 x64 reference: MxNativeCodec, MxNativeClient,
MxAsbClient, probes, tests, harnesses. Executable spec.
- design/ Architectural plan for the Rust port (M0–M6), error
model, protocol invariants, risks (R1–R16), adversarial
review log (review.md).
- rust/ Rust workspace. M0 skeleton + M1 codec parity.
mxaccess-codec: 215 unit tests + 2 cross-implementation
parity tests (byte-identical against .NET reference).
Other crates are M0 stubs awaiting M2+.
- captures/ Frida + netsh + pcap evidence per CLAUDE.md
("captures are evidence, not throwaway logs").
- analysis/ Decompiled C# (frida/proxy/decompiled-*),
Ghidra exports for native DLLs (`exports/` only —
working state at `projects/` and AVEVA's input
binaries at `input/` are gitignored).
- docs/ Reverse-engineering reference docs.
- tools/ Setup-LiveProbeEnv.ps1 (Infisical credential fetcher),
Compute-Crc.ps1 (.NET parity helper).
- .github/workflows/ Rust CI: fmt + build + test + clippy on Windows.
- LICENSE MIT (Joseph Doherty, 2026).
Verified:
- cargo test --workspace → 217 passed (215 unit + 2 .NET parity), 0 failed
- cargo clippy --workspace -- -D warnings → clean
- cargo fmt --all -- --check → clean
- cargo publish --dry-run -p mxaccess-codec → packages cleanly
Excluded from history (see .gitignore):
- **/bin, **/obj, **/target — build artifacts
- analysis/ghidra/projects/ — Ghidra working state (regenerable)
- analysis/ghidra/input/ — AVEVA proprietary DLLs (vendor IP)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
524 lines
24 KiB
Markdown
524 lines
24 KiB
Markdown
# MXAccess public API surface
|
|
|
|
Source of truth: `C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll`
|
|
|
|
Assembly identity:
|
|
|
|
- Name: `ArchestrA.MxAccess`
|
|
- Version: `3.2.0.0`
|
|
- Public key token: `23106a86e706d0ae`
|
|
- File product string: `Assembly imported from type library 'LMXPROXYLib'.`
|
|
- Processor architecture in metadata: `None`; practical use is still x86 because the COM class is a 32-bit in-proc server.
|
|
|
|
## COM class
|
|
|
|
`ArchestrA.MxAccess.LMXProxyServerClass`
|
|
|
|
- CLSID: `{C30B52F5-2CB5-4760-AF0A-3A344A7EB5DC}`
|
|
- ProgID: `LMXProxy.LMXProxyServer.1`
|
|
- Version-independent ProgID: `LMXProxy.LMXProxyServer`
|
|
- Registered server: `C:\Program Files (x86)\ArchestrA\Framework\Bin\LmxProxy.dll`
|
|
- Registry location: `HKCR\Wow6432Node\CLSID\{C30B52F5-2CB5-4760-AF0A-3A344A7EB5DC}`
|
|
- Threading model: `Apartment`
|
|
|
|
Because the server is under `Wow6432Node` and registered as an in-process DLL,
|
|
64-bit callers cannot instantiate it directly.
|
|
|
|
## Interface lineage
|
|
|
|
`ILMXProxyServer5` extends prior versions without changing the older method
|
|
signatures:
|
|
|
|
- `ILMXProxyServer` - original register/add/advise/write/auth API.
|
|
- `ILMXProxyServer2` - adds `ArchestrAUserToId`.
|
|
- `ILMXProxyServer3` - adds `AddItem2`.
|
|
- `ILMXProxyServer4` - adds `Write2`, `WriteSecured2`, `Suspend`, `Activate`, `AdviseSupervisory`.
|
|
- `ILMXProxyServer5` - adds buffered item support.
|
|
|
|
Interface GUIDs:
|
|
|
|
- `ILMXProxyServer`: `{CCE67FB7-EAFD-4367-9212-617043BF126D}`
|
|
- `ILMXProxyServer2`: `{020A8A87-69C5-497F-A893-B629E669FBFF}`
|
|
- `ILMXProxyServer3`: `{57D006B6-F25E-4654-A81E-BCBAFD60FE59}`
|
|
- `ILMXProxyServer4`: `{9DC0D5C1-9371-4E84-86F8-7091D316A66C}`
|
|
- `ILMXProxyServer5`: `{ECEFF506-A752-46E3-9E31-0A8E257C9926}`
|
|
|
|
## Methods
|
|
|
|
| Method | Purpose inferred from API and current usage |
|
|
| --- | --- |
|
|
| `Register(string pClientName) -> int` | Opens a client session and returns an LMX server handle. |
|
|
| `Unregister(int hLMXServerHandle)` | Closes the client session. |
|
|
| `AddItem(int hLMXServerHandle, string strItemDef) -> int` | Resolves/registers an item reference and returns an item handle. |
|
|
| `RemoveItem(int hLMXServerHandle, int hItem)` | Releases an item handle. |
|
|
| `Advise(int hLMXServerHandle, int hItem)` | Subscribes to item updates. |
|
|
| `UnAdvise(int hLMXServerHandle, int hItem)` | Cancels item updates. |
|
|
| `Write(int hLMXServerHandle, int hItem, object pItemValue, int userId)` | Writes a value. In current code `userId` is used as security classification. |
|
|
| `WriteSecured(int hLMXServerHandle, int hItem, int currentUserId, int verifierUserId, object pItemValue)` | Secured/verified write path. |
|
|
| `AuthenticateUser(int hLMXServerHandle, string verifyUser, string verifyUserPsw) -> int` | Authenticates a user and returns a numeric id. |
|
|
| `ArchestrAUserToId(int hLMXServerHandle, string userIdGuid) -> int` | Maps ArchestrA user GUID to integer id. |
|
|
| `AddItem2(int hLMXServerHandle, string strItemDef, string strItemCtxt) -> int` | Adds an item with a separate context string. |
|
|
| `Write2(int hLMXServerHandle, int hItem, object pItemValue, object pItemTime, int userId)` | Timestamped write. |
|
|
| `WriteSecured2(...)` | Timestamped secured/verified write. |
|
|
| `Suspend(int hLMXServerHandle, int hItem, out MxStatus status)` | Suspends an item/reference. |
|
|
| `Activate(int hLMXServerHandle, int hItem, out MxStatus status)` | Activates a suspended item/reference. |
|
|
| `AdviseSupervisory(int hLMXServerHandle, int hItem)` | Supervisory subscription mode. This is what the existing OPC UA bridge uses. |
|
|
| `AddBufferedItem(int hLMXServerHandle, string strItemDef, string strItemCtxt) -> int` | Adds a buffered item. |
|
|
| `SetBufferedUpdateInterval(int hLMXServerHandle, int updateInterval)` | Sets buffered update cadence. |
|
|
|
|
Reflection over the installed interop assembly confirms
|
|
`LMXProxyServerClass` exposes these 18 public methods plus the four public COM
|
|
event families: `OnDataChange`, `OnWriteComplete`, `OperationComplete`, and
|
|
`OnBufferedDataChange`. `Write2` and `WriteSecured2` expose their timestamp
|
|
argument as `object`/VARIANT. The .NET 10 compatibility facade keeps typed
|
|
`DateTime` overloads and also exposes object timestamp overloads for
|
|
API-shape parity.
|
|
|
|
## Secured write capture status
|
|
|
|
The current test node still does not produce a successful dedicated
|
|
`WriteSecured` wire path:
|
|
|
|
- `captures\036-frida-write-secured-test-int`: `WriteSecured` returned
|
|
`0x80004021` before any NMX `0x37` write body was emitted.
|
|
- `captures\038-frida-write-secured-protectedvalue`: `WriteSecured` returned
|
|
`0x80004021` before any NMX `0x37` write body was emitted.
|
|
- `captures\039-frida-write-secured-verified-protectedvalue1`: `WriteSecured`
|
|
returned `0x80004021` before any NMX `0x37` write body was emitted.
|
|
- `captures\111-frida-write-secured-auth-protectedvalue` and
|
|
`captures\112-frida-write-secured-auth-verified-protectedvalue1`:
|
|
`AuthenticateUser` succeeded first, but `WriteSecured` still returned
|
|
`0x80004021` before any value-bearing body.
|
|
- `captures\037-frida-write-secured2-test-int`: `WriteSecured2` returned
|
|
`0x80070057` before any NMX write body was emitted.
|
|
|
|
Headless Ghidra output in
|
|
`analysis\ghidra\exports\LmxProxy.dll.write-secured-decompile.md` shows why
|
|
these paths split: `WriteSecured` has an extra item-record flag check that can
|
|
return `0x80004021`; `WriteSecured2` skips that check and proceeds through the
|
|
authenticated timestamped writer when the user handles are mapped.
|
|
|
|
Normal `Write` calls to the secured/verified public test attributes did emit
|
|
ordinary `0x37` write bodies and succeeded in captures `040` and `041`. That
|
|
means the managed implementation supports the successful observed path through
|
|
normal `Write`.
|
|
|
|
Authenticated `WriteSecured2` now has successful captures and a managed encoder.
|
|
Captures `113`-`116` show command `0x38` over transfer kind `Write` (`3`) for
|
|
the boolean secured/verified tags in this GR. The body carries the current-user
|
|
authenticator token before the client name and the verifier token after the
|
|
client name. Live .NET 10 x64 probes succeeded for
|
|
`TestMachine_001.ProtectedValue` with no verifier and
|
|
`TestMachine_001.ProtectedValue1` with verifier handle `1`, both through the
|
|
low-level session API and through the handle-based compatibility facade. The
|
|
facade does not synthesize `OnWriteComplete` for this path because the native
|
|
successful captures did not show a public write-complete event. Capture `117`
|
|
adds authenticated `WriteSecured2` for integer `TestChildObject.TestInt`; it
|
|
uses the same generic timestamped-prefix rule, so the managed encoder is no
|
|
longer bool-only. Live managed probes also succeeded for float, double, string,
|
|
datetime, and the scalar-array value kinds. Native captures beyond bool and int
|
|
would still be useful as additional golden fixtures, but the managed encoder is
|
|
now generic over the existing timestamped write-body support.
|
|
|
|
## User mapping capture status
|
|
|
|
The x86 `MxTraceHarness` `user-map` scenario directly calls
|
|
`CLMXProxyServer.ArchestrAUserToId`. On this test node, it does not return the
|
|
Galaxy Repository `user_profile_id`:
|
|
|
|
| Capture | Input GUID | MXAccess return |
|
|
| --- | --- | ---: |
|
|
| `captures\mxaccess-user-map-administrator.log` | `9222FBBA-53F4-457E-8B37-C93A9A250B4A` | `1` |
|
|
| `captures\mxaccess-user-map-systemengineer.log` | `626A355F-737E-45F8-9740-43372220DEAB` | `1` |
|
|
| `captures\mxaccess-user-map-defaultuser.log` | `F4A9B907-6E72-48AE-83B5-BBDE918C890F` | `1` |
|
|
| `captures\mxaccess-user-map-invalid.log` | `00000000-0000-0000-0000-000000000000` | `1` |
|
|
|
|
The managed compatibility layer mirrors this observed behavior for
|
|
`ArchestrAUserToId`. GR profile lookup remains available separately through
|
|
`GalaxyRepositoryUserResolver`.
|
|
|
|
## Authentication capture status
|
|
|
|
`captures\mxaccess-authenticate-user-administrator-empty.log` shows
|
|
`AuthenticateUser(session, "Administrator", "")` returning user ID `1`.
|
|
Frida captures `087-frida-authenticate-administrator-empty` and
|
|
`088-frida-authenticate-invalid-empty` show both `Administrator` and
|
|
`DefinitelyNotAUser` returning `S_OK` and user ID `1` when the password is
|
|
empty on this dev node. The Frida hook records only password length, not
|
|
password content.
|
|
|
|
Headless Ghidra decompile `analysis\ghidra\exports\LmxProxy.dll.auth-decompile.md`
|
|
shows `AuthenticateUser` calling the underlying user-authenticator object and,
|
|
when it receives a security token, incrementing a per-session user counter and
|
|
mapping that generated integer to the token GUID. This matches
|
|
`ArchestrAUserToId`: the public return value is a session-local MXAccess handle,
|
|
not the GR `dbo.user_profile.user_profile_id`.
|
|
|
|
`MxNativeCompatibilityServer.AuthenticateUser` now mirrors the observed dev-node
|
|
behavior by validating the server handle and returning a session-local user
|
|
handle. It deliberately ignores and does not retain password material. A
|
|
security-enabled Galaxy still needs dedicated captures before password/hash
|
|
verification can be implemented safely.
|
|
|
|
## AddItem2 context capture status
|
|
|
|
`captures\mxaccess-additem2-testint-context.log` shows
|
|
`CLMXProxyServer.AddItem2(session, "TestInt", "TestChildObject")` succeeding and
|
|
returning item handle `1`. That confirms the simple relative-reference form used
|
|
by `MxNativeCompatibilityServer.AddItem2`: combine context and item definition
|
|
as `TestChildObject.TestInt` before GR resolution. More complex context strings
|
|
still need captures.
|
|
|
|
## Advise capture status
|
|
|
|
`captures\mxaccess-plain-advise-testint.log` shows the public
|
|
`CLMXProxyServer.Advise(session, item)` method succeeding for
|
|
`TestChildObject.TestInt`. Frida capture `099-frida-plain-advise-testint`
|
|
captures the lower-level body: plain `Advise` sends the same item-control
|
|
`0x1f` body shape as the earlier `AdviseSupervisory` capture for the same
|
|
scalar tag, including the same trailing option value `3`. The managed
|
|
compatibility layer therefore routes both public methods through the same
|
|
decoded subscription body for the observed scalar path.
|
|
|
|
## Suspend/Activate capture status
|
|
|
|
`captures\mxaccess-suspend-testint.log` and
|
|
`captures\mxaccess-activate-testint.log` show the public MXAccess methods
|
|
throwing `0x80070057` for `TestChildObject.TestInt` before a usable `MxStatus`
|
|
is returned. These captures establish that the test integer attribute is not a
|
|
valid suspend/activate scenario. A successful capture against the correct item
|
|
class is still required before implementing native NMX bodies for these methods.
|
|
|
|
## Buffered item capture status
|
|
|
|
The x86 harness now covers the public buffered APIs:
|
|
|
|
- `captures\mxaccess-set-buffered-interval-1000.log` shows
|
|
`SetBufferedUpdateInterval(session, 1000)` succeeding.
|
|
- `captures\mxaccess-add-buffered-testint-context.log` shows
|
|
`AddBufferedItem(session, "TestInt", "TestChildObject")` succeeding and
|
|
returning item handle `1`.
|
|
- `captures\mxaccess-add-buffered-write-testint-context.log` shows that using
|
|
the buffered item handle with normal `Write` throws `0x80070057`.
|
|
- `captures\079-frida-add-buffered-advise-testint` and
|
|
`captures\080-frida-buffered-external-write-testint` show the advised
|
|
buffered registration body. MXAccess does not send the normal `0x1f`
|
|
item-control advise for the buffered handle. It sends an item-control `0x10`
|
|
reference registration for `TestInt.property(buffer)` in context
|
|
`TestChildObject`, wrapped in a `MessageKind=2` transfer envelope.
|
|
- `captures\121-frida-buffered-history-testhistoryvalue-context` and
|
|
`captures\122-frida-buffered-history-testhistoryvalue-plainadvise` repeat the
|
|
buffered registration against GR-confirmed historized integer attribute
|
|
`TestMachine_001.TestHistoryValue`. Both supervisory and plain advise forms
|
|
emit the same context-bearing `0x10`/`0x11` registration/result shape for
|
|
`TestHistoryValue.property(buffer)` in context `TestMachine_001`.
|
|
Separate writer-session writes succeed and normal writer data callbacks are
|
|
observed, but native MXAccess still does not enter `Fire_OnBufferedDataChange`
|
|
on this VM.
|
|
- `captures\085-frida-subscribe-property-buffer` and
|
|
`captures\086-frida-write-property-buffer` show that adding the literal item
|
|
`TestChildObject.TestInt.property(buffer)` does not enter the public
|
|
`AddBufferedItem` helper. It follows the normal add/advise path, sends the
|
|
same item-control `0x10` reference-registration body with an empty item
|
|
context, and receives an NMX registration-result frame carrying the runtime
|
|
internal-error text for `TestInt.property(buffer)`. Normal `Write` against
|
|
the literal handle returns from `CLMXProxyServer.Write` without producing an
|
|
observed buffered callback.
|
|
- Ghidra decompile of `LmxProxy.dll` function `1001121d` confirms the public
|
|
`AddBufferedItem` implementation allocates a BSTR copy of `strItemDef`,
|
|
appends `.property(buffer)`, calls the normal add-item implementation, and
|
|
marks the resulting item record as buffered. Function `1000fc80` confirms
|
|
`SetBufferedUpdateInterval` rejects intervals below `1` and stores the
|
|
interval as `(milliseconds + 99) / 100`, effectively rounding up to 100 ms
|
|
ticks.
|
|
- Ghidra decompile of `100163c0` confirms `OnBufferedDataChange` is fired
|
|
through the `_ILMXProxyServerEvents2` connection point with seven arguments:
|
|
server handle, item handle, data type, value variant, quality variant,
|
|
timestamp variant, and status array.
|
|
- Headless xref/decompile output shows `Fire_OnBufferedDataChange` is reached
|
|
from the same native `OnDataChange callback received` method used for normal
|
|
data changes. The callback looks up the item record and branches on the
|
|
buffered item flag: non-buffered items fire `OnDataChange`; buffered items
|
|
convert the callback value into value, quality, and timestamp SAFEARRAY
|
|
variants before firing `OnBufferedDataChange`.
|
|
- The buffered value conversion helper maps MX buffered element type `1` to a
|
|
`VT_BOOL` value array, `2` to `VT_I4`, `3` to `VT_R4`, `4` to `VT_R8`, `5`
|
|
to `VT_BSTR`, and `6`/`7` to `VT_UI8` FILETIME-style values. Quality is
|
|
emitted as a `VT_I2` SAFEARRAY, timestamp as a `VT_UI8` SAFEARRAY, and the
|
|
status argument uses the normal `MXSTATUS_PROXY[]` SAFEARRAY.
|
|
|
|
No live `OnBufferedDataChange` payload has been observed yet. A source/runtime
|
|
condition that actually delivers buffered sample batches is still needed to
|
|
validate the wire body and multi-sample parser. The managed library now
|
|
implements the decoded add/registration surface, including context-bearing
|
|
buffered registrations, and routes parsed buffered callbacks to a separate
|
|
managed buffered event instead of the normal data-change event.
|
|
|
|
## Events
|
|
|
|
Event source interfaces:
|
|
|
|
- `_ILMXProxyServerEvents`: `{848299B6-DD61-4A0D-A304-3947A564B89C}`
|
|
- `_ILMXProxyServerEvents2`: `{C70A6FC4-09EF-4F31-8874-A049FEE87A95}`
|
|
|
|
Events:
|
|
|
|
```csharp
|
|
void OnDataChange(
|
|
int hLMXServerHandle,
|
|
int phItemHandle,
|
|
object pvItemValue,
|
|
int pwItemQuality,
|
|
object pftItemTimeStamp,
|
|
ref MXSTATUS_PROXY[] pVars);
|
|
|
|
void OnWriteComplete(
|
|
int hLMXServerHandle,
|
|
int phItemHandle,
|
|
ref MXSTATUS_PROXY[] pVars);
|
|
|
|
void OperationComplete(
|
|
int hLMXServerHandle,
|
|
int phItemHandle,
|
|
ref MXSTATUS_PROXY[] pVars);
|
|
|
|
void OnBufferedDataChange(
|
|
int hLMXServerHandle,
|
|
int phItemHandle,
|
|
MxDataType dtDataType,
|
|
object pvItemValue,
|
|
object pwItemQuality,
|
|
object pftItemTimeStamp,
|
|
ref MXSTATUS_PROXY[] pVars);
|
|
```
|
|
|
|
Ghidra decompile of `LmxProxy.dll` event helpers confirms that
|
|
`Fire_OnWriteComplete` and `Fire_OperationComplete` both construct a three
|
|
element `VARIANT` argument array containing server handle, item handle, and one
|
|
`MXSTATUS_PROXY` SAFEARRAY. `Fire_OnWriteComplete` dispatches event ID `2`;
|
|
`Fire_OperationComplete` dispatches event ID `3`. In the capture set, successful
|
|
writes raise only `OnWriteComplete`; no `mx.event.operation-complete` line has
|
|
been observed yet.
|
|
|
|
Headless Ghidra xref/decompile output in
|
|
`analysis\ghidra\exports\LmxProxy.dll.event-xrefs.md` and
|
|
`analysis\ghidra\exports\LmxProxy.dll.event-callers-decompile.md` narrows the
|
|
source further: `Fire_OnWriteComplete` is reached from
|
|
`CUserConnectionCallback::OnSetAttributeResult`, while `Fire_OperationComplete`
|
|
is reached from `CUserConnectionCallback::OperationComplete`. They are distinct
|
|
callback paths even though their COM event payload shape is the same.
|
|
|
|
The installed interop assemblies expose the lower-level callback as
|
|
`IMxCallback2.OperationComplete(int lCallbackId, ref MxStatus, string)`. They
|
|
also expose `IDataConsumer.ActivateSuspend` and `ProcessActivateSuspend2`, whose
|
|
response type is `ItemActiveResponse`. That makes the DataConsumer
|
|
activate/suspend completion path the strongest remaining native trigger
|
|
candidate. The public `LMXProxyServerClass.Suspend` and `Activate` methods
|
|
tested in captures `118` and `119` instead decompile to direct
|
|
`IMxScanOnDemand` calls and did not enter
|
|
`CUserConnectionCallback.OperationComplete` on this node.
|
|
|
|
`aaMxDataConsumer.dll` is registered separately as `MxDataConsumer Class`
|
|
(`{85209FB2-0BA1-4594-BBC4-59D3DDAB823D}`) and exposes the same
|
|
`IDataConsumer` activate/suspend methods through its type library. A targeted
|
|
x86 probe can instantiate it and call those methods, but the standalone object
|
|
currently reports `IsConnected(namespaceId)=0` and
|
|
`ProcessActivateSuspend2` returns `0x8007139F`. That means the COM surface is
|
|
reachable, but a DataConsumer/DataClient bootstrap step is still missing before
|
|
it can prove the public `OperationComplete` trigger.
|
|
|
|
The mixed-mode `aaMxDataConsumer.dll` decompile shows that bootstrap should
|
|
ultimately route through managed ASB IData proxies: `CDataClientCLI` owns a
|
|
`DataClientProxy`, starts an auto-connect worker, and passes the namespace
|
|
string as an ASB access name to
|
|
`IDataProxySelector.SelectProxyForLatestEndpoint`. `ASBIDataV2Adapter.dll`
|
|
contains that selector; it looks for `IASBIDataV2` endpoints under
|
|
`domainname/<accessName>/global` before falling back to IData V1. A new x64
|
|
managed probe found and connected to the live `IASBIDataV2` endpoint with
|
|
access name `ZB`, then successfully called `PublishWriteComplete`. This does
|
|
not prove `OperationComplete` yet, but it proves the relevant data-service
|
|
route can be reached from managed x64 code without MXAccess or COM.
|
|
|
|
The same x64 ASB probe now proves the register/read/write/complete flow for
|
|
`TestChildObject.TestInt`. `RegisterItems` and `Read` succeed, ASB type `4`
|
|
decodes as `Int32`, `Write(401)` is accepted with per-item
|
|
`OperationWouldBlock`, the next read returns `401`, and
|
|
`PublishWriteComplete` returns the submitted write handle with final per-item
|
|
success. That gives a concrete managed completion queue to model for ASB-native
|
|
write-complete behavior; `OperationComplete` should still not be synthesized
|
|
until an operation other than a basic write is proven to use the native
|
|
MXAccess event ID `3` path.
|
|
|
|
The same core flow is now reproduced by the pure .NET 10 x64
|
|
`src\MxAsbClient` implementation with no AVEVA assembly references. Its live
|
|
probe reads the ASB solution secret through DPAPI, performs the system-auth
|
|
handshake, reads `TestChildObject.TestInt`, writes a new integer value, reads
|
|
that value back, and decodes `PublishWriteComplete` result `0x00000020` with
|
|
the submitted write handle and final per-item success. `RegisterItems` now
|
|
matches the observed ASB startup behavior: the first immediate call after
|
|
one-way `AuthenticateMe` can return `0x00000001`, so the client retries briefly
|
|
and receives the expected item status/id once the server-side implementation is
|
|
registered. The remaining ASB-native work is API breadth: multi-item calls,
|
|
subscriptions, scalar/array type matrix, and status/error mapping.
|
|
|
|
`UnregisterItems` is now also implemented and compared against the installed
|
|
AVEVA `ASBDataV2Proxy`. Both the pure .NET 10 client and installed proxy return
|
|
global success for the unregister call and the same per-item `0x0000000B`
|
|
(`OperationFailed`) for `TestChildObject.TestInt` on this provider. That is
|
|
documented as parity with the deployed ASB provider, not a successful item-level
|
|
cleanup status.
|
|
|
|
The pure .NET 10 client also handles multi-item register/read bodies. A two-tag
|
|
probe registered and read `TestChildObject.TestInt` plus
|
|
`TestMachine_001.TestHistoryValue` in single requests; both returned per-item
|
|
success and decoded as ASB `TypeInt32`.
|
|
|
|
For write status callbacks, the public event is tied to the non-length-prefixed
|
|
5-byte operation-status body `00 00 50 80 00`. Length-prefixed completion-only
|
|
bodies are lower-level NMX status frames and did not produce public
|
|
`OnWriteComplete` events in captures `089`, `091`, `092`, or `093`, even when
|
|
the completion byte was `0x00`.
|
|
|
|
## Data types
|
|
|
|
`MxDataType`:
|
|
|
|
```text
|
|
Unknown = -1
|
|
NoData = 0
|
|
Boolean = 1
|
|
Integer = 2
|
|
Float = 3
|
|
Double = 4
|
|
String = 5
|
|
Time = 6
|
|
ElapsedTime = 7
|
|
ReferenceType = 8
|
|
StatusType = 9
|
|
Enum = 10
|
|
SecurityClassificationEnum = 11
|
|
DataQualityType = 12
|
|
QualifiedEnum = 13
|
|
QualifiedStruct = 14
|
|
InternationalizedString = 15
|
|
BigString = 16
|
|
END = 17
|
|
```
|
|
|
|
Live GR inventory on this node found deployed/configured instances of all core
|
|
scalar types plus `ElapsedTime` and `InternationalizedString`. Target references
|
|
captured for the two non-core types:
|
|
|
|
- `TestMachine_001.TestAlarm001.Alarm.TimeDeadband`: `ElapsedTime`, observed
|
|
subscription callback wire kind `0x07` with four-byte zero payload in
|
|
`captures\063-frida-subscribe-elapsed-time-deadband`.
|
|
- `TestChildObject.ShortDesc`: `InternationalizedString`, observed callback
|
|
normalizes the empty value to string wire kind `0x05` with compact payload
|
|
`04 00 00 00` in `captures\062-frida-subscribe-intl-shortdesc`.
|
|
- Non-empty GR defaults exist at `DevPlatform._EngUnitsPercent` and
|
|
`DevAppEngine.Scheduler._EngUnitsMB`; captures `064` and `065` resolved and
|
|
advised those items but did not emit a value callback during the capture
|
|
window.
|
|
|
|
Write projection for these non-core types is caller-variant based in the
|
|
observed MXAccess path. Capture `095` wrote an `Int32` to `ElapsedTime` and
|
|
emitted integer wire kind `0x02`; capture `096` wrote a `string` to
|
|
`InternationalizedString` and emitted string wire kind `0x05`. The managed
|
|
library mirrors those projections for write attempts while keeping the data
|
|
types out of generic `TryGetValueKind` classification.
|
|
|
|
`MxStatus` and `MXSTATUS_PROXY` are identical sequential structs with 4-byte
|
|
packing:
|
|
|
|
```csharp
|
|
public short success;
|
|
public MxStatusCategory category;
|
|
public MxStatusSource detectedBy;
|
|
public short detail;
|
|
```
|
|
|
|
`MxStatusCategory`:
|
|
|
|
```text
|
|
Unknown = -1
|
|
Ok = 0
|
|
Pending = 1
|
|
Warning = 2
|
|
CommunicationError = 3
|
|
ConfigurationError = 4
|
|
OperationalError = 5
|
|
SecurityError = 6
|
|
SoftwareError = 7
|
|
OtherError = 8
|
|
```
|
|
|
|
`MxStatusSource`:
|
|
|
|
```text
|
|
Unknown = -1
|
|
RequestingLmx = 0
|
|
RespondingLmx = 1
|
|
RequestingNmx = 2
|
|
RespondingNmx = 3
|
|
RequestingAutomationObject = 4
|
|
RespondingAutomationObject = 5
|
|
```
|
|
|
|
## Status detail text
|
|
|
|
`C:\Program Files (x86)\Common Files\ArchestrA\Framework\Bin\Lmx.aaDCT`
|
|
contains localized text for common status detail codes. The .NET 10 managed
|
|
model now includes the installed English entries:
|
|
|
|
| Detail | Text |
|
|
| ---: | --- |
|
|
| 16 | Request timed out |
|
|
| 17 | Platform communication error |
|
|
| 18 | Invalid platform ID |
|
|
| 19 | Invalid engine ID |
|
|
| 20 | Engine communication error |
|
|
| 21 | Invalid reference |
|
|
| 22 | No Galaxy Repository |
|
|
| 23 | Invalid object ID |
|
|
| 24 | Object signature mismatch |
|
|
| 25 | Invalid primitive ID |
|
|
| 26 | Invalid attribute ID |
|
|
| 27 | Invalid property ID |
|
|
| 28 | Index out of range |
|
|
| 29 | Data out of range |
|
|
| 30 | Incorrect data type |
|
|
| 31 | Attribute not readable |
|
|
| 32 | Attribute not writeable |
|
|
| 33 | Write access denied |
|
|
| 34 | Unknown error |
|
|
| 35 | detected by |
|
|
| 36 | Wrong data type |
|
|
| 37 | Wrong number of dimensions |
|
|
| 38 | Invalid index |
|
|
| 39 | Index out of order |
|
|
| 40 | Dimension does not exist |
|
|
| 41 | Conversion not supported |
|
|
| 42 | Unable to convert string |
|
|
| 43 | Overflow |
|
|
| 44 | Attribute signature mismatch |
|
|
| 45 | Resolving local portion of reference |
|
|
| 46 | Resolving global portion of reference |
|
|
| 47 | Nmx version mismatch |
|
|
| 48 | Nmx command not valid |
|
|
| 49 | Lmx version mismatch |
|
|
| 50 | Lmx command not valid |
|
|
| 51 | However, the object could not be put On Scan - Permission to modify "Operate" attributes is required |
|
|
| 52 | Unable to resolve reference for 'set' request because Galaxy Repository is busy performing a 'Deploy/Undeploy' operation |
|
|
| 53 | Too many outstanding pending requests to engine |
|
|
| 54 | Object Initializing |
|
|
| 55 | Engine Initializing |
|
|
| 56 | Secured Write |
|
|
| 57 | Verified Write |
|
|
| 58 | No Alarm Ack Privilege |
|
|
| 59 | Alarm Acked Already |
|
|
| 60 | User did not have the necessary permissions to write |
|
|
| 61 | Verifier did not have the necessary permissions to verify |
|
|
| 541 | Conversion to intended data type is not supported |
|
|
| 542 | Unable to convert the input string to intended data type |
|
|
| 8017 | Object must be offscan to modify attributes that have an MxSecurityConfigure security classification |
|