327 lines
9.3 KiB
Markdown
327 lines
9.3 KiB
Markdown
# MXAccess Gateway Agent Guide
|
|
|
|
Repository: https://gitea.dohertylan.com/dohertj2/mxaccessgw
|
|
|
|
This project builds a gateway that gives modern clients full MXAccess parity
|
|
without requiring those clients to load MXAccess COM, run as x86, or own an STA
|
|
message pump. Treat the installed MXAccess COM component as the compatibility
|
|
baseline.
|
|
|
|
## Core Contract
|
|
|
|
Preserve MXAccess behavior first:
|
|
|
|
- public MXAccess command semantics,
|
|
- native MXAccess event families,
|
|
- STA/message-pump delivery behavior,
|
|
- installed-provider quirks,
|
|
- HRESULT/status/value marshaling,
|
|
- per-client isolation.
|
|
|
|
Do not simplify, normalize, or "fix" MXAccess behavior unless an explicit
|
|
non-parity mode is being implemented and tested. `MxAsbClient` and managed NMX
|
|
are future acceleration paths only; they do not define the parity contract.
|
|
|
|
## Architecture
|
|
|
|
The intended split is:
|
|
|
|
```text
|
|
client
|
|
-> gRPC over TCP
|
|
-> .NET 10 x64 gateway
|
|
-> session manager
|
|
-> per-session .NET Framework 4.8 x86 worker process
|
|
-> dedicated STA thread
|
|
-> MXAccess COM instance
|
|
-> Windows/COM message pump
|
|
-> command queue
|
|
-> event sink
|
|
```
|
|
|
|
The gateway must never instantiate or call MXAccess directly. All MXAccess COM
|
|
interaction belongs in the worker process on its dedicated STA thread.
|
|
|
|
The worker must not host public gRPC. Gateway-to-worker communication should use
|
|
a small local IPC protocol, with named pipes and protobuf-framed messages as the
|
|
default design.
|
|
|
|
## Runtime Targets
|
|
|
|
- Gateway: .NET 10, C#, x64 preferred, ASP.NET Core gRPC.
|
|
- Worker: .NET Framework 4.8, C#, x86 by default.
|
|
- Worker IPC: one bidirectional named pipe per worker.
|
|
- Worker process model: one external client session maps to one worker by
|
|
default.
|
|
|
|
## Expected Layout
|
|
|
|
Prefer this structure unless there is a strong reason to adjust it:
|
|
|
|
```text
|
|
src/MxGateway.Contracts/
|
|
Protos/
|
|
mxaccess_gateway.proto
|
|
mxaccess_worker.proto
|
|
Generated/
|
|
|
|
src/MxGateway.Server/
|
|
Program.cs
|
|
Sessions/
|
|
Workers/
|
|
Grpc/
|
|
Metrics/
|
|
|
|
src/MxGateway.Worker/
|
|
Program.cs
|
|
Ipc/
|
|
Sta/
|
|
MxAccess/
|
|
Conversion/
|
|
|
|
src/MxGateway.Tests/
|
|
contract tests
|
|
gateway session tests
|
|
fake worker tests
|
|
|
|
src/MxGateway.Worker.Tests/
|
|
value/status conversion tests
|
|
STA queue tests
|
|
|
|
src/MxGateway.IntegrationTests/
|
|
optional live MXAccess tests
|
|
```
|
|
|
|
The contracts project may multi-target, or the `.proto` files may be shared as
|
|
source inputs to both gateway and worker builds.
|
|
|
|
## Public API Shape
|
|
|
|
The external API should be session-oriented. Initial rollout should prefer
|
|
unary `OpenSession`, `CloseSession`, and `Invoke`, plus server-streaming
|
|
`StreamEvents`. Add a bidirectional `Session` stream after the command and event
|
|
model is stable.
|
|
|
|
Do not compress MXAccess into generic verbs too early. Use a command enum with
|
|
method-specific payloads so parity can be tested method by method.
|
|
|
|
Core MXAccess commands to represent:
|
|
|
|
- `Register`
|
|
- `Unregister`
|
|
- `AddItem`
|
|
- `AddItem2`
|
|
- `RemoveItem`
|
|
- `Advise`
|
|
- `UnAdvise`
|
|
- `AdviseSupervisory`
|
|
- `AddBufferedItem`
|
|
- `SetBufferedUpdateInterval`
|
|
- `Suspend`
|
|
- `Activate`
|
|
- `Write`
|
|
- `Write2`
|
|
- `WriteSecured`
|
|
- `WriteSecured2`
|
|
- `AuthenticateUser`
|
|
- `ArchestrAUserToId`
|
|
|
|
Diagnostics may include `Ping`, `GetSessionState`, `GetWorkerInfo`,
|
|
`DrainEvents`, and `ShutdownWorker`.
|
|
|
|
## Event Requirements
|
|
|
|
Represent every public MXAccess event family:
|
|
|
|
- `OnDataChange`
|
|
- `OnWriteComplete`
|
|
- `OperationComplete`
|
|
- `OnBufferedDataChange`
|
|
|
|
Preserve per-worker event order. The gateway must not reorder events emitted by
|
|
the same MXAccess instance.
|
|
|
|
Event DTOs should carry event family, session id, server handle, item handle,
|
|
value, quality, timestamp, `MXSTATUS_PROXY[]` equivalent, raw HRESULT/status
|
|
fields when available, event sequence, worker timestamp, and gateway receive
|
|
timestamp.
|
|
|
|
## Value And Status Rules
|
|
|
|
Use a protobuf value union that can represent COM `VARIANT` values and arrays.
|
|
When a value cannot be losslessly converted, preserve both the best typed
|
|
projection and enough raw diagnostic metadata to reproduce the case.
|
|
|
|
Represent `MXSTATUS_PROXY` explicitly. Do not collapse status arrays into a
|
|
single success flag.
|
|
|
|
Command replies should include protocol status, COM HRESULT if available,
|
|
MXAccess return values, method-specific out parameters, and status arrays where
|
|
the MXAccess method emits them.
|
|
|
|
## Worker Rules
|
|
|
|
Each worker owns:
|
|
|
|
- one process,
|
|
- one MXAccess session,
|
|
- one dedicated STA thread,
|
|
- one MXAccess COM object,
|
|
- one inbound command queue,
|
|
- one outbound event queue.
|
|
|
|
All MXAccess operations must run on the STA. A plain blocking queue is not
|
|
enough for the STA; the STA loop must pump Windows/COM messages and service
|
|
queued commands.
|
|
|
|
Do not block the STA on pipe writes, gRPC calls, or slow consumers. Event
|
|
handlers should convert event args, enqueue outbound events, and return to
|
|
pumping messages.
|
|
|
|
On graceful shutdown, reject new commands, optionally clean up active MXAccess
|
|
handles, detach events, release the COM object, uninitialize COM, and exit. If
|
|
graceful shutdown exceeds the configured timeout, the gateway may kill the
|
|
worker.
|
|
|
|
## IPC Rules
|
|
|
|
Default pipe name shape:
|
|
|
|
```text
|
|
mxaccess-gateway-{gatewayProcessId}-{sessionId}
|
|
```
|
|
|
|
Frame messages as:
|
|
|
|
```text
|
|
uint32 little-endian payload_length
|
|
payload_length bytes protobuf WorkerEnvelope
|
|
```
|
|
|
|
Every envelope should include protocol version, session id, monotonic sender
|
|
sequence, correlation id, and a typed body. Protocol version mismatch should
|
|
fail session creation.
|
|
|
|
Pipe security should be local-machine only, with ACLs restricted to the gateway
|
|
identity and launched worker identity. Prefer a per-session nonce handshake.
|
|
|
|
## Gateway Rules
|
|
|
|
The gateway is responsible for:
|
|
|
|
- public TCP/gRPC API,
|
|
- authn/authz when needed,
|
|
- session creation and teardown,
|
|
- worker launch and lifecycle management,
|
|
- command routing,
|
|
- event streaming,
|
|
- leases, heartbeats, timeouts, and quotas,
|
|
- worker kill/restart policy,
|
|
- metrics and structured logs.
|
|
|
|
The gRPC layer should stay thin: validate request, find session, call the
|
|
session worker client, map worker replies to public replies, and stream events.
|
|
Keep MXAccess-specific translation logic testable outside the gRPC handlers.
|
|
|
|
Gateway restart should not try to reattach old workers in the first version.
|
|
Terminate orphaned workers on startup if that behavior is implemented.
|
|
|
|
## Command, Timeout, And Cancellation Semantics
|
|
|
|
Command lifecycle:
|
|
|
|
```text
|
|
client gRPC command
|
|
gateway validates session and payload
|
|
gateway assigns correlation id
|
|
gateway writes WorkerCommand to pipe
|
|
worker queues command to STA
|
|
STA executes MXAccess method
|
|
worker captures return/out/status/HRESULT
|
|
worker sends WorkerCommandReply
|
|
gateway completes gRPC response
|
|
```
|
|
|
|
Canceling a gRPC call should stop waiting in the gateway, but it cannot safely
|
|
abort an in-flight COM call on the STA. Hard cancellation means killing the
|
|
worker process.
|
|
|
|
If a command wedges the STA beyond a configured grace period, the gateway should
|
|
kill the worker and fail the session.
|
|
|
|
## Backpressure Policy
|
|
|
|
Worker outbound events must use a bounded queue. For parity testing, prefer
|
|
fail-fast behavior over silent drops. Production coalescing or drop policies
|
|
must be explicit and observable.
|
|
|
|
The gateway should preserve per-session event order, apply backpressure from
|
|
slow gRPC streams, and disconnect or coalesce only according to an explicit
|
|
policy.
|
|
|
|
## Security And Logging
|
|
|
|
Use TLS for remote gRPC when crossing machine boundaries. Authentication may be
|
|
Windows auth, mTLS, or a deployment-specific token.
|
|
|
|
Commands that write, authenticate users, or alter runtime state need explicit
|
|
authorization design.
|
|
|
|
Never log passwords or raw credential values for `AuthenticateUser`,
|
|
`WriteSecured`, or related secured operations. Do not log full values by
|
|
default; make value logging opt-in and redacted.
|
|
|
|
## Testing Expectations
|
|
|
|
Use focused tests for:
|
|
|
|
- contract/protobuf compatibility,
|
|
- gateway session state and worker lifecycle,
|
|
- gateway behavior with a fake worker,
|
|
- worker value/status conversion,
|
|
- STA queue and message-pump behavior.
|
|
|
|
Live MXAccess integration tests are optional but should be isolated because they
|
|
depend on installed COM components and provider behavior.
|
|
|
|
Parity tests should compare direct MXAccess behavior against the gateway:
|
|
|
|
- return values,
|
|
- HRESULTs and exceptions,
|
|
- event sequence,
|
|
- value projection,
|
|
- quality/status arrays,
|
|
- invalid handle behavior,
|
|
- cross-server handle behavior,
|
|
- cleanup behavior.
|
|
|
|
Known important parity areas:
|
|
|
|
- `WriteSecured` may fail before a value-bearing NMX body is emitted.
|
|
- `WriteSecured2` can succeed in observed native paths.
|
|
- `OperationComplete` is distinct from write completion.
|
|
- `OnBufferedDataChange` has a distinct public event shape.
|
|
- Invalid handles and cross-server handles have specific exception/status
|
|
behavior.
|
|
- STA message pumping is required for event delivery.
|
|
|
|
## Implementation Priority
|
|
|
|
Build the smallest end-to-end slice first:
|
|
|
|
1. .NET 10 gateway starts.
|
|
2. Client calls `OpenSession`.
|
|
3. Gateway launches .NET Framework 4.8 x86 worker.
|
|
4. Worker creates STA and MXAccess COM object.
|
|
5. Client calls `Register`.
|
|
6. Client calls `AddItem`.
|
|
7. Client calls `Advise`.
|
|
8. Worker forwards one `OnDataChange` event to the gateway.
|
|
9. Gateway streams the event to the client.
|
|
10. Client calls `CloseSession`.
|
|
11. Gateway shuts down the worker.
|
|
|
|
That slice proves the high-risk requirements: process isolation, STA ownership,
|
|
message pumping, command routing, and event streaming.
|
|
|