# 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//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 |