Compare commits

...

5 Commits

Author SHA1 Message Date
Joseph Doherty 647fe9a4b5 Merge remote-tracking branch 'origin/main' into agent-2/issue-29-implement-event-sink-and-event-queue 2026-04-26 19:05:31 -04:00
Joseph Doherty dd455089b4 Implement worker MXAccess event queue 2026-04-26 19:04:56 -04:00
dohertj2 d0bc4e3c01 Merge pull request #82 from agent-1/issue-36-publish-stable-client-proto-generation-inputs
Issue #36: publish stable client proto generation inputs
2026-04-26 18:56:37 -04:00
Joseph Doherty 6a40d26366 Publish stable client proto inputs 2026-04-26 18:52:39 -04:00
dohertj2 366f57198f Merge pull request #81 from agent-2/issue-28-implement-advise-unadvise-advisesupervisory
Issue #28: implement Advise, UnAdvise, AdviseSupervisory
2026-04-26 18:45:59 -04:00
35 changed files with 1574 additions and 284 deletions
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
@@ -0,0 +1,28 @@
{
"family": "MX_EVENT_FAMILY_ON_DATA_CHANGE",
"sessionId": "session-fixture",
"serverHandle": 12,
"itemHandle": 34,
"value": {
"dataType": "MX_DATA_TYPE_INTEGER",
"variantType": "VT_I4",
"int32Value": 123
},
"quality": 192,
"sourceTimestamp": "2026-01-01T00:00:00Z",
"statuses": [
{
"success": 1,
"category": "MX_STATUS_CATEGORY_OK",
"detectedBy": "MX_STATUS_SOURCE_RESPONDING_LMX",
"detail": 0,
"rawCategory": 0,
"rawDetectedBy": 0,
"diagnosticText": "OK"
}
],
"workerSequence": "1",
"workerTimestamp": "2026-01-01T00:00:00.010Z",
"gatewayReceiveTimestamp": "2026-01-01T00:00:00.015Z",
"onDataChange": {}
}
@@ -0,0 +1,18 @@
{
"sessionId": "session-fixture",
"backendName": "mxaccess-worker",
"workerProcessId": 1234,
"workerProtocolVersion": 1,
"gatewayProtocolVersion": 1,
"capabilities": [
"unary-open-session",
"unary-close-session",
"unary-invoke",
"server-stream-events"
],
"defaultCommandTimeout": "30s",
"protocolStatus": {
"code": "PROTOCOL_STATUS_CODE_OK",
"message": "Session opened."
}
}
@@ -0,0 +1,10 @@
{
"sessionId": "session-fixture",
"clientCorrelationId": "fixture-register-1",
"command": {
"kind": "MX_COMMAND_KIND_REGISTER",
"register": {
"clientName": "fixture-client"
}
}
}
+26
View File
@@ -0,0 +1,26 @@
{
"schemaVersion": 1,
"contractName": "mxaccess-gateway",
"gatewayProtocolVersion": 1,
"workerProtocolVersion": 1,
"protoRoot": "src/MxGateway.Contracts/Protos",
"sourceFiles": [
{
"path": "mxaccess_gateway.proto",
"role": "public_gateway"
},
{
"path": "mxaccess_worker.proto",
"role": "gateway_worker_ipc"
}
],
"descriptorSet": "clients/proto/descriptors/mxaccessgw-client-v1.protoset",
"fixtureRoot": "clients/proto/fixtures/golden",
"generatedOutputs": {
"dotnet": "clients/dotnet/generated",
"go": "clients/go/internal/generated",
"rust": "clients/rust/src/generated",
"python": "clients/python/src/mxgateway/generated",
"java": "clients/java/src/main/generated"
}
}
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+13
View File
@@ -18,6 +18,12 @@ event, value, and status shapes.
Generated C# output is written to `src/MxGateway.Contracts/Generated/`. Do not
hand-edit generated files.
Client generation inputs are published through
`clients/proto/proto-inputs.json` and the descriptor set under
`clients/proto/descriptors/`. See
[Client Proto Generation](./client-proto-generation.md) for language-specific
generation inputs, output directories, and golden protobuf JSON fixtures.
## Generation
Run the contracts build to regenerate C# protobuf and gRPC code:
@@ -39,8 +45,15 @@ gateway and test projects:
dotnet build src/MxGateway.sln
```
Regenerate the client descriptor after changing either `.proto` file:
```bash
powershell -ExecutionPolicy Bypass -File scripts/publish-client-proto-inputs.ps1
```
## Related Documentation
- [Client Proto Generation](./client-proto-generation.md)
- [Gateway Process Detailed Design](./gateway-process-design.md)
- [MXAccess Worker Instance Detailed Design](./mxaccess-worker-instance-design.md)
- [Protobuf Style Guide](./style-guides/ProtobufStyleGuide.md)
+15
View File
@@ -26,6 +26,11 @@ Language-specific plans:
- `docs/clients-python-design.md`
- `docs/clients-java-design.md`
Shared generation inputs:
- `docs/client-proto-generation.md`
- `clients/proto/proto-inputs.json`
Language style guides:
| Client | Style guide |
@@ -365,6 +370,16 @@ examples/
Generated code should be reproducible from `src/MxGateway.Contracts/Protos/`.
Do not hand-edit generated code.
The stable client proto manifest defines the generated-code directories:
```text
clients/dotnet/generated
clients/go/internal/generated
clients/rust/src/generated
clients/python/src/mxgateway/generated
clients/java/src/main/generated
```
## Versioning
All clients should expose:
+133
View File
@@ -0,0 +1,133 @@
# Client Proto Generation
This document defines the stable protobuf inputs that official clients use to
generate language-specific gRPC bindings. The checked-in `.proto` files remain
the source of truth so clients do not drift from the gateway and worker
contracts.
## Stable Inputs
The stable client input manifest is `clients/proto/proto-inputs.json`. It
records:
- the public gateway protocol version,
- the worker IPC protocol version,
- the protobuf import root,
- the public and worker source files,
- the descriptor set path,
- golden fixture locations,
- generated-code output directories for each planned client.
The source files listed by the manifest are:
- `src/MxGateway.Contracts/Protos/mxaccess_gateway.proto`
- `src/MxGateway.Contracts/Protos/mxaccess_worker.proto`
`mxaccess_gateway.proto` defines the public gRPC service and shared DTOs.
`mxaccess_worker.proto` is included in the descriptor because worker-aware
tests and fake-worker clients need the same command, reply, event, value, and
status shapes.
## Protocol Version
`GatewayContractInfo.GatewayProtocolVersion` is the public gateway protocol
version. `OpenSessionReply.gateway_protocol_version` returns the same value so
clients can compare their generated bindings against the gateway before issuing
MXAccess commands.
`GatewayContractInfo.WorkerProtocolVersion` remains the gateway-to-worker IPC
protocol version. It is also present in `OpenSessionReply` because parity
fixtures and fake-worker tests need to know the worker contract used by the
session.
## Descriptor Publishing
Run this command after changing either source `.proto` file or the client proto
manifest:
```powershell
scripts/publish-client-proto-inputs.ps1
```
The script writes
`clients/proto/descriptors/mxaccessgw-client-v1.protoset` with imports and
source information included. The descriptor is a generated artifact; do not edit
it by hand.
Use the check mode in CI or before committing:
```powershell
scripts/publish-client-proto-inputs.ps1 -Check
```
`-Check` rebuilds the descriptor in a temporary path and fails when the checked
in descriptor is stale.
## Output Directories
The manifest declares these generated-code directories:
| Client | Directory |
|--------|-----------|
| .NET | `clients/dotnet/generated` |
| Go | `clients/go/internal/generated` |
| Rust | `clients/rust/src/generated` |
| Python | `clients/python/src/mxgateway/generated` |
| Java | `clients/java/src/main/generated` |
Only generator output belongs in these directories. Handwritten client wrappers
belong in the language-specific source trees created by the client scaffold
issues.
## Language Generation Inputs
All generators use `src/MxGateway.Contracts/Protos` as the protobuf import
root. The checked-in descriptor is available when a language build prefers a
descriptor input, but the `.proto` files remain canonical.
.NET generation currently runs through the contracts project:
```powershell
dotnet build src/MxGateway.Contracts/MxGateway.Contracts.csproj
```
Future .NET client projects may either reference `MxGateway.Contracts` or
generate client-local files into `clients/dotnet/generated` with `Grpc.Tools`.
Go clients should generate `mxaccess_gateway.proto` and
`mxaccess_worker.proto` into `clients/go/internal/generated` with
`protoc-gen-go` and `protoc-gen-go-grpc`. Keep generated packages internal
unless the wrapper API intentionally exposes raw protobuf messages.
Rust clients should use `tonic-build` or the selected protobuf generator from
the Rust client build script, with generated modules placed under
`clients/rust/src/generated` or included from the build output according to the
client crate design.
Python clients should use `grpc_tools.protoc` and write generated modules under
`clients/python/src/mxgateway/generated` so imports stay separate from
handwritten async wrappers.
Java clients should use the Gradle protobuf plugin and write generated sources
under `clients/java/src/main/generated`. The Java client scaffold owns the
Gradle plugin versions and source-set wiring.
## Golden Fixtures
Golden protobuf JSON fixtures live in `clients/proto/fixtures/golden`. They
exercise payloads that every language client must parse:
- `open-session-reply.ok.json`
- `register-command-request.json`
- `on-data-change-event.json`
The fixtures use protobuf JSON field names and enum values. Contract tests parse
them with the generated C# types so schema drift is caught before client
generation work starts.
## Related Documentation
- [Protobuf Contracts](./Contracts.md)
- [Client Libraries Detailed Design](./client-libraries-design.md)
- [Client Libraries Implementation Plan](./implementation-plan-clients.md)
- [Protobuf Style Guide](./style-guides/ProtobufStyleGuide.md)
@@ -277,6 +277,8 @@ Live tests:
Labels: `area:worker`, `type:feature`, `priority:p0`
Status: implemented.
Deliverables:
- handlers for `OnDataChange`,
+22 -3
View File
@@ -348,9 +348,28 @@ Event handling rules:
- Enqueue to the outbound event queue.
- Return quickly to preserve message pumping.
If event conversion throws, catch it inside the event handler, enqueue a
structured `WorkerFault` or diagnostic event, and keep the worker alive only if
the fault policy allows it.
`MxAccessBaseEventSink` implements the COM connection-point handlers and keeps
the handlers limited to event argument conversion plus enqueue. It uses
`MxAccessEventMapper` to create `MxEvent` DTOs for `OnDataChange`,
`OnWriteComplete`, `OperationComplete`, and `OnBufferedDataChange`. The mapper
converts scalar and array values through `VariantConverter`, converts
`MXSTATUS_PROXY[]` through `MxStatusProxyConverter`, and maps installed
`MxDataType` values to the public protobuf enum while preserving the raw data
type on buffered events. `OperationComplete` is only emitted from the native
`OperationComplete` handler; write completion does not synthesize it.
`MxAccessEventQueue` is the bounded outbound event queue for one worker
session. It assigns the monotonic `WorkerSequence` and `WorkerTimestamp` when an
event is accepted, preserving the order in which MXAccess handlers enqueue
events. The default capacity is `10000`. When the queue reaches capacity it
records a `WorkerFaultCategory.QueueOverflow` fault and rejects further events.
The event handler catches conversion and enqueue failures, records the first
fault on the queue, and returns to the STA message pump instead of writing to
the pipe.
If event conversion throws, catch it inside the event handler, record a
structured `WorkerFault`, and keep the worker alive only if the fault policy
allows it.
## Command Queue
+95
View File
@@ -0,0 +1,95 @@
[CmdletBinding()]
param(
[switch]$Check
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
$protoRoot = Join-Path $repoRoot "src/MxGateway.Contracts/Protos"
$manifestPath = Join-Path $repoRoot "clients/proto/proto-inputs.json"
$descriptorPath = Join-Path $repoRoot "clients/proto/descriptors/mxaccessgw-client-v1.protoset"
function Resolve-Protoc {
$pathCommand = Get-Command "protoc.exe" -ErrorAction SilentlyContinue
if ($null -ne $pathCommand) {
return $pathCommand.Source
}
$documentedPath = Join-Path $env:LOCALAPPDATA "Microsoft/WinGet/Packages/Google.Protobuf_Microsoft.Winget.Source_8wekyb3d8bbwe/bin/protoc.exe"
if (Test-Path $documentedPath) {
return $documentedPath
}
throw "Could not find protoc.exe. See docs/toolchain-links.md for the documented protobuf toolchain path."
}
function Ensure-Directory {
param([string]$Path)
if (-not (Test-Path $Path)) {
New-Item -ItemType Directory -Path $Path | Out-Null
}
}
function Compare-FileBytes {
param(
[string]$ExpectedPath,
[string]$ActualPath
)
if (-not (Test-Path $ExpectedPath)) {
return $false
}
$expected = [System.IO.File]::ReadAllBytes($ExpectedPath)
$actual = [System.IO.File]::ReadAllBytes($ActualPath)
if ($expected.Length -ne $actual.Length) {
return $false
}
for ($index = 0; $index -lt $expected.Length; $index++) {
if ($expected[$index] -ne $actual[$index]) {
return $false
}
}
return $true
}
$manifest = Get-Content -Raw $manifestPath | ConvertFrom-Json
Ensure-Directory (Split-Path $descriptorPath -Parent)
foreach ($output in $manifest.generatedOutputs.PSObject.Properties.Value) {
Ensure-Directory (Join-Path $repoRoot $output)
}
$protoc = Resolve-Protoc
$outputPath = $descriptorPath
if ($Check) {
$outputPath = Join-Path ([System.IO.Path]::GetTempPath()) ("mxaccessgw-client-v1-" + [System.Guid]::NewGuid().ToString("N") + ".protoset")
}
try {
& $protoc `
"--proto_path=$protoRoot" `
"--include_imports" `
"--include_source_info" `
"--descriptor_set_out=$outputPath" `
"mxaccess_gateway.proto" `
"mxaccess_worker.proto"
if ($LASTEXITCODE -ne 0) {
throw "protoc failed with exit code $LASTEXITCODE."
}
if ($Check -and -not (Compare-FileBytes -ExpectedPath $descriptorPath -ActualPath $outputPath)) {
throw "Client proto descriptor is stale. Run scripts/publish-client-proto-inputs.ps1 and commit the updated descriptor."
}
}
finally {
if ($Check -and (Test-Path $outputPath)) {
Remove-Item -LiteralPath $outputPath
}
}
@@ -6,6 +6,8 @@ namespace MxGateway.Contracts;
/// </summary>
public static class GatewayContractInfo
{
public const uint GatewayProtocolVersion = 1;
public const uint WorkerProtocolVersion = 1;
public const string DefaultBackendName = "mxaccess-worker";
@@ -30,282 +30,282 @@ namespace MxGateway.Contracts.Proto {
"ChFyZXF1ZXN0ZWRfYmFja2VuZBgBIAEoCRIbChNjbGllbnRfc2Vzc2lvbl9u",
"YW1lGAIgASgJEh0KFWNsaWVudF9jb3JyZWxhdGlvbl9pZBgDIAEoCRIyCg9j",
"b21tYW5kX3RpbWVvdXQYBCABKAsyGS5nb29nbGUucHJvdG9idWYuRHVyYXRp",
"b24iiAIKEE9wZW5TZXNzaW9uUmVwbHkSEgoKc2Vzc2lvbl9pZBgBIAEoCRIU",
"b24iqgIKEE9wZW5TZXNzaW9uUmVwbHkSEgoKc2Vzc2lvbl9pZBgBIAEoCRIU",
"CgxiYWNrZW5kX25hbWUYAiABKAkSGQoRd29ya2VyX3Byb2Nlc3NfaWQYAyAB",
"KAUSHwoXd29ya2VyX3Byb3RvY29sX3ZlcnNpb24YBCABKA0SFAoMY2FwYWJp",
"bGl0aWVzGAUgAygJEjoKF2RlZmF1bHRfY29tbWFuZF90aW1lb3V0GAYgASgL",
"MhkuZ29vZ2xlLnByb3RvYnVmLkR1cmF0aW9uEjwKD3Byb3RvY29sX3N0YXR1",
"cxgHIAEoCzIjLm14YWNjZXNzX2dhdGV3YXkudjEuUHJvdG9jb2xTdGF0dXMi",
"SAoTQ2xvc2VTZXNzaW9uUmVxdWVzdBISCgpzZXNzaW9uX2lkGAEgASgJEh0K",
"FWNsaWVudF9jb3JyZWxhdGlvbl9pZBgCIAEoCSKdAQoRQ2xvc2VTZXNzaW9u",
"UmVwbHkSEgoKc2Vzc2lvbl9pZBgBIAEoCRI2CgtmaW5hbF9zdGF0ZRgCIAEo",
"DjIhLm14YWNjZXNzX2dhdGV3YXkudjEuU2Vzc2lvblN0YXRlEjwKD3Byb3Rv",
"Y29sX3N0YXR1cxgDIAEoCzIjLm14YWNjZXNzX2dhdGV3YXkudjEuUHJvdG9j",
"b2xTdGF0dXMiSAoTU3RyZWFtRXZlbnRzUmVxdWVzdBISCgpzZXNzaW9uX2lk",
"GAEgASgJEh0KFWFmdGVyX3dvcmtlcl9zZXF1ZW5jZRgCIAEoBCJ2ChBNeENv",
"bW1hbmRSZXF1ZXN0EhIKCnNlc3Npb25faWQYASABKAkSHQoVY2xpZW50X2Nv",
"cnJlbGF0aW9uX2lkGAIgASgJEi8KB2NvbW1hbmQYAyABKAsyHi5teGFjY2Vz",
"c19nYXRld2F5LnYxLk14Q29tbWFuZCKiDAoJTXhDb21tYW5kEjAKBGtpbmQY",
"ASABKA4yIi5teGFjY2Vzc19nYXRld2F5LnYxLk14Q29tbWFuZEtpbmQSOAoI",
"cmVnaXN0ZXIYCiABKAsyJC5teGFjY2Vzc19nYXRld2F5LnYxLlJlZ2lzdGVy",
"Q29tbWFuZEgAEjwKCnVucmVnaXN0ZXIYCyABKAsyJi5teGFjY2Vzc19nYXRl",
"d2F5LnYxLlVucmVnaXN0ZXJDb21tYW5kSAASNwoIYWRkX2l0ZW0YDCABKAsy",
"Iy5teGFjY2Vzc19nYXRld2F5LnYxLkFkZEl0ZW1Db21tYW5kSAASOQoJYWRk",
"X2l0ZW0yGA0gASgLMiQubXhhY2Nlc3NfZ2F0ZXdheS52MS5BZGRJdGVtMkNv",
"bW1hbmRIABI9CgtyZW1vdmVfaXRlbRgOIAEoCzImLm14YWNjZXNzX2dhdGV3",
"YXkudjEuUmVtb3ZlSXRlbUNvbW1hbmRIABI0CgZhZHZpc2UYDyABKAsyIi5t",
"eGFjY2Vzc19nYXRld2F5LnYxLkFkdmlzZUNvbW1hbmRIABI5Cgl1bl9hZHZp",
"c2UYECABKAsyJC5teGFjY2Vzc19nYXRld2F5LnYxLlVuQWR2aXNlQ29tbWFu",
"ZEgAEksKEmFkdmlzZV9zdXBlcnZpc29yeRgRIAEoCzItLm14YWNjZXNzX2dh",
"dGV3YXkudjEuQWR2aXNlU3VwZXJ2aXNvcnlDb21tYW5kSAASSAoRYWRkX2J1",
"ZmZlcmVkX2l0ZW0YEiABKAsyKy5teGFjY2Vzc19nYXRld2F5LnYxLkFkZEJ1",
"ZmZlcmVkSXRlbUNvbW1hbmRIABJdChxzZXRfYnVmZmVyZWRfdXBkYXRlX2lu",
"dGVydmFsGBMgASgLMjUubXhhY2Nlc3NfZ2F0ZXdheS52MS5TZXRCdWZmZXJl",
"ZFVwZGF0ZUludGVydmFsQ29tbWFuZEgAEjYKB3N1c3BlbmQYFCABKAsyIy5t",
"eGFjY2Vzc19nYXRld2F5LnYxLlN1c3BlbmRDb21tYW5kSAASOAoIYWN0aXZh",
"dGUYFSABKAsyJC5teGFjY2Vzc19nYXRld2F5LnYxLkFjdGl2YXRlQ29tbWFu",
"ZEgAEjIKBXdyaXRlGBYgASgLMiEubXhhY2Nlc3NfZ2F0ZXdheS52MS5Xcml0",
"ZUNvbW1hbmRIABI0CgZ3cml0ZTIYFyABKAsyIi5teGFjY2Vzc19nYXRld2F5",
"LnYxLldyaXRlMkNvbW1hbmRIABJBCg13cml0ZV9zZWN1cmVkGBggASgLMigu",
"bXhhY2Nlc3NfZ2F0ZXdheS52MS5Xcml0ZVNlY3VyZWRDb21tYW5kSAASQwoO",
"d3JpdGVfc2VjdXJlZDIYGSABKAsyKS5teGFjY2Vzc19nYXRld2F5LnYxLldy",
"aXRlU2VjdXJlZDJDb21tYW5kSAASSQoRYXV0aGVudGljYXRlX3VzZXIYGiAB",
"KAsyLC5teGFjY2Vzc19nYXRld2F5LnYxLkF1dGhlbnRpY2F0ZVVzZXJDb21t",
"YW5kSAASTQoUYXJjaGVzdHJhX3VzZXJfdG9faWQYGyABKAsyLS5teGFjY2Vz",
"c19nYXRld2F5LnYxLkFyY2hlc3RyQVVzZXJUb0lkQ29tbWFuZEgAEjAKBHBp",
"bmcYZCABKAsyIC5teGFjY2Vzc19nYXRld2F5LnYxLlBpbmdDb21tYW5kSAAS",
"SAoRZ2V0X3Nlc3Npb25fc3RhdGUYZSABKAsyKy5teGFjY2Vzc19nYXRld2F5",
"LnYxLkdldFNlc3Npb25TdGF0ZUNvbW1hbmRIABJECg9nZXRfd29ya2VyX2lu",
"Zm8YZiABKAsyKS5teGFjY2Vzc19nYXRld2F5LnYxLkdldFdvcmtlckluZm9D",
"b21tYW5kSAASPwoMZHJhaW5fZXZlbnRzGGcgASgLMicubXhhY2Nlc3NfZ2F0",
"ZXdheS52MS5EcmFpbkV2ZW50c0NvbW1hbmRIABJFCg9zaHV0ZG93bl93b3Jr",
"ZXIYaCABKAsyKi5teGFjY2Vzc19nYXRld2F5LnYxLlNodXRkb3duV29ya2Vy",
"Q29tbWFuZEgAQgkKB3BheWxvYWQiJgoPUmVnaXN0ZXJDb21tYW5kEhMKC2Ns",
"aWVudF9uYW1lGAEgASgJIioKEVVucmVnaXN0ZXJDb21tYW5kEhUKDXNlcnZl",
"cl9oYW5kbGUYASABKAUiQAoOQWRkSXRlbUNvbW1hbmQSFQoNc2VydmVyX2hh",
"bmRsZRgBIAEoBRIXCg9pdGVtX2RlZmluaXRpb24YAiABKAkiVwoPQWRkSXRl",
"bTJDb21tYW5kEhUKDXNlcnZlcl9oYW5kbGUYASABKAUSFwoPaXRlbV9kZWZp",
"bml0aW9uGAIgASgJEhQKDGl0ZW1fY29udGV4dBgDIAEoCSI/ChFSZW1vdmVJ",
"dGVtQ29tbWFuZBIVCg1zZXJ2ZXJfaGFuZGxlGAEgASgFEhMKC2l0ZW1faGFu",
"ZGxlGAIgASgFIjsKDUFkdmlzZUNvbW1hbmQSFQoNc2VydmVyX2hhbmRsZRgB",
"IAEoBRITCgtpdGVtX2hhbmRsZRgCIAEoBSI9Cg9VbkFkdmlzZUNvbW1hbmQS",
"FQoNc2VydmVyX2hhbmRsZRgBIAEoBRITCgtpdGVtX2hhbmRsZRgCIAEoBSJG",
"ChhBZHZpc2VTdXBlcnZpc29yeUNvbW1hbmQSFQoNc2VydmVyX2hhbmRsZRgB",
"IAEoBRITCgtpdGVtX2hhbmRsZRgCIAEoBSJeChZBZGRCdWZmZXJlZEl0ZW1D",
"b21tYW5kEhUKDXNlcnZlcl9oYW5kbGUYASABKAUSFwoPaXRlbV9kZWZpbml0",
"aW9uGAIgASgJEhQKDGl0ZW1fY29udGV4dBgDIAEoCSJfCiBTZXRCdWZmZXJl",
"ZFVwZGF0ZUludGVydmFsQ29tbWFuZBIVCg1zZXJ2ZXJfaGFuZGxlGAEgASgF",
"EiQKHHVwZGF0ZV9pbnRlcnZhbF9taWxsaXNlY29uZHMYAiABKAUiPAoOU3Vz",
"cGVuZENvbW1hbmQSFQoNc2VydmVyX2hhbmRsZRgBIAEoBRITCgtpdGVtX2hh",
"bmRsZRgCIAEoBSI9Cg9BY3RpdmF0ZUNvbW1hbmQSFQoNc2VydmVyX2hhbmRs",
"ZRgBIAEoBRITCgtpdGVtX2hhbmRsZRgCIAEoBSJ4CgxXcml0ZUNvbW1hbmQS",
"FQoNc2VydmVyX2hhbmRsZRgBIAEoBRITCgtpdGVtX2hhbmRsZRgCIAEoBRIr",
"CgV2YWx1ZRgDIAEoCzIcLm14YWNjZXNzX2dhdGV3YXkudjEuTXhWYWx1ZRIP",
"Cgd1c2VyX2lkGAQgASgFIrABCg1Xcml0ZTJDb21tYW5kEhUKDXNlcnZlcl9o",
"cxgHIAEoCzIjLm14YWNjZXNzX2dhdGV3YXkudjEuUHJvdG9jb2xTdGF0dXMS",
"IAoYZ2F0ZXdheV9wcm90b2NvbF92ZXJzaW9uGAggASgNIkgKE0Nsb3NlU2Vz",
"c2lvblJlcXVlc3QSEgoKc2Vzc2lvbl9pZBgBIAEoCRIdChVjbGllbnRfY29y",
"cmVsYXRpb25faWQYAiABKAkinQEKEUNsb3NlU2Vzc2lvblJlcGx5EhIKCnNl",
"c3Npb25faWQYASABKAkSNgoLZmluYWxfc3RhdGUYAiABKA4yIS5teGFjY2Vz",
"c19nYXRld2F5LnYxLlNlc3Npb25TdGF0ZRI8Cg9wcm90b2NvbF9zdGF0dXMY",
"AyABKAsyIy5teGFjY2Vzc19nYXRld2F5LnYxLlByb3RvY29sU3RhdHVzIkgK",
"E1N0cmVhbUV2ZW50c1JlcXVlc3QSEgoKc2Vzc2lvbl9pZBgBIAEoCRIdChVh",
"ZnRlcl93b3JrZXJfc2VxdWVuY2UYAiABKAQidgoQTXhDb21tYW5kUmVxdWVz",
"dBISCgpzZXNzaW9uX2lkGAEgASgJEh0KFWNsaWVudF9jb3JyZWxhdGlvbl9p",
"ZBgCIAEoCRIvCgdjb21tYW5kGAMgASgLMh4ubXhhY2Nlc3NfZ2F0ZXdheS52",
"MS5NeENvbW1hbmQiogwKCU14Q29tbWFuZBIwCgRraW5kGAEgASgOMiIubXhh",
"Y2Nlc3NfZ2F0ZXdheS52MS5NeENvbW1hbmRLaW5kEjgKCHJlZ2lzdGVyGAog",
"ASgLMiQubXhhY2Nlc3NfZ2F0ZXdheS52MS5SZWdpc3RlckNvbW1hbmRIABI8",
"Cgp1bnJlZ2lzdGVyGAsgASgLMiYubXhhY2Nlc3NfZ2F0ZXdheS52MS5VbnJl",
"Z2lzdGVyQ29tbWFuZEgAEjcKCGFkZF9pdGVtGAwgASgLMiMubXhhY2Nlc3Nf",
"Z2F0ZXdheS52MS5BZGRJdGVtQ29tbWFuZEgAEjkKCWFkZF9pdGVtMhgNIAEo",
"CzIkLm14YWNjZXNzX2dhdGV3YXkudjEuQWRkSXRlbTJDb21tYW5kSAASPQoL",
"cmVtb3ZlX2l0ZW0YDiABKAsyJi5teGFjY2Vzc19nYXRld2F5LnYxLlJlbW92",
"ZUl0ZW1Db21tYW5kSAASNAoGYWR2aXNlGA8gASgLMiIubXhhY2Nlc3NfZ2F0",
"ZXdheS52MS5BZHZpc2VDb21tYW5kSAASOQoJdW5fYWR2aXNlGBAgASgLMiQu",
"bXhhY2Nlc3NfZ2F0ZXdheS52MS5VbkFkdmlzZUNvbW1hbmRIABJLChJhZHZp",
"c2Vfc3VwZXJ2aXNvcnkYESABKAsyLS5teGFjY2Vzc19nYXRld2F5LnYxLkFk",
"dmlzZVN1cGVydmlzb3J5Q29tbWFuZEgAEkgKEWFkZF9idWZmZXJlZF9pdGVt",
"GBIgASgLMisubXhhY2Nlc3NfZ2F0ZXdheS52MS5BZGRCdWZmZXJlZEl0ZW1D",
"b21tYW5kSAASXQocc2V0X2J1ZmZlcmVkX3VwZGF0ZV9pbnRlcnZhbBgTIAEo",
"CzI1Lm14YWNjZXNzX2dhdGV3YXkudjEuU2V0QnVmZmVyZWRVcGRhdGVJbnRl",
"cnZhbENvbW1hbmRIABI2CgdzdXNwZW5kGBQgASgLMiMubXhhY2Nlc3NfZ2F0",
"ZXdheS52MS5TdXNwZW5kQ29tbWFuZEgAEjgKCGFjdGl2YXRlGBUgASgLMiQu",
"bXhhY2Nlc3NfZ2F0ZXdheS52MS5BY3RpdmF0ZUNvbW1hbmRIABIyCgV3cml0",
"ZRgWIAEoCzIhLm14YWNjZXNzX2dhdGV3YXkudjEuV3JpdGVDb21tYW5kSAAS",
"NAoGd3JpdGUyGBcgASgLMiIubXhhY2Nlc3NfZ2F0ZXdheS52MS5Xcml0ZTJD",
"b21tYW5kSAASQQoNd3JpdGVfc2VjdXJlZBgYIAEoCzIoLm14YWNjZXNzX2dh",
"dGV3YXkudjEuV3JpdGVTZWN1cmVkQ29tbWFuZEgAEkMKDndyaXRlX3NlY3Vy",
"ZWQyGBkgASgLMikubXhhY2Nlc3NfZ2F0ZXdheS52MS5Xcml0ZVNlY3VyZWQy",
"Q29tbWFuZEgAEkkKEWF1dGhlbnRpY2F0ZV91c2VyGBogASgLMiwubXhhY2Nl",
"c3NfZ2F0ZXdheS52MS5BdXRoZW50aWNhdGVVc2VyQ29tbWFuZEgAEk0KFGFy",
"Y2hlc3RyYV91c2VyX3RvX2lkGBsgASgLMi0ubXhhY2Nlc3NfZ2F0ZXdheS52",
"MS5BcmNoZXN0ckFVc2VyVG9JZENvbW1hbmRIABIwCgRwaW5nGGQgASgLMiAu",
"bXhhY2Nlc3NfZ2F0ZXdheS52MS5QaW5nQ29tbWFuZEgAEkgKEWdldF9zZXNz",
"aW9uX3N0YXRlGGUgASgLMisubXhhY2Nlc3NfZ2F0ZXdheS52MS5HZXRTZXNz",
"aW9uU3RhdGVDb21tYW5kSAASRAoPZ2V0X3dvcmtlcl9pbmZvGGYgASgLMiku",
"bXhhY2Nlc3NfZ2F0ZXdheS52MS5HZXRXb3JrZXJJbmZvQ29tbWFuZEgAEj8K",
"DGRyYWluX2V2ZW50cxhnIAEoCzInLm14YWNjZXNzX2dhdGV3YXkudjEuRHJh",
"aW5FdmVudHNDb21tYW5kSAASRQoPc2h1dGRvd25fd29ya2VyGGggASgLMiou",
"bXhhY2Nlc3NfZ2F0ZXdheS52MS5TaHV0ZG93bldvcmtlckNvbW1hbmRIAEIJ",
"CgdwYXlsb2FkIiYKD1JlZ2lzdGVyQ29tbWFuZBITCgtjbGllbnRfbmFtZRgB",
"IAEoCSIqChFVbnJlZ2lzdGVyQ29tbWFuZBIVCg1zZXJ2ZXJfaGFuZGxlGAEg",
"ASgFIkAKDkFkZEl0ZW1Db21tYW5kEhUKDXNlcnZlcl9oYW5kbGUYASABKAUS",
"FwoPaXRlbV9kZWZpbml0aW9uGAIgASgJIlcKD0FkZEl0ZW0yQ29tbWFuZBIV",
"Cg1zZXJ2ZXJfaGFuZGxlGAEgASgFEhcKD2l0ZW1fZGVmaW5pdGlvbhgCIAEo",
"CRIUCgxpdGVtX2NvbnRleHQYAyABKAkiPwoRUmVtb3ZlSXRlbUNvbW1hbmQS",
"FQoNc2VydmVyX2hhbmRsZRgBIAEoBRITCgtpdGVtX2hhbmRsZRgCIAEoBSI7",
"Cg1BZHZpc2VDb21tYW5kEhUKDXNlcnZlcl9oYW5kbGUYASABKAUSEwoLaXRl",
"bV9oYW5kbGUYAiABKAUiPQoPVW5BZHZpc2VDb21tYW5kEhUKDXNlcnZlcl9o",
"YW5kbGUYASABKAUSEwoLaXRlbV9oYW5kbGUYAiABKAUiRgoYQWR2aXNlU3Vw",
"ZXJ2aXNvcnlDb21tYW5kEhUKDXNlcnZlcl9oYW5kbGUYASABKAUSEwoLaXRl",
"bV9oYW5kbGUYAiABKAUiXgoWQWRkQnVmZmVyZWRJdGVtQ29tbWFuZBIVCg1z",
"ZXJ2ZXJfaGFuZGxlGAEgASgFEhcKD2l0ZW1fZGVmaW5pdGlvbhgCIAEoCRIU",
"CgxpdGVtX2NvbnRleHQYAyABKAkiXwogU2V0QnVmZmVyZWRVcGRhdGVJbnRl",
"cnZhbENvbW1hbmQSFQoNc2VydmVyX2hhbmRsZRgBIAEoBRIkChx1cGRhdGVf",
"aW50ZXJ2YWxfbWlsbGlzZWNvbmRzGAIgASgFIjwKDlN1c3BlbmRDb21tYW5k",
"EhUKDXNlcnZlcl9oYW5kbGUYASABKAUSEwoLaXRlbV9oYW5kbGUYAiABKAUi",
"PQoPQWN0aXZhdGVDb21tYW5kEhUKDXNlcnZlcl9oYW5kbGUYASABKAUSEwoL",
"aXRlbV9oYW5kbGUYAiABKAUieAoMV3JpdGVDb21tYW5kEhUKDXNlcnZlcl9o",
"YW5kbGUYASABKAUSEwoLaXRlbV9oYW5kbGUYAiABKAUSKwoFdmFsdWUYAyAB",
"KAsyHC5teGFjY2Vzc19nYXRld2F5LnYxLk14VmFsdWUSNQoPdGltZXN0YW1w",
"X3ZhbHVlGAQgASgLMhwubXhhY2Nlc3NfZ2F0ZXdheS52MS5NeFZhbHVlEg8K",
"B3VzZXJfaWQYBSABKAUioQEKE1dyaXRlU2VjdXJlZENvbW1hbmQSFQoNc2Vy",
"dmVyX2hhbmRsZRgBIAEoBRITCgtpdGVtX2hhbmRsZRgCIAEoBRIXCg9jdXJy",
"ZW50X3VzZXJfaWQYAyABKAUSGAoQdmVyaWZpZXJfdXNlcl9pZBgEIAEoBRIr",
"CgV2YWx1ZRgFIAEoCzIcLm14YWNjZXNzX2dhdGV3YXkudjEuTXhWYWx1ZSLZ",
"AQoUV3JpdGVTZWN1cmVkMkNvbW1hbmQSFQoNc2VydmVyX2hhbmRsZRgBIAEo",
"BRITCgtpdGVtX2hhbmRsZRgCIAEoBRIXCg9jdXJyZW50X3VzZXJfaWQYAyAB",
"KAUSGAoQdmVyaWZpZXJfdXNlcl9pZBgEIAEoBRIrCgV2YWx1ZRgFIAEoCzIc",
"Lm14YWNjZXNzX2dhdGV3YXkudjEuTXhWYWx1ZRI1Cg90aW1lc3RhbXBfdmFs",
"dWUYBiABKAsyHC5teGFjY2Vzc19nYXRld2F5LnYxLk14VmFsdWUiYwoXQXV0",
"aGVudGljYXRlVXNlckNvbW1hbmQSFQoNc2VydmVyX2hhbmRsZRgBIAEoBRIT",
"Cgt2ZXJpZnlfdXNlchgCIAEoCRIcChR2ZXJpZnlfdXNlcl9wYXNzd29yZBgD",
"IAEoCSJHChhBcmNoZXN0ckFVc2VyVG9JZENvbW1hbmQSFQoNc2VydmVyX2hh",
"bmRsZRgBIAEoBRIUCgx1c2VyX2lkX2d1aWQYAiABKAkiHgoLUGluZ0NvbW1h",
"bmQSDwoHbWVzc2FnZRgBIAEoCSIYChZHZXRTZXNzaW9uU3RhdGVDb21tYW5k",
"IhYKFEdldFdvcmtlckluZm9Db21tYW5kIigKEkRyYWluRXZlbnRzQ29tbWFu",
"ZBISCgptYXhfZXZlbnRzGAEgASgNIkgKFVNodXRkb3duV29ya2VyQ29tbWFu",
"ZBIvCgxncmFjZV9wZXJpb2QYASABKAsyGS5nb29nbGUucHJvdG9idWYuRHVy",
"YXRpb24ikAgKDk14Q29tbWFuZFJlcGx5EhIKCnNlc3Npb25faWQYASABKAkS",
"FgoOY29ycmVsYXRpb25faWQYAiABKAkSMAoEa2luZBgDIAEoDjIiLm14YWNj",
"ZXNzX2dhdGV3YXkudjEuTXhDb21tYW5kS2luZBI8Cg9wcm90b2NvbF9zdGF0",
"dXMYBCABKAsyIy5teGFjY2Vzc19nYXRld2F5LnYxLlByb3RvY29sU3RhdHVz",
"EhQKB2hyZXN1bHQYBSABKAVIAYgBARIyCgxyZXR1cm5fdmFsdWUYBiABKAsy",
"HC5teGFjY2Vzc19nYXRld2F5LnYxLk14VmFsdWUSNAoIc3RhdHVzZXMYByAD",
"KAsyIi5teGFjY2Vzc19nYXRld2F5LnYxLk14U3RhdHVzUHJveHkSGgoSZGlh",
"Z25vc3RpY19tZXNzYWdlGAggASgJEjYKCHJlZ2lzdGVyGBQgASgLMiIubXhh",
"Y2Nlc3NfZ2F0ZXdheS52MS5SZWdpc3RlclJlcGx5SAASNQoIYWRkX2l0ZW0Y",
"FSABKAsyIS5teGFjY2Vzc19nYXRld2F5LnYxLkFkZEl0ZW1SZXBseUgAEjcK",
"CWFkZF9pdGVtMhgWIAEoCzIiLm14YWNjZXNzX2dhdGV3YXkudjEuQWRkSXRl",
"bTJSZXBseUgAEkYKEWFkZF9idWZmZXJlZF9pdGVtGBcgASgLMikubXhhY2Nl",
"c3NfZ2F0ZXdheS52MS5BZGRCdWZmZXJlZEl0ZW1SZXBseUgAEjQKB3N1c3Bl",
"bmQYGCABKAsyIS5teGFjY2Vzc19nYXRld2F5LnYxLlN1c3BlbmRSZXBseUgA",
"EjYKCGFjdGl2YXRlGBkgASgLMiIubXhhY2Nlc3NfZ2F0ZXdheS52MS5BY3Rp",
"dmF0ZVJlcGx5SAASRwoRYXV0aGVudGljYXRlX3VzZXIYGiABKAsyKi5teGFj",
"Y2Vzc19nYXRld2F5LnYxLkF1dGhlbnRpY2F0ZVVzZXJSZXBseUgAEksKFGFy",
"Y2hlc3RyYV91c2VyX3RvX2lkGBsgASgLMisubXhhY2Nlc3NfZ2F0ZXdheS52",
"MS5BcmNoZXN0ckFVc2VyVG9JZFJlcGx5SAASPwoNc2Vzc2lvbl9zdGF0ZRhk",
"IAEoCzImLm14YWNjZXNzX2dhdGV3YXkudjEuU2Vzc2lvblN0YXRlUmVwbHlI",
"ABI7Cgt3b3JrZXJfaW5mbxhlIAEoCzIkLm14YWNjZXNzX2dhdGV3YXkudjEu",
"V29ya2VySW5mb1JlcGx5SAASPQoMZHJhaW5fZXZlbnRzGGYgASgLMiUubXhh",
"Y2Nlc3NfZ2F0ZXdheS52MS5EcmFpbkV2ZW50c1JlcGx5SABCCQoHcGF5bG9h",
"ZEIKCghfaHJlc3VsdCImCg1SZWdpc3RlclJlcGx5EhUKDXNlcnZlcl9oYW5k",
"bGUYASABKAUiIwoMQWRkSXRlbVJlcGx5EhMKC2l0ZW1faGFuZGxlGAEgASgF",
"IiQKDUFkZEl0ZW0yUmVwbHkSEwoLaXRlbV9oYW5kbGUYASABKAUiKwoUQWRk",
"QnVmZmVyZWRJdGVtUmVwbHkSEwoLaXRlbV9oYW5kbGUYASABKAUiQgoMU3Vz",
"cGVuZFJlcGx5EjIKBnN0YXR1cxgBIAEoCzIiLm14YWNjZXNzX2dhdGV3YXku",
"djEuTXhTdGF0dXNQcm94eSJDCg1BY3RpdmF0ZVJlcGx5EjIKBnN0YXR1cxgB",
"IAEoCzIiLm14YWNjZXNzX2dhdGV3YXkudjEuTXhTdGF0dXNQcm94eSIoChVB",
"dXRoZW50aWNhdGVVc2VyUmVwbHkSDwoHdXNlcl9pZBgBIAEoBSIpChZBcmNo",
"ZXN0ckFVc2VyVG9JZFJlcGx5Eg8KB3VzZXJfaWQYASABKAUiRQoRU2Vzc2lv",
"blN0YXRlUmVwbHkSMAoFc3RhdGUYASABKA4yIS5teGFjY2Vzc19nYXRld2F5",
"LnYxLlNlc3Npb25TdGF0ZSJ1Cg9Xb3JrZXJJbmZvUmVwbHkSGQoRd29ya2Vy",
"X3Byb2Nlc3NfaWQYASABKAUSFgoOd29ya2VyX3ZlcnNpb24YAiABKAkSFwoP",
"bXhhY2Nlc3NfcHJvZ2lkGAMgASgJEhYKDm14YWNjZXNzX2Nsc2lkGAQgASgJ",
"IkAKEERyYWluRXZlbnRzUmVwbHkSLAoGZXZlbnRzGAEgAygLMhwubXhhY2Nl",
"c3NfZ2F0ZXdheS52MS5NeEV2ZW50IpsGCgdNeEV2ZW50EjIKBmZhbWlseRgB",
"IAEoDjIiLm14YWNjZXNzX2dhdGV3YXkudjEuTXhFdmVudEZhbWlseRISCgpz",
"ZXNzaW9uX2lkGAIgASgJEhUKDXNlcnZlcl9oYW5kbGUYAyABKAUSEwoLaXRl",
"bV9oYW5kbGUYBCABKAUSKwoFdmFsdWUYBSABKAsyHC5teGFjY2Vzc19nYXRl",
"d2F5LnYxLk14VmFsdWUSDwoHcXVhbGl0eRgGIAEoBRI0ChBzb3VyY2VfdGlt",
"ZXN0YW1wGAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBI0Cghz",
"dGF0dXNlcxgIIAMoCzIiLm14YWNjZXNzX2dhdGV3YXkudjEuTXhTdGF0dXNQ",
"cm94eRIXCg93b3JrZXJfc2VxdWVuY2UYCSABKAQSNAoQd29ya2VyX3RpbWVz",
"dGFtcBgKIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASPQoZZ2F0",
"ZXdheV9yZWNlaXZlX3RpbWVzdGFtcBgLIAEoCzIaLmdvb2dsZS5wcm90b2J1",
"Zi5UaW1lc3RhbXASFAoHaHJlc3VsdBgMIAEoBUgBiAEBEhIKCnJhd19zdGF0",
"dXMYDSABKAkSQAoOb25fZGF0YV9jaGFuZ2UYFCABKAsyJi5teGFjY2Vzc19n",
"YXRld2F5LnYxLk9uRGF0YUNoYW5nZUV2ZW50SAASRgoRb25fd3JpdGVfY29t",
"cGxldGUYFSABKAsyKS5teGFjY2Vzc19nYXRld2F5LnYxLk9uV3JpdGVDb21w",
"bGV0ZUV2ZW50SAASSQoSb3BlcmF0aW9uX2NvbXBsZXRlGBYgASgLMisubXhh",
"Y2Nlc3NfZ2F0ZXdheS52MS5PcGVyYXRpb25Db21wbGV0ZUV2ZW50SAASUQoX",
"b25fYnVmZmVyZWRfZGF0YV9jaGFuZ2UYFyABKAsyLi5teGFjY2Vzc19nYXRl",
"d2F5LnYxLk9uQnVmZmVyZWREYXRhQ2hhbmdlRXZlbnRIAEIGCgRib2R5QgoK",
"CF9ocmVzdWx0IhMKEU9uRGF0YUNoYW5nZUV2ZW50IhYKFE9uV3JpdGVDb21w",
"bGV0ZUV2ZW50IhgKFk9wZXJhdGlvbkNvbXBsZXRlRXZlbnQi1AEKGU9uQnVm",
"ZmVyZWREYXRhQ2hhbmdlRXZlbnQSMgoJZGF0YV90eXBlGAEgASgOMh8ubXhh",
"Y2Nlc3NfZ2F0ZXdheS52MS5NeERhdGFUeXBlEjQKDnF1YWxpdHlfdmFsdWVz",
"GAIgASgLMhwubXhhY2Nlc3NfZ2F0ZXdheS52MS5NeEFycmF5EjYKEHRpbWVz",
"dGFtcF92YWx1ZXMYAyABKAsyHC5teGFjY2Vzc19nYXRld2F5LnYxLk14QXJy",
"YXkSFQoNcmF3X2RhdGFfdHlwZRgEIAEoBSLrAQoNTXhTdGF0dXNQcm94eRIP",
"CgdzdWNjZXNzGAEgASgFEjcKCGNhdGVnb3J5GAIgASgOMiUubXhhY2Nlc3Nf",
"Z2F0ZXdheS52MS5NeFN0YXR1c0NhdGVnb3J5EjgKC2RldGVjdGVkX2J5GAMg",
"ASgOMiMubXhhY2Nlc3NfZ2F0ZXdheS52MS5NeFN0YXR1c1NvdXJjZRIOCgZk",
"ZXRhaWwYBCABKAUSFAoMcmF3X2NhdGVnb3J5GAUgASgFEhcKD3Jhd19kZXRl",
"Y3RlZF9ieRgGIAEoBRIXCg9kaWFnbm9zdGljX3RleHQYByABKAkipwMKB014",
"VmFsdWUSMgoJZGF0YV90eXBlGAEgASgOMh8ubXhhY2Nlc3NfZ2F0ZXdheS52",
"MS5NeERhdGFUeXBlEhQKDHZhcmlhbnRfdHlwZRgCIAEoCRIPCgdpc19udWxs",
"GAMgASgIEhYKDnJhd19kaWFnbm9zdGljGAQgASgJEhUKDXJhd19kYXRhX3R5",
"cGUYBSABKAUSFAoKYm9vbF92YWx1ZRgKIAEoCEgAEhUKC2ludDMyX3ZhbHVl",
"GAsgASgFSAASFQoLaW50NjRfdmFsdWUYDCABKANIABIVCgtmbG9hdF92YWx1",
"ZRgNIAEoAkgAEhYKDGRvdWJsZV92YWx1ZRgOIAEoAUgAEhYKDHN0cmluZ192",
"YWx1ZRgPIAEoCUgAEjUKD3RpbWVzdGFtcF92YWx1ZRgQIAEoCzIaLmdvb2ds",
"ZS5wcm90b2J1Zi5UaW1lc3RhbXBIABIzCgthcnJheV92YWx1ZRgRIAEoCzIc",
"Lm14YWNjZXNzX2dhdGV3YXkudjEuTXhBcnJheUgAEhMKCXJhd192YWx1ZRgS",
"IAEoDEgAQgYKBGtpbmQi/gQKB014QXJyYXkSOgoRZWxlbWVudF9kYXRhX3R5",
"cGUYASABKA4yHy5teGFjY2Vzc19nYXRld2F5LnYxLk14RGF0YVR5cGUSFAoM",
"dmFyaWFudF90eXBlGAIgASgJEhIKCmRpbWVuc2lvbnMYAyADKA0SFgoOcmF3",
"X2RpYWdub3N0aWMYBCABKAkSHQoVcmF3X2VsZW1lbnRfZGF0YV90eXBlGAUg",
"ASgFEjUKC2Jvb2xfdmFsdWVzGAogASgLMh4ubXhhY2Nlc3NfZ2F0ZXdheS52",
"MS5Cb29sQXJyYXlIABI3CgxpbnQzMl92YWx1ZXMYCyABKAsyHy5teGFjY2Vz",
"c19nYXRld2F5LnYxLkludDMyQXJyYXlIABI3CgxpbnQ2NF92YWx1ZXMYDCAB",
"KAsyHy5teGFjY2Vzc19nYXRld2F5LnYxLkludDY0QXJyYXlIABI3CgxmbG9h",
"dF92YWx1ZXMYDSABKAsyHy5teGFjY2Vzc19nYXRld2F5LnYxLkZsb2F0QXJy",
"YXlIABI5Cg1kb3VibGVfdmFsdWVzGA4gASgLMiAubXhhY2Nlc3NfZ2F0ZXdh",
"eS52MS5Eb3VibGVBcnJheUgAEjkKDXN0cmluZ192YWx1ZXMYDyABKAsyIC5t",
"eGFjY2Vzc19nYXRld2F5LnYxLlN0cmluZ0FycmF5SAASPwoQdGltZXN0YW1w",
"X3ZhbHVlcxgQIAEoCzIjLm14YWNjZXNzX2dhdGV3YXkudjEuVGltZXN0YW1w",
"QXJyYXlIABIzCgpyYXdfdmFsdWVzGBEgASgLMh0ubXhhY2Nlc3NfZ2F0ZXdh",
"eS52MS5SYXdBcnJheUgAQggKBnZhbHVlcyIbCglCb29sQXJyYXkSDgoGdmFs",
"dWVzGAEgAygIIhwKCkludDMyQXJyYXkSDgoGdmFsdWVzGAEgAygFIhwKCklu",
"dDY0QXJyYXkSDgoGdmFsdWVzGAEgAygDIhwKCkZsb2F0QXJyYXkSDgoGdmFs",
"dWVzGAEgAygCIh0KC0RvdWJsZUFycmF5Eg4KBnZhbHVlcxgBIAMoASIdCgtT",
"dHJpbmdBcnJheRIOCgZ2YWx1ZXMYASADKAkiPAoOVGltZXN0YW1wQXJyYXkS",
"KgoGdmFsdWVzGAEgAygLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCIa",
"CghSYXdBcnJheRIOCgZ2YWx1ZXMYASADKAwiWAoOUHJvdG9jb2xTdGF0dXMS",
"NQoEY29kZRgBIAEoDjInLm14YWNjZXNzX2dhdGV3YXkudjEuUHJvdG9jb2xT",
"dGF0dXNDb2RlEg8KB21lc3NhZ2UYAiABKAkqvwYKDU14Q29tbWFuZEtpbmQS",
"HwobTVhfQ09NTUFORF9LSU5EX1VOU1BFQ0lGSUVEEAASHAoYTVhfQ09NTUFO",
"RF9LSU5EX1JFR0lTVEVSEAESHgoaTVhfQ09NTUFORF9LSU5EX1VOUkVHSVNU",
"RVIQAhIcChhNWF9DT01NQU5EX0tJTkRfQUREX0lURU0QAxIdChlNWF9DT01N",
"QU5EX0tJTkRfQUREX0lURU0yEAQSHwobTVhfQ09NTUFORF9LSU5EX1JFTU9W",
"RV9JVEVNEAUSGgoWTVhfQ09NTUFORF9LSU5EX0FEVklTRRAGEh0KGU1YX0NP",
"TU1BTkRfS0lORF9VTl9BRFZJU0UQBxImCiJNWF9DT01NQU5EX0tJTkRfQURW",
"SVNFX1NVUEVSVklTT1JZEAgSJQohTVhfQ09NTUFORF9LSU5EX0FERF9CVUZG",
"RVJFRF9JVEVNEAkSMAosTVhfQ09NTUFORF9LSU5EX1NFVF9CVUZGRVJFRF9V",
"UERBVEVfSU5URVJWQUwQChIbChdNWF9DT01NQU5EX0tJTkRfU1VTUEVORBAL",
"EhwKGE1YX0NPTU1BTkRfS0lORF9BQ1RJVkFURRAMEhkKFU1YX0NPTU1BTkRf",
"S0lORF9XUklURRANEhoKFk1YX0NPTU1BTkRfS0lORF9XUklURTIQDhIhCh1N",
"WF9DT01NQU5EX0tJTkRfV1JJVEVfU0VDVVJFRBAPEiIKHk1YX0NPTU1BTkRf",
"S0lORF9XUklURV9TRUNVUkVEMhAQEiUKIU1YX0NPTU1BTkRfS0lORF9BVVRI",
"RU5USUNBVEVfVVNFUhAREigKJE1YX0NPTU1BTkRfS0lORF9BUkNIRVNUUkFf",
"VVNFUl9UT19JRBASEhgKFE1YX0NPTU1BTkRfS0lORF9QSU5HEGQSJQohTVhf",
"Q09NTUFORF9LSU5EX0dFVF9TRVNTSU9OX1NUQVRFEGUSIwofTVhfQ09NTUFO",
"RF9LSU5EX0dFVF9XT1JLRVJfSU5GTxBmEiAKHE1YX0NPTU1BTkRfS0lORF9E",
"UkFJTl9FVkVOVFMQZxIjCh9NWF9DT01NQU5EX0tJTkRfU0hVVERPV05fV09S",
"S0VSEGgq0AEKDU14RXZlbnRGYW1pbHkSHwobTVhfRVZFTlRfRkFNSUxZX1VO",
"U1BFQ0lGSUVEEAASIgoeTVhfRVZFTlRfRkFNSUxZX09OX0RBVEFfQ0hBTkdF",
"EAESJQohTVhfRVZFTlRfRkFNSUxZX09OX1dSSVRFX0NPTVBMRVRFEAISJgoi",
"TVhfRVZFTlRfRkFNSUxZX09QRVJBVElPTl9DT01QTEVURRADEisKJ01YX0VW",
"RU5UX0ZBTUlMWV9PTl9CVUZGRVJFRF9EQVRBX0NIQU5HRRAEKqUDChBNeFN0",
"YXR1c0NhdGVnb3J5EiIKHk1YX1NUQVRVU19DQVRFR09SWV9VTlNQRUNJRklF",
"RBAAEh4KGk1YX1NUQVRVU19DQVRFR09SWV9VTktOT1dOEAESGQoVTVhfU1RB",
"VFVTX0NBVEVHT1JZX09LEAISHgoaTVhfU1RBVFVTX0NBVEVHT1JZX1BFTkRJ",
"TkcQAxIeChpNWF9TVEFUVVNfQ0FURUdPUllfV0FSTklORxAEEioKJk1YX1NU",
"QVRVU19DQVRFR09SWV9DT01NVU5JQ0FUSU9OX0VSUk9SEAUSKgomTVhfU1RB",
"VFVTX0NBVEVHT1JZX0NPTkZJR1VSQVRJT05fRVJST1IQBhIoCiRNWF9TVEFU",
"VVNfQ0FURUdPUllfT1BFUkFUSU9OQUxfRVJST1IQBxIlCiFNWF9TVEFUVVNf",
"Q0FURUdPUllfU0VDVVJJVFlfRVJST1IQCBIlCiFNWF9TVEFUVVNfQ0FURUdP",
"UllfU09GVFdBUkVfRVJST1IQCRIiCh5NWF9TVEFUVVNfQ0FURUdPUllfT1RI",
"RVJfRVJST1IQCirKAgoOTXhTdGF0dXNTb3VyY2USIAocTVhfU1RBVFVTX1NP",
"VVJDRV9VTlNQRUNJRklFRBAAEhwKGE1YX1NUQVRVU19TT1VSQ0VfVU5LTk9X",
"ThABEiMKH01YX1NUQVRVU19TT1VSQ0VfUkVRVUVTVElOR19MTVgQAhIjCh9N",
"WF9TVEFUVVNfU09VUkNFX1JFU1BPTkRJTkdfTE1YEAMSIwofTVhfU1RBVFVT",
"X1NPVVJDRV9SRVFVRVNUSU5HX05NWBAEEiMKH01YX1NUQVRVU19TT1VSQ0Vf",
"UkVTUE9ORElOR19OTVgQBRIxCi1NWF9TVEFUVVNfU09VUkNFX1JFUVVFU1RJ",
"TkdfQVVUT01BVElPTl9PQkpFQ1QQBhIxCi1NWF9TVEFUVVNfU09VUkNFX1JF",
"U1BPTkRJTkdfQVVUT01BVElPTl9PQkpFQ1QQByrdBAoKTXhEYXRhVHlwZRIc",
"ChhNWF9EQVRBX1RZUEVfVU5TUEVDSUZJRUQQABIYChRNWF9EQVRBX1RZUEVf",
"VU5LTk9XThABEhgKFE1YX0RBVEFfVFlQRV9OT19EQVRBEAISGAoUTVhfREFU",
"QV9UWVBFX0JPT0xFQU4QAxIYChRNWF9EQVRBX1RZUEVfSU5URUdFUhAEEhYK",
"Ek1YX0RBVEFfVFlQRV9GTE9BVBAFEhcKE01YX0RBVEFfVFlQRV9ET1VCTEUQ",
"BhIXChNNWF9EQVRBX1RZUEVfU1RSSU5HEAcSFQoRTVhfREFUQV9UWVBFX1RJ",
"TUUQCBIdChlNWF9EQVRBX1RZUEVfRUxBUFNFRF9USU1FEAkSHwobTVhfREFU",
"QV9UWVBFX1JFRkVSRU5DRV9UWVBFEAoSHAoYTVhfREFUQV9UWVBFX1NUQVRV",
"U19UWVBFEAsSFQoRTVhfREFUQV9UWVBFX0VOVU0QDBItCilNWF9EQVRBX1RZ",
"UEVfU0VDVVJJVFlfQ0xBU1NJRklDQVRJT05fRU5VTRANEiIKHk1YX0RBVEFf",
"VFlQRV9EQVRBX1FVQUxJVFlfVFlQRRAOEh8KG01YX0RBVEFfVFlQRV9RVUFM",
"SUZJRURfRU5VTRAPEiEKHU1YX0RBVEFfVFlQRV9RVUFMSUZJRURfU1RSVUNU",
"EBASKQolTVhfREFUQV9UWVBFX0lOVEVSTkFUSU9OQUxJWkVEX1NUUklORxAR",
"EhsKF01YX0RBVEFfVFlQRV9CSUdfU1RSSU5HEBISFAoQTVhfREFUQV9UWVBF",
"X0VORBATKqMDChJQcm90b2NvbFN0YXR1c0NvZGUSJAogUFJPVE9DT0xfU1RB",
"VFVTX0NPREVfVU5TUEVDSUZJRUQQABIbChdQUk9UT0NPTF9TVEFUVVNfQ09E",
"RV9PSxABEigKJFBST1RPQ09MX1NUQVRVU19DT0RFX0lOVkFMSURfUkVRVUVT",
"VBACEioKJlBST1RPQ09MX1NUQVRVU19DT0RFX1NFU1NJT05fTk9UX0ZPVU5E",
"EAMSKgomUFJPVE9DT0xfU1RBVFVTX0NPREVfU0VTU0lPTl9OT1RfUkVBRFkQ",
"BBIrCidQUk9UT0NPTF9TVEFUVVNfQ09ERV9XT1JLRVJfVU5BVkFJTEFCTEUQ",
"BRIgChxQUk9UT0NPTF9TVEFUVVNfQ09ERV9USU1FT1VUEAYSIQodUFJPVE9D",
"T0xfU1RBVFVTX0NPREVfQ0FOQ0VMRUQQBxIrCidQUk9UT0NPTF9TVEFUVVNf",
"Q09ERV9QUk9UT0NPTF9WSU9MQVRJT04QCBIpCiVQUk9UT0NPTF9TVEFUVVNf",
"Q09ERV9NWEFDQ0VTU19GQUlMVVJFEAkqvwIKDFNlc3Npb25TdGF0ZRIdChlT",
"RVNTSU9OX1NUQVRFX1VOU1BFQ0lGSUVEEAASGgoWU0VTU0lPTl9TVEFURV9D",
"UkVBVElORxABEiEKHVNFU1NJT05fU1RBVEVfU1RBUlRJTkdfV09SS0VSEAIS",
"IgoeU0VTU0lPTl9TVEFURV9XQUlUSU5HX0ZPUl9QSVBFEAMSHQoZU0VTU0lP",
"Tl9TVEFURV9IQU5EU0hBS0lORxAEEiUKIVNFU1NJT05fU1RBVEVfSU5JVElB",
"TElaSU5HX1dPUktFUhAFEhcKE1NFU1NJT05fU1RBVEVfUkVBRFkQBhIZChVT",
"RVNTSU9OX1NUQVRFX0NMT1NJTkcQBxIYChRTRVNTSU9OX1NUQVRFX0NMT1NF",
"RBAIEhkKFVNFU1NJT05fU1RBVEVfRkFVTFRFRBAJMoIDCg9NeEFjY2Vzc0dh",
"dGV3YXkSXQoLT3BlblNlc3Npb24SJy5teGFjY2Vzc19nYXRld2F5LnYxLk9w",
"ZW5TZXNzaW9uUmVxdWVzdBolLm14YWNjZXNzX2dhdGV3YXkudjEuT3BlblNl",
"c3Npb25SZXBseRJgCgxDbG9zZVNlc3Npb24SKC5teGFjY2Vzc19nYXRld2F5",
"LnYxLkNsb3NlU2Vzc2lvblJlcXVlc3QaJi5teGFjY2Vzc19nYXRld2F5LnYx",
"LkNsb3NlU2Vzc2lvblJlcGx5ElQKBkludm9rZRIlLm14YWNjZXNzX2dhdGV3",
"YXkudjEuTXhDb21tYW5kUmVxdWVzdBojLm14YWNjZXNzX2dhdGV3YXkudjEu",
"TXhDb21tYW5kUmVwbHkSWAoMU3RyZWFtRXZlbnRzEigubXhhY2Nlc3NfZ2F0",
"ZXdheS52MS5TdHJlYW1FdmVudHNSZXF1ZXN0GhwubXhhY2Nlc3NfZ2F0ZXdh",
"eS52MS5NeEV2ZW50MAFCHKoCGU14R2F0ZXdheS5Db250cmFjdHMuUHJvdG9i",
"BnByb3RvMw=="));
"KAsyHC5teGFjY2Vzc19nYXRld2F5LnYxLk14VmFsdWUSDwoHdXNlcl9pZBgE",
"IAEoBSKwAQoNV3JpdGUyQ29tbWFuZBIVCg1zZXJ2ZXJfaGFuZGxlGAEgASgF",
"EhMKC2l0ZW1faGFuZGxlGAIgASgFEisKBXZhbHVlGAMgASgLMhwubXhhY2Nl",
"c3NfZ2F0ZXdheS52MS5NeFZhbHVlEjUKD3RpbWVzdGFtcF92YWx1ZRgEIAEo",
"CzIcLm14YWNjZXNzX2dhdGV3YXkudjEuTXhWYWx1ZRIPCgd1c2VyX2lkGAUg",
"ASgFIqEBChNXcml0ZVNlY3VyZWRDb21tYW5kEhUKDXNlcnZlcl9oYW5kbGUY",
"ASABKAUSEwoLaXRlbV9oYW5kbGUYAiABKAUSFwoPY3VycmVudF91c2VyX2lk",
"GAMgASgFEhgKEHZlcmlmaWVyX3VzZXJfaWQYBCABKAUSKwoFdmFsdWUYBSAB",
"KAsyHC5teGFjY2Vzc19nYXRld2F5LnYxLk14VmFsdWUi2QEKFFdyaXRlU2Vj",
"dXJlZDJDb21tYW5kEhUKDXNlcnZlcl9oYW5kbGUYASABKAUSEwoLaXRlbV9o",
"YW5kbGUYAiABKAUSFwoPY3VycmVudF91c2VyX2lkGAMgASgFEhgKEHZlcmlm",
"aWVyX3VzZXJfaWQYBCABKAUSKwoFdmFsdWUYBSABKAsyHC5teGFjY2Vzc19n",
"YXRld2F5LnYxLk14VmFsdWUSNQoPdGltZXN0YW1wX3ZhbHVlGAYgASgLMhwu",
"bXhhY2Nlc3NfZ2F0ZXdheS52MS5NeFZhbHVlImMKF0F1dGhlbnRpY2F0ZVVz",
"ZXJDb21tYW5kEhUKDXNlcnZlcl9oYW5kbGUYASABKAUSEwoLdmVyaWZ5X3Vz",
"ZXIYAiABKAkSHAoUdmVyaWZ5X3VzZXJfcGFzc3dvcmQYAyABKAkiRwoYQXJj",
"aGVzdHJBVXNlclRvSWRDb21tYW5kEhUKDXNlcnZlcl9oYW5kbGUYASABKAUS",
"FAoMdXNlcl9pZF9ndWlkGAIgASgJIh4KC1BpbmdDb21tYW5kEg8KB21lc3Nh",
"Z2UYASABKAkiGAoWR2V0U2Vzc2lvblN0YXRlQ29tbWFuZCIWChRHZXRXb3Jr",
"ZXJJbmZvQ29tbWFuZCIoChJEcmFpbkV2ZW50c0NvbW1hbmQSEgoKbWF4X2V2",
"ZW50cxgBIAEoDSJIChVTaHV0ZG93bldvcmtlckNvbW1hbmQSLwoMZ3JhY2Vf",
"cGVyaW9kGAEgASgLMhkuZ29vZ2xlLnByb3RvYnVmLkR1cmF0aW9uIpAICg5N",
"eENvbW1hbmRSZXBseRISCgpzZXNzaW9uX2lkGAEgASgJEhYKDmNvcnJlbGF0",
"aW9uX2lkGAIgASgJEjAKBGtpbmQYAyABKA4yIi5teGFjY2Vzc19nYXRld2F5",
"LnYxLk14Q29tbWFuZEtpbmQSPAoPcHJvdG9jb2xfc3RhdHVzGAQgASgLMiMu",
"bXhhY2Nlc3NfZ2F0ZXdheS52MS5Qcm90b2NvbFN0YXR1cxIUCgdocmVzdWx0",
"GAUgASgFSAGIAQESMgoMcmV0dXJuX3ZhbHVlGAYgASgLMhwubXhhY2Nlc3Nf",
"Z2F0ZXdheS52MS5NeFZhbHVlEjQKCHN0YXR1c2VzGAcgAygLMiIubXhhY2Nl",
"c3NfZ2F0ZXdheS52MS5NeFN0YXR1c1Byb3h5EhoKEmRpYWdub3N0aWNfbWVz",
"c2FnZRgIIAEoCRI2CghyZWdpc3RlchgUIAEoCzIiLm14YWNjZXNzX2dhdGV3",
"YXkudjEuUmVnaXN0ZXJSZXBseUgAEjUKCGFkZF9pdGVtGBUgASgLMiEubXhh",
"Y2Nlc3NfZ2F0ZXdheS52MS5BZGRJdGVtUmVwbHlIABI3CglhZGRfaXRlbTIY",
"FiABKAsyIi5teGFjY2Vzc19nYXRld2F5LnYxLkFkZEl0ZW0yUmVwbHlIABJG",
"ChFhZGRfYnVmZmVyZWRfaXRlbRgXIAEoCzIpLm14YWNjZXNzX2dhdGV3YXku",
"djEuQWRkQnVmZmVyZWRJdGVtUmVwbHlIABI0CgdzdXNwZW5kGBggASgLMiEu",
"bXhhY2Nlc3NfZ2F0ZXdheS52MS5TdXNwZW5kUmVwbHlIABI2CghhY3RpdmF0",
"ZRgZIAEoCzIiLm14YWNjZXNzX2dhdGV3YXkudjEuQWN0aXZhdGVSZXBseUgA",
"EkcKEWF1dGhlbnRpY2F0ZV91c2VyGBogASgLMioubXhhY2Nlc3NfZ2F0ZXdh",
"eS52MS5BdXRoZW50aWNhdGVVc2VyUmVwbHlIABJLChRhcmNoZXN0cmFfdXNl",
"cl90b19pZBgbIAEoCzIrLm14YWNjZXNzX2dhdGV3YXkudjEuQXJjaGVzdHJB",
"VXNlclRvSWRSZXBseUgAEj8KDXNlc3Npb25fc3RhdGUYZCABKAsyJi5teGFj",
"Y2Vzc19nYXRld2F5LnYxLlNlc3Npb25TdGF0ZVJlcGx5SAASOwoLd29ya2Vy",
"X2luZm8YZSABKAsyJC5teGFjY2Vzc19nYXRld2F5LnYxLldvcmtlckluZm9S",
"ZXBseUgAEj0KDGRyYWluX2V2ZW50cxhmIAEoCzIlLm14YWNjZXNzX2dhdGV3",
"YXkudjEuRHJhaW5FdmVudHNSZXBseUgAQgkKB3BheWxvYWRCCgoIX2hyZXN1",
"bHQiJgoNUmVnaXN0ZXJSZXBseRIVCg1zZXJ2ZXJfaGFuZGxlGAEgASgFIiMK",
"DEFkZEl0ZW1SZXBseRITCgtpdGVtX2hhbmRsZRgBIAEoBSIkCg1BZGRJdGVt",
"MlJlcGx5EhMKC2l0ZW1faGFuZGxlGAEgASgFIisKFEFkZEJ1ZmZlcmVkSXRl",
"bVJlcGx5EhMKC2l0ZW1faGFuZGxlGAEgASgFIkIKDFN1c3BlbmRSZXBseRIy",
"CgZzdGF0dXMYASABKAsyIi5teGFjY2Vzc19nYXRld2F5LnYxLk14U3RhdHVz",
"UHJveHkiQwoNQWN0aXZhdGVSZXBseRIyCgZzdGF0dXMYASABKAsyIi5teGFj",
"Y2Vzc19nYXRld2F5LnYxLk14U3RhdHVzUHJveHkiKAoVQXV0aGVudGljYXRl",
"VXNlclJlcGx5Eg8KB3VzZXJfaWQYASABKAUiKQoWQXJjaGVzdHJBVXNlclRv",
"SWRSZXBseRIPCgd1c2VyX2lkGAEgASgFIkUKEVNlc3Npb25TdGF0ZVJlcGx5",
"EjAKBXN0YXRlGAEgASgOMiEubXhhY2Nlc3NfZ2F0ZXdheS52MS5TZXNzaW9u",
"U3RhdGUidQoPV29ya2VySW5mb1JlcGx5EhkKEXdvcmtlcl9wcm9jZXNzX2lk",
"GAEgASgFEhYKDndvcmtlcl92ZXJzaW9uGAIgASgJEhcKD214YWNjZXNzX3By",
"b2dpZBgDIAEoCRIWCg5teGFjY2Vzc19jbHNpZBgEIAEoCSJAChBEcmFpbkV2",
"ZW50c1JlcGx5EiwKBmV2ZW50cxgBIAMoCzIcLm14YWNjZXNzX2dhdGV3YXku",
"djEuTXhFdmVudCKbBgoHTXhFdmVudBIyCgZmYW1pbHkYASABKA4yIi5teGFj",
"Y2Vzc19nYXRld2F5LnYxLk14RXZlbnRGYW1pbHkSEgoKc2Vzc2lvbl9pZBgC",
"IAEoCRIVCg1zZXJ2ZXJfaGFuZGxlGAMgASgFEhMKC2l0ZW1faGFuZGxlGAQg",
"ASgFEisKBXZhbHVlGAUgASgLMhwubXhhY2Nlc3NfZ2F0ZXdheS52MS5NeFZh",
"bHVlEg8KB3F1YWxpdHkYBiABKAUSNAoQc291cmNlX3RpbWVzdGFtcBgHIAEo",
"CzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASNAoIc3RhdHVzZXMYCCAD",
"KAsyIi5teGFjY2Vzc19nYXRld2F5LnYxLk14U3RhdHVzUHJveHkSFwoPd29y",
"a2VyX3NlcXVlbmNlGAkgASgEEjQKEHdvcmtlcl90aW1lc3RhbXAYCiABKAsy",
"Gi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEj0KGWdhdGV3YXlfcmVjZWl2",
"ZV90aW1lc3RhbXAYCyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1w",
"EhQKB2hyZXN1bHQYDCABKAVIAYgBARISCgpyYXdfc3RhdHVzGA0gASgJEkAK",
"Dm9uX2RhdGFfY2hhbmdlGBQgASgLMiYubXhhY2Nlc3NfZ2F0ZXdheS52MS5P",
"bkRhdGFDaGFuZ2VFdmVudEgAEkYKEW9uX3dyaXRlX2NvbXBsZXRlGBUgASgL",
"MikubXhhY2Nlc3NfZ2F0ZXdheS52MS5PbldyaXRlQ29tcGxldGVFdmVudEgA",
"EkkKEm9wZXJhdGlvbl9jb21wbGV0ZRgWIAEoCzIrLm14YWNjZXNzX2dhdGV3",
"YXkudjEuT3BlcmF0aW9uQ29tcGxldGVFdmVudEgAElEKF29uX2J1ZmZlcmVk",
"X2RhdGFfY2hhbmdlGBcgASgLMi4ubXhhY2Nlc3NfZ2F0ZXdheS52MS5PbkJ1",
"ZmZlcmVkRGF0YUNoYW5nZUV2ZW50SABCBgoEYm9keUIKCghfaHJlc3VsdCIT",
"ChFPbkRhdGFDaGFuZ2VFdmVudCIWChRPbldyaXRlQ29tcGxldGVFdmVudCIY",
"ChZPcGVyYXRpb25Db21wbGV0ZUV2ZW50ItQBChlPbkJ1ZmZlcmVkRGF0YUNo",
"YW5nZUV2ZW50EjIKCWRhdGFfdHlwZRgBIAEoDjIfLm14YWNjZXNzX2dhdGV3",
"YXkudjEuTXhEYXRhVHlwZRI0Cg5xdWFsaXR5X3ZhbHVlcxgCIAEoCzIcLm14",
"YWNjZXNzX2dhdGV3YXkudjEuTXhBcnJheRI2ChB0aW1lc3RhbXBfdmFsdWVz",
"GAMgASgLMhwubXhhY2Nlc3NfZ2F0ZXdheS52MS5NeEFycmF5EhUKDXJhd19k",
"YXRhX3R5cGUYBCABKAUi6wEKDU14U3RhdHVzUHJveHkSDwoHc3VjY2VzcxgB",
"IAEoBRI3CghjYXRlZ29yeRgCIAEoDjIlLm14YWNjZXNzX2dhdGV3YXkudjEu",
"TXhTdGF0dXNDYXRlZ29yeRI4CgtkZXRlY3RlZF9ieRgDIAEoDjIjLm14YWNj",
"ZXNzX2dhdGV3YXkudjEuTXhTdGF0dXNTb3VyY2USDgoGZGV0YWlsGAQgASgF",
"EhQKDHJhd19jYXRlZ29yeRgFIAEoBRIXCg9yYXdfZGV0ZWN0ZWRfYnkYBiAB",
"KAUSFwoPZGlhZ25vc3RpY190ZXh0GAcgASgJIqcDCgdNeFZhbHVlEjIKCWRh",
"dGFfdHlwZRgBIAEoDjIfLm14YWNjZXNzX2dhdGV3YXkudjEuTXhEYXRhVHlw",
"ZRIUCgx2YXJpYW50X3R5cGUYAiABKAkSDwoHaXNfbnVsbBgDIAEoCBIWCg5y",
"YXdfZGlhZ25vc3RpYxgEIAEoCRIVCg1yYXdfZGF0YV90eXBlGAUgASgFEhQK",
"CmJvb2xfdmFsdWUYCiABKAhIABIVCgtpbnQzMl92YWx1ZRgLIAEoBUgAEhUK",
"C2ludDY0X3ZhbHVlGAwgASgDSAASFQoLZmxvYXRfdmFsdWUYDSABKAJIABIW",
"Cgxkb3VibGVfdmFsdWUYDiABKAFIABIWCgxzdHJpbmdfdmFsdWUYDyABKAlI",
"ABI1Cg90aW1lc3RhbXBfdmFsdWUYECABKAsyGi5nb29nbGUucHJvdG9idWYu",
"VGltZXN0YW1wSAASMwoLYXJyYXlfdmFsdWUYESABKAsyHC5teGFjY2Vzc19n",
"YXRld2F5LnYxLk14QXJyYXlIABITCglyYXdfdmFsdWUYEiABKAxIAEIGCgRr",
"aW5kIv4ECgdNeEFycmF5EjoKEWVsZW1lbnRfZGF0YV90eXBlGAEgASgOMh8u",
"bXhhY2Nlc3NfZ2F0ZXdheS52MS5NeERhdGFUeXBlEhQKDHZhcmlhbnRfdHlw",
"ZRgCIAEoCRISCgpkaW1lbnNpb25zGAMgAygNEhYKDnJhd19kaWFnbm9zdGlj",
"GAQgASgJEh0KFXJhd19lbGVtZW50X2RhdGFfdHlwZRgFIAEoBRI1Cgtib29s",
"X3ZhbHVlcxgKIAEoCzIeLm14YWNjZXNzX2dhdGV3YXkudjEuQm9vbEFycmF5",
"SAASNwoMaW50MzJfdmFsdWVzGAsgASgLMh8ubXhhY2Nlc3NfZ2F0ZXdheS52",
"MS5JbnQzMkFycmF5SAASNwoMaW50NjRfdmFsdWVzGAwgASgLMh8ubXhhY2Nl",
"c3NfZ2F0ZXdheS52MS5JbnQ2NEFycmF5SAASNwoMZmxvYXRfdmFsdWVzGA0g",
"ASgLMh8ubXhhY2Nlc3NfZ2F0ZXdheS52MS5GbG9hdEFycmF5SAASOQoNZG91",
"YmxlX3ZhbHVlcxgOIAEoCzIgLm14YWNjZXNzX2dhdGV3YXkudjEuRG91Ymxl",
"QXJyYXlIABI5Cg1zdHJpbmdfdmFsdWVzGA8gASgLMiAubXhhY2Nlc3NfZ2F0",
"ZXdheS52MS5TdHJpbmdBcnJheUgAEj8KEHRpbWVzdGFtcF92YWx1ZXMYECAB",
"KAsyIy5teGFjY2Vzc19nYXRld2F5LnYxLlRpbWVzdGFtcEFycmF5SAASMwoK",
"cmF3X3ZhbHVlcxgRIAEoCzIdLm14YWNjZXNzX2dhdGV3YXkudjEuUmF3QXJy",
"YXlIAEIICgZ2YWx1ZXMiGwoJQm9vbEFycmF5Eg4KBnZhbHVlcxgBIAMoCCIc",
"CgpJbnQzMkFycmF5Eg4KBnZhbHVlcxgBIAMoBSIcCgpJbnQ2NEFycmF5Eg4K",
"BnZhbHVlcxgBIAMoAyIcCgpGbG9hdEFycmF5Eg4KBnZhbHVlcxgBIAMoAiId",
"CgtEb3VibGVBcnJheRIOCgZ2YWx1ZXMYASADKAEiHQoLU3RyaW5nQXJyYXkS",
"DgoGdmFsdWVzGAEgAygJIjwKDlRpbWVzdGFtcEFycmF5EioKBnZhbHVlcxgB",
"IAMoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiGgoIUmF3QXJyYXkS",
"DgoGdmFsdWVzGAEgAygMIlgKDlByb3RvY29sU3RhdHVzEjUKBGNvZGUYASAB",
"KA4yJy5teGFjY2Vzc19nYXRld2F5LnYxLlByb3RvY29sU3RhdHVzQ29kZRIP",
"CgdtZXNzYWdlGAIgASgJKr8GCg1NeENvbW1hbmRLaW5kEh8KG01YX0NPTU1B",
"TkRfS0lORF9VTlNQRUNJRklFRBAAEhwKGE1YX0NPTU1BTkRfS0lORF9SRUdJ",
"U1RFUhABEh4KGk1YX0NPTU1BTkRfS0lORF9VTlJFR0lTVEVSEAISHAoYTVhf",
"Q09NTUFORF9LSU5EX0FERF9JVEVNEAMSHQoZTVhfQ09NTUFORF9LSU5EX0FE",
"RF9JVEVNMhAEEh8KG01YX0NPTU1BTkRfS0lORF9SRU1PVkVfSVRFTRAFEhoK",
"Fk1YX0NPTU1BTkRfS0lORF9BRFZJU0UQBhIdChlNWF9DT01NQU5EX0tJTkRf",
"VU5fQURWSVNFEAcSJgoiTVhfQ09NTUFORF9LSU5EX0FEVklTRV9TVVBFUlZJ",
"U09SWRAIEiUKIU1YX0NPTU1BTkRfS0lORF9BRERfQlVGRkVSRURfSVRFTRAJ",
"EjAKLE1YX0NPTU1BTkRfS0lORF9TRVRfQlVGRkVSRURfVVBEQVRFX0lOVEVS",
"VkFMEAoSGwoXTVhfQ09NTUFORF9LSU5EX1NVU1BFTkQQCxIcChhNWF9DT01N",
"QU5EX0tJTkRfQUNUSVZBVEUQDBIZChVNWF9DT01NQU5EX0tJTkRfV1JJVEUQ",
"DRIaChZNWF9DT01NQU5EX0tJTkRfV1JJVEUyEA4SIQodTVhfQ09NTUFORF9L",
"SU5EX1dSSVRFX1NFQ1VSRUQQDxIiCh5NWF9DT01NQU5EX0tJTkRfV1JJVEVf",
"U0VDVVJFRDIQEBIlCiFNWF9DT01NQU5EX0tJTkRfQVVUSEVOVElDQVRFX1VT",
"RVIQERIoCiRNWF9DT01NQU5EX0tJTkRfQVJDSEVTVFJBX1VTRVJfVE9fSUQQ",
"EhIYChRNWF9DT01NQU5EX0tJTkRfUElORxBkEiUKIU1YX0NPTU1BTkRfS0lO",
"RF9HRVRfU0VTU0lPTl9TVEFURRBlEiMKH01YX0NPTU1BTkRfS0lORF9HRVRf",
"V09SS0VSX0lORk8QZhIgChxNWF9DT01NQU5EX0tJTkRfRFJBSU5fRVZFTlRT",
"EGcSIwofTVhfQ09NTUFORF9LSU5EX1NIVVRET1dOX1dPUktFUhBoKtABCg1N",
"eEV2ZW50RmFtaWx5Eh8KG01YX0VWRU5UX0ZBTUlMWV9VTlNQRUNJRklFRBAA",
"EiIKHk1YX0VWRU5UX0ZBTUlMWV9PTl9EQVRBX0NIQU5HRRABEiUKIU1YX0VW",
"RU5UX0ZBTUlMWV9PTl9XUklURV9DT01QTEVURRACEiYKIk1YX0VWRU5UX0ZB",
"TUlMWV9PUEVSQVRJT05fQ09NUExFVEUQAxIrCidNWF9FVkVOVF9GQU1JTFlf",
"T05fQlVGRkVSRURfREFUQV9DSEFOR0UQBCqlAwoQTXhTdGF0dXNDYXRlZ29y",
"eRIiCh5NWF9TVEFUVVNfQ0FURUdPUllfVU5TUEVDSUZJRUQQABIeChpNWF9T",
"VEFUVVNfQ0FURUdPUllfVU5LTk9XThABEhkKFU1YX1NUQVRVU19DQVRFR09S",
"WV9PSxACEh4KGk1YX1NUQVRVU19DQVRFR09SWV9QRU5ESU5HEAMSHgoaTVhf",
"U1RBVFVTX0NBVEVHT1JZX1dBUk5JTkcQBBIqCiZNWF9TVEFUVVNfQ0FURUdP",
"UllfQ09NTVVOSUNBVElPTl9FUlJPUhAFEioKJk1YX1NUQVRVU19DQVRFR09S",
"WV9DT05GSUdVUkFUSU9OX0VSUk9SEAYSKAokTVhfU1RBVFVTX0NBVEVHT1JZ",
"X09QRVJBVElPTkFMX0VSUk9SEAcSJQohTVhfU1RBVFVTX0NBVEVHT1JZX1NF",
"Q1VSSVRZX0VSUk9SEAgSJQohTVhfU1RBVFVTX0NBVEVHT1JZX1NPRlRXQVJF",
"X0VSUk9SEAkSIgoeTVhfU1RBVFVTX0NBVEVHT1JZX09USEVSX0VSUk9SEAoq",
"ygIKDk14U3RhdHVzU291cmNlEiAKHE1YX1NUQVRVU19TT1VSQ0VfVU5TUEVD",
"SUZJRUQQABIcChhNWF9TVEFUVVNfU09VUkNFX1VOS05PV04QARIjCh9NWF9T",
"VEFUVVNfU09VUkNFX1JFUVVFU1RJTkdfTE1YEAISIwofTVhfU1RBVFVTX1NP",
"VVJDRV9SRVNQT05ESU5HX0xNWBADEiMKH01YX1NUQVRVU19TT1VSQ0VfUkVR",
"VUVTVElOR19OTVgQBBIjCh9NWF9TVEFUVVNfU09VUkNFX1JFU1BPTkRJTkdf",
"Tk1YEAUSMQotTVhfU1RBVFVTX1NPVVJDRV9SRVFVRVNUSU5HX0FVVE9NQVRJ",
"T05fT0JKRUNUEAYSMQotTVhfU1RBVFVTX1NPVVJDRV9SRVNQT05ESU5HX0FV",
"VE9NQVRJT05fT0JKRUNUEAcq3QQKCk14RGF0YVR5cGUSHAoYTVhfREFUQV9U",
"WVBFX1VOU1BFQ0lGSUVEEAASGAoUTVhfREFUQV9UWVBFX1VOS05PV04QARIY",
"ChRNWF9EQVRBX1RZUEVfTk9fREFUQRACEhgKFE1YX0RBVEFfVFlQRV9CT09M",
"RUFOEAMSGAoUTVhfREFUQV9UWVBFX0lOVEVHRVIQBBIWChJNWF9EQVRBX1RZ",
"UEVfRkxPQVQQBRIXChNNWF9EQVRBX1RZUEVfRE9VQkxFEAYSFwoTTVhfREFU",
"QV9UWVBFX1NUUklORxAHEhUKEU1YX0RBVEFfVFlQRV9USU1FEAgSHQoZTVhf",
"REFUQV9UWVBFX0VMQVBTRURfVElNRRAJEh8KG01YX0RBVEFfVFlQRV9SRUZF",
"UkVOQ0VfVFlQRRAKEhwKGE1YX0RBVEFfVFlQRV9TVEFUVVNfVFlQRRALEhUK",
"EU1YX0RBVEFfVFlQRV9FTlVNEAwSLQopTVhfREFUQV9UWVBFX1NFQ1VSSVRZ",
"X0NMQVNTSUZJQ0FUSU9OX0VOVU0QDRIiCh5NWF9EQVRBX1RZUEVfREFUQV9R",
"VUFMSVRZX1RZUEUQDhIfChtNWF9EQVRBX1RZUEVfUVVBTElGSUVEX0VOVU0Q",
"DxIhCh1NWF9EQVRBX1RZUEVfUVVBTElGSUVEX1NUUlVDVBAQEikKJU1YX0RB",
"VEFfVFlQRV9JTlRFUk5BVElPTkFMSVpFRF9TVFJJTkcQERIbChdNWF9EQVRB",
"X1RZUEVfQklHX1NUUklORxASEhQKEE1YX0RBVEFfVFlQRV9FTkQQEyqjAwoS",
"UHJvdG9jb2xTdGF0dXNDb2RlEiQKIFBST1RPQ09MX1NUQVRVU19DT0RFX1VO",
"U1BFQ0lGSUVEEAASGwoXUFJPVE9DT0xfU1RBVFVTX0NPREVfT0sQARIoCiRQ",
"Uk9UT0NPTF9TVEFUVVNfQ09ERV9JTlZBTElEX1JFUVVFU1QQAhIqCiZQUk9U",
"T0NPTF9TVEFUVVNfQ09ERV9TRVNTSU9OX05PVF9GT1VORBADEioKJlBST1RP",
"Q09MX1NUQVRVU19DT0RFX1NFU1NJT05fTk9UX1JFQURZEAQSKwonUFJPVE9D",
"T0xfU1RBVFVTX0NPREVfV09SS0VSX1VOQVZBSUxBQkxFEAUSIAocUFJPVE9D",
"T0xfU1RBVFVTX0NPREVfVElNRU9VVBAGEiEKHVBST1RPQ09MX1NUQVRVU19D",
"T0RFX0NBTkNFTEVEEAcSKwonUFJPVE9DT0xfU1RBVFVTX0NPREVfUFJPVE9D",
"T0xfVklPTEFUSU9OEAgSKQolUFJPVE9DT0xfU1RBVFVTX0NPREVfTVhBQ0NF",
"U1NfRkFJTFVSRRAJKr8CCgxTZXNzaW9uU3RhdGUSHQoZU0VTU0lPTl9TVEFU",
"RV9VTlNQRUNJRklFRBAAEhoKFlNFU1NJT05fU1RBVEVfQ1JFQVRJTkcQARIh",
"Ch1TRVNTSU9OX1NUQVRFX1NUQVJUSU5HX1dPUktFUhACEiIKHlNFU1NJT05f",
"U1RBVEVfV0FJVElOR19GT1JfUElQRRADEh0KGVNFU1NJT05fU1RBVEVfSEFO",
"RFNIQUtJTkcQBBIlCiFTRVNTSU9OX1NUQVRFX0lOSVRJQUxJWklOR19XT1JL",
"RVIQBRIXChNTRVNTSU9OX1NUQVRFX1JFQURZEAYSGQoVU0VTU0lPTl9TVEFU",
"RV9DTE9TSU5HEAcSGAoUU0VTU0lPTl9TVEFURV9DTE9TRUQQCBIZChVTRVNT",
"SU9OX1NUQVRFX0ZBVUxURUQQCTKCAwoPTXhBY2Nlc3NHYXRld2F5El0KC09w",
"ZW5TZXNzaW9uEicubXhhY2Nlc3NfZ2F0ZXdheS52MS5PcGVuU2Vzc2lvblJl",
"cXVlc3QaJS5teGFjY2Vzc19nYXRld2F5LnYxLk9wZW5TZXNzaW9uUmVwbHkS",
"YAoMQ2xvc2VTZXNzaW9uEigubXhhY2Nlc3NfZ2F0ZXdheS52MS5DbG9zZVNl",
"c3Npb25SZXF1ZXN0GiYubXhhY2Nlc3NfZ2F0ZXdheS52MS5DbG9zZVNlc3Np",
"b25SZXBseRJUCgZJbnZva2USJS5teGFjY2Vzc19nYXRld2F5LnYxLk14Q29t",
"bWFuZFJlcXVlc3QaIy5teGFjY2Vzc19nYXRld2F5LnYxLk14Q29tbWFuZFJl",
"cGx5ElgKDFN0cmVhbUV2ZW50cxIoLm14YWNjZXNzX2dhdGV3YXkudjEuU3Ry",
"ZWFtRXZlbnRzUmVxdWVzdBocLm14YWNjZXNzX2dhdGV3YXkudjEuTXhFdmVu",
"dDABQhyqAhlNeEdhdGV3YXkuQ29udHJhY3RzLlByb3RvYgZwcm90bzM="));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { global::Google.Protobuf.WellKnownTypes.DurationReflection.Descriptor, global::Google.Protobuf.WellKnownTypes.TimestampReflection.Descriptor, },
new pbr::GeneratedClrTypeInfo(new[] {typeof(global::MxGateway.Contracts.Proto.MxCommandKind), typeof(global::MxGateway.Contracts.Proto.MxEventFamily), typeof(global::MxGateway.Contracts.Proto.MxStatusCategory), typeof(global::MxGateway.Contracts.Proto.MxStatusSource), typeof(global::MxGateway.Contracts.Proto.MxDataType), typeof(global::MxGateway.Contracts.Proto.ProtocolStatusCode), typeof(global::MxGateway.Contracts.Proto.SessionState), }, null, new pbr::GeneratedClrTypeInfo[] {
new pbr::GeneratedClrTypeInfo(typeof(global::MxGateway.Contracts.Proto.OpenSessionRequest), global::MxGateway.Contracts.Proto.OpenSessionRequest.Parser, new[]{ "RequestedBackend", "ClientSessionName", "ClientCorrelationId", "CommandTimeout" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::MxGateway.Contracts.Proto.OpenSessionReply), global::MxGateway.Contracts.Proto.OpenSessionReply.Parser, new[]{ "SessionId", "BackendName", "WorkerProcessId", "WorkerProtocolVersion", "Capabilities", "DefaultCommandTimeout", "ProtocolStatus" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::MxGateway.Contracts.Proto.OpenSessionReply), global::MxGateway.Contracts.Proto.OpenSessionReply.Parser, new[]{ "SessionId", "BackendName", "WorkerProcessId", "WorkerProtocolVersion", "Capabilities", "DefaultCommandTimeout", "ProtocolStatus", "GatewayProtocolVersion" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::MxGateway.Contracts.Proto.CloseSessionRequest), global::MxGateway.Contracts.Proto.CloseSessionRequest.Parser, new[]{ "SessionId", "ClientCorrelationId" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::MxGateway.Contracts.Proto.CloseSessionReply), global::MxGateway.Contracts.Proto.CloseSessionReply.Parser, new[]{ "SessionId", "FinalState", "ProtocolStatus" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::MxGateway.Contracts.Proto.StreamEventsRequest), global::MxGateway.Contracts.Proto.StreamEventsRequest.Parser, new[]{ "SessionId", "AfterWorkerSequence" }, null, null, null, null),
@@ -841,6 +841,7 @@ namespace MxGateway.Contracts.Proto {
capabilities_ = other.capabilities_.Clone();
defaultCommandTimeout_ = other.defaultCommandTimeout_ != null ? other.defaultCommandTimeout_.Clone() : null;
protocolStatus_ = other.protocolStatus_ != null ? other.protocolStatus_.Clone() : null;
gatewayProtocolVersion_ = other.gatewayProtocolVersion_;
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
@@ -933,6 +934,23 @@ namespace MxGateway.Contracts.Proto {
}
}
/// <summary>Field number for the "gateway_protocol_version" field.</summary>
public const int GatewayProtocolVersionFieldNumber = 8;
private uint gatewayProtocolVersion_;
/// <summary>
/// Public gateway contract version implemented by this endpoint. Clients use
/// this value to reject incompatible generated-code inputs before issuing
/// command-specific MXAccess calls.
/// </summary>
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public uint GatewayProtocolVersion {
get { return gatewayProtocolVersion_; }
set {
gatewayProtocolVersion_ = value;
}
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override bool Equals(object other) {
@@ -955,6 +973,7 @@ namespace MxGateway.Contracts.Proto {
if(!capabilities_.Equals(other.capabilities_)) return false;
if (!object.Equals(DefaultCommandTimeout, other.DefaultCommandTimeout)) return false;
if (!object.Equals(ProtocolStatus, other.ProtocolStatus)) return false;
if (GatewayProtocolVersion != other.GatewayProtocolVersion) return false;
return Equals(_unknownFields, other._unknownFields);
}
@@ -969,6 +988,7 @@ namespace MxGateway.Contracts.Proto {
hash ^= capabilities_.GetHashCode();
if (defaultCommandTimeout_ != null) hash ^= DefaultCommandTimeout.GetHashCode();
if (protocolStatus_ != null) hash ^= ProtocolStatus.GetHashCode();
if (GatewayProtocolVersion != 0) hash ^= GatewayProtocolVersion.GetHashCode();
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
@@ -1012,6 +1032,10 @@ namespace MxGateway.Contracts.Proto {
output.WriteRawTag(58);
output.WriteMessage(ProtocolStatus);
}
if (GatewayProtocolVersion != 0) {
output.WriteRawTag(64);
output.WriteUInt32(GatewayProtocolVersion);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(output);
}
@@ -1047,6 +1071,10 @@ namespace MxGateway.Contracts.Proto {
output.WriteRawTag(58);
output.WriteMessage(ProtocolStatus);
}
if (GatewayProtocolVersion != 0) {
output.WriteRawTag(64);
output.WriteUInt32(GatewayProtocolVersion);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(ref output);
}
@@ -1076,6 +1104,9 @@ namespace MxGateway.Contracts.Proto {
if (protocolStatus_ != null) {
size += 1 + pb::CodedOutputStream.ComputeMessageSize(ProtocolStatus);
}
if (GatewayProtocolVersion != 0) {
size += 1 + pb::CodedOutputStream.ComputeUInt32Size(GatewayProtocolVersion);
}
if (_unknownFields != null) {
size += _unknownFields.CalculateSize();
}
@@ -1113,6 +1144,9 @@ namespace MxGateway.Contracts.Proto {
}
ProtocolStatus.MergeFrom(other.ProtocolStatus);
}
if (other.GatewayProtocolVersion != 0) {
GatewayProtocolVersion = other.GatewayProtocolVersion;
}
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
@@ -1166,6 +1200,10 @@ namespace MxGateway.Contracts.Proto {
input.ReadMessage(ProtocolStatus);
break;
}
case 64: {
GatewayProtocolVersion = input.ReadUInt32();
break;
}
}
}
#endif
@@ -1219,6 +1257,10 @@ namespace MxGateway.Contracts.Proto {
input.ReadMessage(ProtocolStatus);
break;
}
case 64: {
GatewayProtocolVersion = input.ReadUInt32();
break;
}
}
}
}
@@ -30,6 +30,10 @@ message OpenSessionReply {
repeated string capabilities = 5;
google.protobuf.Duration default_command_timeout = 6;
ProtocolStatus protocol_status = 7;
// Public gateway contract version implemented by this endpoint. Clients use
// this value to reject incompatible generated-code inputs before issuing
// command-specific MXAccess calls.
uint32 gateway_protocol_version = 8;
}
message CloseSessionRequest {
@@ -35,6 +35,7 @@ public sealed class MxAccessGatewayService(
BackendName = session.BackendName,
WorkerProcessId = session.WorkerProcessId ?? 0,
WorkerProtocolVersion = GatewayContractInfo.WorkerProtocolVersion,
GatewayProtocolVersion = GatewayContractInfo.GatewayProtocolVersion,
DefaultCommandTimeout = Google.Protobuf.WellKnownTypes.Duration.FromTimeSpan(session.CommandTimeout),
ProtocolStatus = MxAccessGrpcMapper.Ok(),
};
@@ -0,0 +1,102 @@
using System.Text.Json;
using Google.Protobuf;
using MxGateway.Contracts;
using MxGateway.Contracts.Proto;
namespace MxGateway.Tests.Contracts;
public sealed class ClientProtoInputTests
{
[Fact]
public void Manifest_DeclaresCurrentProtocolVersionsAndExistingInputs()
{
DirectoryInfo repositoryRoot = FindRepositoryRoot();
string manifestPath = Path.Combine(repositoryRoot.FullName, "clients", "proto", "proto-inputs.json");
using JsonDocument manifest = JsonDocument.Parse(File.ReadAllText(manifestPath));
JsonElement root = manifest.RootElement;
Assert.Equal(1, root.GetProperty("schemaVersion").GetInt32());
Assert.Equal(GatewayContractInfo.GatewayProtocolVersion, root.GetProperty("gatewayProtocolVersion").GetUInt32());
Assert.Equal(GatewayContractInfo.WorkerProtocolVersion, root.GetProperty("workerProtocolVersion").GetUInt32());
string protoRoot = Path.Combine(repositoryRoot.FullName, root.GetProperty("protoRoot").GetString()!);
foreach (JsonElement sourceFile in root.GetProperty("sourceFiles").EnumerateArray())
{
string sourcePath = Path.Combine(protoRoot, sourceFile.GetProperty("path").GetString()!);
Assert.True(File.Exists(sourcePath), $"Expected proto source file '{sourcePath}' to exist.");
}
foreach (JsonProperty output in root.GetProperty("generatedOutputs").EnumerateObject())
{
string outputPath = Path.Combine(repositoryRoot.FullName, output.Value.GetString()!);
Assert.True(Directory.Exists(outputPath), $"Expected generated output directory '{outputPath}' to exist.");
}
}
[Fact]
public void OpenSessionReplyFixture_ParsesWithCurrentContract()
{
OpenSessionReply reply = ParseFixture(
"open-session-reply.ok.json",
OpenSessionReply.Parser);
Assert.Equal(GatewayContractInfo.GatewayProtocolVersion, reply.GatewayProtocolVersion);
Assert.Equal(GatewayContractInfo.WorkerProtocolVersion, reply.WorkerProtocolVersion);
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
}
[Fact]
public void RegisterCommandRequestFixture_ParsesWithCurrentContract()
{
MxCommandRequest request = ParseFixture(
"register-command-request.json",
MxCommandRequest.Parser);
Assert.Equal(MxCommandKind.Register, request.Command.Kind);
Assert.Equal("fixture-client", request.Command.Register.ClientName);
}
[Fact]
public void OnDataChangeEventFixture_ParsesWithCurrentContract()
{
MxEvent gatewayEvent = ParseFixture(
"on-data-change-event.json",
MxEvent.Parser);
Assert.Equal(MxEventFamily.OnDataChange, gatewayEvent.Family);
Assert.Equal(1ul, gatewayEvent.WorkerSequence);
Assert.Equal(MxDataType.Integer, gatewayEvent.Value.DataType);
Assert.Equal(MxEvent.BodyOneofCase.OnDataChange, gatewayEvent.BodyCase);
}
private static T ParseFixture<T>(
string fixtureName,
MessageParser<T> parser)
where T : IMessage<T>
{
DirectoryInfo repositoryRoot = FindRepositoryRoot();
string fixturePath = Path.Combine(repositoryRoot.FullName, "clients", "proto", "fixtures", "golden", fixtureName);
return parser.ParseJson(File.ReadAllText(fixturePath));
}
private static DirectoryInfo FindRepositoryRoot()
{
DirectoryInfo? current = new(AppContext.BaseDirectory);
while (current is not null)
{
if (File.Exists(Path.Combine(current.FullName, "AGENTS.md"))
&& Directory.Exists(Path.Combine(current.FullName, "src"))
&& Directory.Exists(Path.Combine(current.FullName, "clients")))
{
return current;
}
current = current.Parent;
}
throw new DirectoryNotFoundException("Could not locate the repository root from the test output directory.");
}
}
@@ -10,6 +10,12 @@ public sealed class GatewayContractInfoTests
Assert.Equal("mxaccess-worker", GatewayContractInfo.DefaultBackendName);
}
[Fact]
public void GatewayProtocolVersion_StartsAtVersionOne()
{
Assert.Equal(1u, GatewayContractInfo.GatewayProtocolVersion);
}
[Fact]
public void WorkerProtocolVersion_StartsAtVersionOne()
{
@@ -37,6 +37,7 @@ public sealed class MxAccessGatewayServiceTests
Assert.Equal(GatewayContractInfo.DefaultBackendName, reply.BackendName);
Assert.Equal(4321, reply.WorkerProcessId);
Assert.Equal(GatewayContractInfo.WorkerProtocolVersion, reply.WorkerProtocolVersion);
Assert.Equal(GatewayContractInfo.GatewayProtocolVersion, reply.GatewayProtocolVersion);
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
Assert.Contains("unary-invoke", reply.Capabilities);
Assert.Equal("Operator Key", sessionManager.LastClientIdentity);
@@ -842,7 +842,9 @@ public sealed class MxAccessCommandExecutorTests
private sealed class NoopEventSink : IMxAccessEventSink
{
public void Attach(object mxAccessComObject)
public void Attach(
object mxAccessComObject,
string sessionId)
{
}
@@ -0,0 +1,117 @@
using System;
using MxGateway.Contracts.Proto;
using MxGateway.Worker.MxAccess;
namespace MxGateway.Worker.Tests.MxAccess;
public sealed class MxAccessEventMapperTests
{
private readonly MxAccessEventMapper mapper = new();
[Fact]
public void CreateOnDataChange_ConvertsValueTimestampQualityAndStatuses()
{
DateTime timestamp = new(2026, 4, 26, 12, 30, 0, DateTimeKind.Utc);
FakeStatus[] statuses =
{
new()
{
success = -1,
category = 0,
detectedBy = 5,
detail = 0,
},
};
MxEvent mxEvent = mapper.CreateOnDataChange(
"session-1",
serverHandle: 12,
itemHandle: 34,
value: 42,
quality: 192,
timestamp: timestamp,
statuses: statuses);
Assert.Equal(MxEventFamily.OnDataChange, mxEvent.Family);
Assert.Equal("session-1", mxEvent.SessionId);
Assert.Equal(12, mxEvent.ServerHandle);
Assert.Equal(34, mxEvent.ItemHandle);
Assert.Equal(42, mxEvent.Value.Int32Value);
Assert.Equal(192, mxEvent.Quality);
Assert.Equal(timestamp, mxEvent.SourceTimestamp.ToDateTime());
Assert.Equal(MxEvent.BodyOneofCase.OnDataChange, mxEvent.BodyCase);
MxStatusProxy status = Assert.Single(mxEvent.Statuses);
Assert.Equal(-1, status.Success);
Assert.Equal(MxStatusCategory.Ok, status.Category);
Assert.Equal(MxStatusSource.RespondingAutomationObject, status.DetectedBy);
}
[Fact]
public void CreateOnWriteCompleteAndOperationComplete_PreservesDistinctFamilies()
{
MxEvent writeComplete = mapper.CreateOnWriteComplete(
"session-1",
serverHandle: 1,
itemHandle: 2,
statuses: Array.Empty<FakeStatus>());
MxEvent operationComplete = mapper.CreateOperationComplete(
"session-1",
serverHandle: 1,
itemHandle: 2,
statuses: Array.Empty<FakeStatus>());
Assert.Equal(MxEventFamily.OnWriteComplete, writeComplete.Family);
Assert.Equal(MxEvent.BodyOneofCase.OnWriteComplete, writeComplete.BodyCase);
Assert.Equal(MxEventFamily.OperationComplete, operationComplete.Family);
Assert.Equal(MxEvent.BodyOneofCase.OperationComplete, operationComplete.BodyCase);
}
[Fact]
public void CreateOnBufferedDataChange_PreservesRawDataTypeAndArrayMetadata()
{
DateTime firstTimestamp = new(2026, 4, 26, 13, 0, 0, DateTimeKind.Utc);
DateTime secondTimestamp = new(2026, 4, 26, 13, 1, 0, DateTimeKind.Utc);
MxEvent mxEvent = mapper.CreateOnBufferedDataChange(
"session-1",
serverHandle: 10,
itemHandle: 20,
rawDataType: 2,
value: new[] { 7, 8 },
quality: new[] { 192, 0 },
timestamp: new[] { firstTimestamp, secondTimestamp },
statuses: null);
Assert.Equal(MxEventFamily.OnBufferedDataChange, mxEvent.Family);
Assert.Equal(MxDataType.Integer, mxEvent.OnBufferedDataChange.DataType);
Assert.Equal(2, mxEvent.OnBufferedDataChange.RawDataType);
Assert.Equal(MxDataType.Integer, mxEvent.Value.ArrayValue.ElementDataType);
Assert.Equal(new[] { 7, 8 }, mxEvent.Value.ArrayValue.Int32Values.Values);
Assert.Equal(new[] { 192, 0 }, mxEvent.OnBufferedDataChange.QualityValues.Int32Values.Values);
Assert.Equal(2, mxEvent.OnBufferedDataChange.TimestampValues.TimestampValues.Values.Count);
}
[Theory]
[InlineData(-1, MxDataType.Unknown)]
[InlineData(0, MxDataType.NoData)]
[InlineData(1, MxDataType.Boolean)]
[InlineData(2, MxDataType.Integer)]
[InlineData(6, MxDataType.Time)]
[InlineData(15, MxDataType.InternationalizedString)]
[InlineData(999, MxDataType.Unknown)]
public void MapMxDataType_MapsInstalledMxAccessValues(
int rawDataType,
MxDataType expectedDataType)
{
Assert.Equal(expectedDataType, MxAccessEventMapper.MapMxDataType(rawDataType));
}
private sealed class FakeStatus
{
public int success;
public int category;
public int detectedBy;
public int detail;
}
}
@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using MxGateway.Contracts.Proto;
using MxGateway.Worker.MxAccess;
namespace MxGateway.Worker.Tests.MxAccess;
public sealed class MxAccessEventQueueTests
{
[Fact]
public void Enqueue_AssignsMonotonicWorkerSequencesAndPreservesOrder()
{
MxAccessEventQueue queue = new(capacity: 4);
WorkerEvent first = queue.Enqueue(CreateEvent(MxEventFamily.OnDataChange, itemHandle: 10));
WorkerEvent second = queue.Enqueue(CreateEvent(MxEventFamily.OnWriteComplete, itemHandle: 11));
Assert.Equal(1UL, first.Event.WorkerSequence);
Assert.Equal(2UL, second.Event.WorkerSequence);
Assert.NotNull(first.Event.WorkerTimestamp);
Assert.Equal(2, queue.Count);
Assert.Equal(2UL, queue.LastEventSequence);
Assert.True(queue.TryDequeue(out WorkerEvent? dequeuedFirst));
Assert.True(queue.TryDequeue(out WorkerEvent? dequeuedSecond));
Assert.Equal(10, dequeuedFirst?.Event.ItemHandle);
Assert.Equal(11, dequeuedSecond?.Event.ItemHandle);
Assert.False(queue.TryDequeue(out _));
}
[Fact]
public void Drain_RemovesAtMostRequestedEvents()
{
MxAccessEventQueue queue = new(capacity: 4);
queue.Enqueue(CreateEvent(MxEventFamily.OnDataChange, itemHandle: 10));
queue.Enqueue(CreateEvent(MxEventFamily.OnDataChange, itemHandle: 11));
queue.Enqueue(CreateEvent(MxEventFamily.OnDataChange, itemHandle: 12));
IReadOnlyList<WorkerEvent> drained = queue.Drain(maxEvents: 2);
Assert.Equal(2, drained.Count);
Assert.Equal(10, drained[0].Event.ItemHandle);
Assert.Equal(11, drained[1].Event.ItemHandle);
Assert.Equal(1, queue.Count);
}
[Fact]
public void Enqueue_WhenCapacityIsExceeded_RecordsOverflowFaultAndRejectsNewEvents()
{
MxAccessEventQueue queue = new(capacity: 1);
queue.Enqueue(CreateEvent(MxEventFamily.OnDataChange, itemHandle: 10));
MxAccessEventQueueOverflowException overflow = Assert.Throws<MxAccessEventQueueOverflowException>(
() => queue.Enqueue(CreateEvent(MxEventFamily.OnDataChange, itemHandle: 11)));
Assert.Equal(1, overflow.Capacity);
Assert.True(queue.IsFaulted);
Assert.Equal(WorkerFaultCategory.QueueOverflow, queue.Fault?.Category);
Assert.Equal(ProtocolStatusCode.WorkerUnavailable, queue.Fault?.ProtocolStatus.Code);
Assert.Throws<InvalidOperationException>(
() => queue.Enqueue(CreateEvent(MxEventFamily.OnDataChange, itemHandle: 12)));
}
[Fact]
public void RecordFault_KeepsFirstFault()
{
MxAccessEventQueue queue = new(capacity: 1);
queue.RecordFault(new WorkerFault
{
Category = WorkerFaultCategory.MxaccessEventConversionFailed,
});
queue.RecordFault(new WorkerFault
{
Category = WorkerFaultCategory.QueueOverflow,
});
Assert.True(queue.IsFaulted);
Assert.Equal(WorkerFaultCategory.MxaccessEventConversionFailed, queue.Fault?.Category);
}
private static MxEvent CreateEvent(
MxEventFamily family,
int itemHandle)
{
MxEvent mxEvent = new()
{
Family = family,
SessionId = "session-1",
ServerHandle = 1,
ItemHandle = itemHandle,
};
switch (family)
{
case MxEventFamily.OnWriteComplete:
mxEvent.OnWriteComplete = new OnWriteCompleteEvent();
break;
default:
mxEvent.OnDataChange = new OnDataChangeEvent();
break;
}
return mxEvent;
}
}
@@ -18,7 +18,7 @@ public sealed class MxAccessStaSessionTests
using StaRuntime runtime = CreateRuntime();
using MxAccessStaSession session = new(runtime, factory, eventSink);
WorkerReady ready = await session.StartAsync(workerProcessId: 1234);
WorkerReady ready = await session.StartAsync("session-1", workerProcessId: 1234);
Assert.Equal(1234, ready.WorkerProcessId);
Assert.Equal(MxAccessInteropInfo.ProgId, ready.MxaccessProgid);
@@ -28,6 +28,7 @@ public sealed class MxAccessStaSessionTests
Assert.Equal(runtime.StaThreadId, eventSink.AttachThreadId);
Assert.Equal(ApartmentState.STA, factory.CreateApartmentState);
Assert.Same(factory.CreatedObject, eventSink.AttachedObject);
Assert.Equal("session-1", eventSink.SessionId);
}
[Fact]
@@ -107,10 +108,15 @@ public sealed class MxAccessStaSessionTests
public int? DetachThreadId { get; private set; }
public void Attach(object mxAccessComObject)
public string? SessionId { get; private set; }
public void Attach(
object mxAccessComObject,
string sessionId)
{
AttachedObject = mxAccessComObject;
AttachThreadId = Thread.CurrentThread.ManagedThreadId;
SessionId = sessionId;
}
public void Detach()
@@ -235,7 +235,7 @@ public sealed class WorkerPipeSession
try
{
return await _mxAccessStaSession
.StartAsync(_processIdProvider(), cancellationToken)
.StartAsync(_options.SessionId, _processIdProvider(), cancellationToken)
.ConfigureAwait(false);
}
catch
@@ -2,7 +2,9 @@ namespace MxGateway.Worker.MxAccess;
public interface IMxAccessEventSink
{
void Attach(object mxAccessComObject);
void Attach(
object mxAccessComObject,
string sessionId);
void Detach();
}
@@ -1,13 +1,39 @@
using System;
using ArchestrA.MxAccess;
using Proto = MxGateway.Contracts.Proto;
namespace MxGateway.Worker.MxAccess;
public sealed class MxAccessBaseEventSink : IMxAccessEventSink
{
private readonly MxAccessEventMapper eventMapper;
private readonly MxAccessEventQueue eventQueue;
private LMXProxyServerClass? server;
private string sessionId = string.Empty;
public void Attach(object mxAccessComObject)
public MxAccessBaseEventSink()
: this(new MxAccessEventQueue())
{
}
public MxAccessBaseEventSink(MxAccessEventQueue eventQueue)
: this(eventQueue, new MxAccessEventMapper())
{
}
public MxAccessBaseEventSink(
MxAccessEventQueue eventQueue,
MxAccessEventMapper eventMapper)
{
this.eventQueue = eventQueue ?? throw new ArgumentNullException(nameof(eventQueue));
this.eventMapper = eventMapper ?? throw new ArgumentNullException(nameof(eventMapper));
}
public void Attach(
object mxAccessComObject,
string sessionId)
{
this.sessionId = sessionId ?? string.Empty;
server = (LMXProxyServerClass)mxAccessComObject;
server.OnDataChange += OnDataChange;
server.OnWriteComplete += OnWriteComplete;
@@ -27,9 +53,10 @@ public sealed class MxAccessBaseEventSink : IMxAccessEventSink
server.OperationComplete -= OperationComplete;
server.OnBufferedDataChange -= OnBufferedDataChange;
server = null;
sessionId = string.Empty;
}
private static void OnDataChange(
private void OnDataChange(
int hLMXServerHandle,
int phItemHandle,
object pvItemValue,
@@ -37,23 +64,44 @@ public sealed class MxAccessBaseEventSink : IMxAccessEventSink
object pftItemTimeStamp,
ref MXSTATUS_PROXY[] pVars)
{
MXSTATUS_PROXY[] statuses = pVars;
EnqueueEvent(() => eventMapper.CreateOnDataChange(
sessionId,
hLMXServerHandle,
phItemHandle,
pvItemValue,
pwItemQuality,
pftItemTimeStamp,
statuses));
}
private static void OnWriteComplete(
private void OnWriteComplete(
int hLMXServerHandle,
int phItemHandle,
ref MXSTATUS_PROXY[] pVars)
{
MXSTATUS_PROXY[] statuses = pVars;
EnqueueEvent(() => eventMapper.CreateOnWriteComplete(
sessionId,
hLMXServerHandle,
phItemHandle,
statuses));
}
private static void OperationComplete(
private void OperationComplete(
int hLMXServerHandle,
int phItemHandle,
ref MXSTATUS_PROXY[] pVars)
{
MXSTATUS_PROXY[] statuses = pVars;
EnqueueEvent(() => eventMapper.CreateOperationComplete(
sessionId,
hLMXServerHandle,
phItemHandle,
statuses));
}
private static void OnBufferedDataChange(
private void OnBufferedDataChange(
int hLMXServerHandle,
int phItemHandle,
MxDataType dtDataType,
@@ -62,5 +110,42 @@ public sealed class MxAccessBaseEventSink : IMxAccessEventSink
object pftItemTimeStamp,
ref MXSTATUS_PROXY[] pVars)
{
MXSTATUS_PROXY[] statuses = pVars;
EnqueueEvent(() => eventMapper.CreateOnBufferedDataChange(
sessionId,
hLMXServerHandle,
phItemHandle,
(int)dtDataType,
pvItemValue,
pwItemQuality,
pftItemTimeStamp,
statuses));
}
private void EnqueueEvent(Func<Proto.MxEvent> createEvent)
{
try
{
eventQueue.Enqueue(createEvent());
}
catch (Exception exception)
{
eventQueue.RecordFault(CreateEventConversionFault(exception));
}
}
private Proto.WorkerFault CreateEventConversionFault(Exception exception)
{
return new Proto.WorkerFault
{
Category = Proto.WorkerFaultCategory.MxaccessEventConversionFailed,
ExceptionType = exception.GetType().FullName ?? string.Empty,
DiagnosticMessage = $"{exception.GetType().FullName}: HRESULT 0x{unchecked((uint)exception.HResult):X8}",
ProtocolStatus = new Proto.ProtocolStatus
{
Code = Proto.ProtocolStatusCode.MxaccessFailure,
Message = "MXAccess event conversion failed.",
},
};
}
}
@@ -0,0 +1,221 @@
using System;
using MxGateway.Contracts.Proto;
using MxGateway.Worker.Conversion;
namespace MxGateway.Worker.MxAccess;
public sealed class MxAccessEventMapper
{
private readonly VariantConverter variantConverter;
private readonly MxStatusProxyConverter statusProxyConverter;
public MxAccessEventMapper()
: this(new VariantConverter(), new MxStatusProxyConverter())
{
}
public MxAccessEventMapper(
VariantConverter variantConverter,
MxStatusProxyConverter statusProxyConverter)
{
this.variantConverter = variantConverter ?? throw new ArgumentNullException(nameof(variantConverter));
this.statusProxyConverter = statusProxyConverter ?? throw new ArgumentNullException(nameof(statusProxyConverter));
}
public MxEvent CreateOnDataChange(
string sessionId,
int serverHandle,
int itemHandle,
object? value,
int quality,
object? timestamp,
Array? statuses)
{
MxEvent mxEvent = CreateBaseEvent(
MxEventFamily.OnDataChange,
sessionId,
serverHandle,
itemHandle,
statuses);
mxEvent.Value = variantConverter.Convert(value);
mxEvent.Quality = quality;
ApplySourceTimestamp(mxEvent, timestamp);
mxEvent.OnDataChange = new OnDataChangeEvent();
return mxEvent;
}
public MxEvent CreateOnWriteComplete(
string sessionId,
int serverHandle,
int itemHandle,
Array? statuses)
{
MxEvent mxEvent = CreateBaseEvent(
MxEventFamily.OnWriteComplete,
sessionId,
serverHandle,
itemHandle,
statuses);
mxEvent.OnWriteComplete = new OnWriteCompleteEvent();
return mxEvent;
}
public MxEvent CreateOperationComplete(
string sessionId,
int serverHandle,
int itemHandle,
Array? statuses)
{
MxEvent mxEvent = CreateBaseEvent(
MxEventFamily.OperationComplete,
sessionId,
serverHandle,
itemHandle,
statuses);
mxEvent.OperationComplete = new OperationCompleteEvent();
return mxEvent;
}
public MxEvent CreateOnBufferedDataChange(
string sessionId,
int serverHandle,
int itemHandle,
int rawDataType,
object? value,
object? quality,
object? timestamp,
Array? statuses)
{
MxDataType dataType = MapMxDataType(rawDataType);
MxEvent mxEvent = CreateBaseEvent(
MxEventFamily.OnBufferedDataChange,
sessionId,
serverHandle,
itemHandle,
statuses);
mxEvent.Value = variantConverter.Convert(value, dataType);
mxEvent.OnBufferedDataChange = new OnBufferedDataChangeEvent
{
DataType = dataType,
RawDataType = rawDataType,
QualityValues = ConvertBufferedArray(quality, MxDataType.Integer),
TimestampValues = ConvertBufferedArray(timestamp, MxDataType.Time),
};
return mxEvent;
}
public static MxDataType MapMxDataType(int rawDataType)
{
return rawDataType switch
{
-1 => MxDataType.Unknown,
0 => MxDataType.NoData,
1 => MxDataType.Boolean,
2 => MxDataType.Integer,
3 => MxDataType.Float,
4 => MxDataType.Double,
5 => MxDataType.String,
6 => MxDataType.Time,
7 => MxDataType.ElapsedTime,
8 => MxDataType.ReferenceType,
9 => MxDataType.StatusType,
10 => MxDataType.Enum,
11 => MxDataType.SecurityClassificationEnum,
12 => MxDataType.DataQualityType,
13 => MxDataType.QualifiedEnum,
14 => MxDataType.QualifiedStruct,
15 => MxDataType.InternationalizedString,
16 => MxDataType.BigString,
17 => MxDataType.End,
_ => MxDataType.Unknown,
};
}
private MxEvent CreateBaseEvent(
MxEventFamily family,
string sessionId,
int serverHandle,
int itemHandle,
Array? statuses)
{
MxEvent mxEvent = new()
{
Family = family,
SessionId = sessionId ?? string.Empty,
ServerHandle = serverHandle,
ItemHandle = itemHandle,
};
mxEvent.Statuses.Add(statusProxyConverter.ConvertMany(statuses));
return mxEvent;
}
private void ApplySourceTimestamp(
MxEvent mxEvent,
object? timestamp)
{
MxValue convertedTimestamp = variantConverter.Convert(timestamp, MxDataType.Time);
if (convertedTimestamp.KindCase == MxValue.KindOneofCase.TimestampValue)
{
mxEvent.SourceTimestamp = convertedTimestamp.TimestampValue;
return;
}
if (!string.IsNullOrWhiteSpace(convertedTimestamp.RawDiagnostic))
{
mxEvent.RawStatus = string.IsNullOrWhiteSpace(mxEvent.RawStatus)
? convertedTimestamp.RawDiagnostic
: $"{mxEvent.RawStatus}; {convertedTimestamp.RawDiagnostic}";
}
}
private MxArray ConvertBufferedArray(
object? value,
MxDataType expectedElementDataType)
{
if (value is Array array)
{
return variantConverter.ConvertArray(array, expectedElementDataType);
}
MxValue converted = variantConverter.Convert(value, expectedElementDataType);
if (converted.KindCase == MxValue.KindOneofCase.ArrayValue)
{
return converted.ArrayValue;
}
MxArray mxArray = new()
{
ElementDataType = converted.DataType,
VariantType = converted.VariantType,
RawElementDataType = converted.RawDataType,
RawDiagnostic = string.IsNullOrWhiteSpace(converted.RawDiagnostic)
? "Buffered MXAccess event argument was not a SAFEARRAY."
: converted.RawDiagnostic,
};
switch (converted.KindCase)
{
case MxValue.KindOneofCase.Int32Value:
mxArray.Int32Values = new Int32Array();
mxArray.Int32Values.Values.Add(converted.Int32Value);
break;
case MxValue.KindOneofCase.Int64Value:
mxArray.Int64Values = new Int64Array();
mxArray.Int64Values.Values.Add(converted.Int64Value);
break;
case MxValue.KindOneofCase.TimestampValue:
mxArray.TimestampValues = new TimestampArray();
mxArray.TimestampValues.Values.Add(converted.TimestampValue);
break;
}
return mxArray;
}
}
@@ -0,0 +1,180 @@
using System;
using System.Collections.Generic;
using Google.Protobuf.WellKnownTypes;
using MxGateway.Contracts.Proto;
namespace MxGateway.Worker.MxAccess;
public sealed class MxAccessEventQueue
{
public const int DefaultCapacity = 10000;
private readonly int capacity;
private readonly Queue<WorkerEvent> events;
private readonly object syncRoot = new();
private ulong lastEventSequence;
private WorkerFault? fault;
public MxAccessEventQueue()
: this(DefaultCapacity)
{
}
public MxAccessEventQueue(int capacity)
{
if (capacity <= 0)
{
throw new ArgumentOutOfRangeException(
nameof(capacity),
"MXAccess event queue capacity must be greater than zero.");
}
this.capacity = capacity;
events = new Queue<WorkerEvent>(capacity);
}
public int Capacity => capacity;
public int Count
{
get
{
lock (syncRoot)
{
return events.Count;
}
}
}
public ulong LastEventSequence
{
get
{
lock (syncRoot)
{
return lastEventSequence;
}
}
}
public bool IsFaulted
{
get
{
lock (syncRoot)
{
return fault is not null;
}
}
}
public WorkerFault? Fault
{
get
{
lock (syncRoot)
{
return fault?.Clone();
}
}
}
public WorkerEvent Enqueue(MxEvent mxEvent)
{
if (mxEvent is null)
{
throw new ArgumentNullException(nameof(mxEvent));
}
lock (syncRoot)
{
if (fault is not null)
{
throw new InvalidOperationException("MXAccess outbound event queue is faulted.");
}
if (events.Count >= capacity)
{
fault = CreateOverflowFault();
throw new MxAccessEventQueueOverflowException(capacity);
}
MxEvent queuedEvent = mxEvent.Clone();
queuedEvent.WorkerSequence = ++lastEventSequence;
queuedEvent.WorkerTimestamp = Timestamp.FromDateTime(DateTime.UtcNow);
WorkerEvent workerEvent = new()
{
Event = queuedEvent,
};
events.Enqueue(workerEvent);
return workerEvent.Clone();
}
}
public bool TryDequeue(out WorkerEvent? workerEvent)
{
lock (syncRoot)
{
if (events.Count == 0)
{
workerEvent = null;
return false;
}
workerEvent = events.Dequeue().Clone();
return true;
}
}
public IReadOnlyList<WorkerEvent> Drain(uint maxEvents)
{
lock (syncRoot)
{
int drainCount = maxEvents == 0
? events.Count
: Math.Min(events.Count, checked((int)Math.Min(maxEvents, int.MaxValue)));
if (drainCount == 0)
{
return Array.Empty<WorkerEvent>();
}
List<WorkerEvent> drained = new(drainCount);
for (int index = 0; index < drainCount; index++)
{
drained.Add(events.Dequeue().Clone());
}
return drained;
}
}
public void RecordFault(WorkerFault workerFault)
{
if (workerFault is null)
{
throw new ArgumentNullException(nameof(workerFault));
}
lock (syncRoot)
{
fault ??= workerFault.Clone();
}
}
private WorkerFault CreateOverflowFault()
{
string message = $"MXAccess outbound event queue reached capacity {capacity}.";
return new WorkerFault
{
Category = WorkerFaultCategory.QueueOverflow,
DiagnosticMessage = message,
ProtocolStatus = new ProtocolStatus
{
Code = ProtocolStatusCode.WorkerUnavailable,
Message = message,
},
};
}
}
@@ -0,0 +1,14 @@
using System;
namespace MxGateway.Worker.MxAccess;
public sealed class MxAccessEventQueueOverflowException : Exception
{
public MxAccessEventQueueOverflowException(int capacity)
: base($"MXAccess outbound event queue reached its configured capacity of {capacity}.")
{
Capacity = capacity;
}
public int Capacity { get; }
}
@@ -44,7 +44,8 @@ public sealed class MxAccessSession : IDisposable
public static MxAccessSession Create(
IMxAccessComObjectFactory factory,
IMxAccessEventSink eventSink)
IMxAccessEventSink eventSink,
string sessionId)
{
if (factory is null)
{
@@ -66,7 +67,7 @@ public sealed class MxAccessSession : IDisposable
throw new InvalidOperationException("MXAccess COM factory returned null.");
}
eventSink.Attach(mxAccessComObject);
eventSink.Attach(mxAccessComObject, sessionId);
return new MxAccessSession(
mxAccessComObject,
@@ -11,6 +11,7 @@ public sealed class MxAccessStaSession : IDisposable
{
private readonly IMxAccessComObjectFactory factory;
private readonly IMxAccessEventSink eventSink;
private readonly MxAccessEventQueue eventQueue;
private readonly StaRuntime staRuntime;
private StaCommandDispatcher? commandDispatcher;
private MxAccessSession? session;
@@ -20,7 +21,7 @@ public sealed class MxAccessStaSession : IDisposable
: this(
new StaRuntime(),
new MxAccessComObjectFactory(),
new MxAccessBaseEventSink())
new MxAccessEventQueue())
{
}
@@ -28,13 +29,41 @@ public sealed class MxAccessStaSession : IDisposable
StaRuntime staRuntime,
IMxAccessComObjectFactory factory,
IMxAccessEventSink eventSink)
: this(staRuntime, factory, eventSink, new MxAccessEventQueue())
{
}
public MxAccessStaSession(
StaRuntime staRuntime,
IMxAccessComObjectFactory factory,
MxAccessEventQueue eventQueue)
: this(staRuntime, factory, new MxAccessBaseEventSink(eventQueue), eventQueue)
{
}
public MxAccessStaSession(
StaRuntime staRuntime,
IMxAccessComObjectFactory factory,
IMxAccessEventSink eventSink,
MxAccessEventQueue eventQueue)
{
this.staRuntime = staRuntime ?? throw new ArgumentNullException(nameof(staRuntime));
this.factory = factory ?? throw new ArgumentNullException(nameof(factory));
this.eventSink = eventSink ?? throw new ArgumentNullException(nameof(eventSink));
this.eventQueue = eventQueue ?? throw new ArgumentNullException(nameof(eventQueue));
}
public MxAccessEventQueue EventQueue => eventQueue;
public Task<WorkerReady> StartAsync(
int workerProcessId,
CancellationToken cancellationToken = default)
{
return StartAsync(string.Empty, workerProcessId, cancellationToken);
}
public Task<WorkerReady> StartAsync(
string sessionId,
int workerProcessId,
CancellationToken cancellationToken = default)
{
@@ -48,7 +77,7 @@ public sealed class MxAccessStaSession : IDisposable
throw new InvalidOperationException("MXAccess COM session has already been created.");
}
session = MxAccessSession.Create(factory, eventSink);
session = MxAccessSession.Create(factory, eventSink, sessionId);
commandDispatcher = new StaCommandDispatcher(
staRuntime,
new MxAccessCommandExecutor(session));
@@ -68,6 +97,11 @@ public sealed class MxAccessStaSession : IDisposable
return commandDispatcher.DispatchAsync(command);
}
public IReadOnlyList<WorkerEvent> DrainEvents(uint maxEvents)
{
return eventQueue.Drain(maxEvents);
}
public Task<IReadOnlyList<RegisteredServerHandle>> GetRegisteredServerHandlesAsync(
CancellationToken cancellationToken = default)
{