Initial project state: .NET reference, design, Rust port (M0+M1), evidence
rust / build / test / clippy / fmt (push) Has been cancelled

Layout:
- src/                    .NET 10 x64 reference: MxNativeCodec, MxNativeClient,
                          MxAsbClient, probes, tests, harnesses. Executable spec.
- design/                 Architectural plan for the Rust port (M0–M6), error
                          model, protocol invariants, risks (R1–R16), adversarial
                          review log (review.md).
- rust/                   Rust workspace. M0 skeleton + M1 codec parity.
                          mxaccess-codec: 215 unit tests + 2 cross-implementation
                          parity tests (byte-identical against .NET reference).
                          Other crates are M0 stubs awaiting M2+.
- captures/               Frida + netsh + pcap evidence per CLAUDE.md
                          ("captures are evidence, not throwaway logs").
- analysis/               Decompiled C# (frida/proxy/decompiled-*),
                          Ghidra exports for native DLLs (`exports/` only —
                          working state at `projects/` and AVEVA's input
                          binaries at `input/` are gitignored).
- docs/                   Reverse-engineering reference docs.
- tools/                  Setup-LiveProbeEnv.ps1 (Infisical credential fetcher),
                          Compute-Crc.ps1 (.NET parity helper).
- .github/workflows/      Rust CI: fmt + build + test + clippy on Windows.
- LICENSE                 MIT (Joseph Doherty, 2026).

Verified:
- cargo test --workspace → 217 passed (215 unit + 2 .NET parity), 0 failed
- cargo clippy --workspace -- -D warnings → clean
- cargo fmt --all -- --check → clean
- cargo publish --dry-run -p mxaccess-codec → packages cleanly

Excluded from history (see .gitignore):
- **/bin, **/obj, **/target — build artifacts
- analysis/ghidra/projects/ — Ghidra working state (regenerable)
- analysis/ghidra/input/ — AVEVA proprietary DLLs (vendor IP)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-05 06:21:00 -04:00
parent 43733699b0
commit fe2a6db786
3849 changed files with 352975 additions and 0 deletions
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MxNativeCodec\MxNativeCodec.csproj" />
</ItemGroup>
</Project>
+860
View File
@@ -0,0 +1,860 @@
using System.Buffers.Binary;
using MxNativeCodec;
RunRoundTrip("int", MxValueKind.Int32, 109,
"37 01 00 05 00 36 d7 02 00 9b 00 0a 00 3e da 00 00 02 6d 00 00 00 ff ff 00 00 00 00 00 00 00 00 c9 14 b1 08 01 00 00 00");
RunRoundTrip("bool", MxValueKind.Boolean, true,
"37 01 00 05 00 36 d7 02 00 9a 00 0a 00 fa 7d 00 00 01 ff ff ff 00 00 00 00 00 00 00 00 e1 d8 b5 08 01 00 00 00");
RunRoundTrip("float", MxValueKind.Float32, 1.25f,
"37 01 00 05 00 36 d7 02 00 9c 00 0a 00 a6 ed 00 00 03 00 00 a0 3f ff ff 00 00 00 00 00 00 00 00 64 6f b6 08 01 00 00 00");
RunRoundTrip("double", MxValueKind.Float64, 1.125d,
"37 01 00 05 00 36 d7 02 00 9d 00 0a 00 1e 95 00 00 04 00 00 00 00 00 00 f2 3f ff ff 00 00 00 00 00 00 00 00 bf 04 b7 08 01 00 00 00");
RunRoundTrip("string", MxValueKind.String, "AlphaMX",
"37 01 00 05 00 36 d7 02 00 9e 00 0a 00 1a 94 00 00 05 14 00 00 00 10 00 00 00 41 00 6c 00 70 00 68 00 61 00 4d 00 58 00 00 00 ff ff 00 00 00 00 00 00 00 00 3d a1 b7 08 01 00 00 00");
RunRoundTrip("elapsed int caller", MxValueKind.Int32, 1000,
"37 01 00 06 00 08 f4 7e 00 7d 00 0a 00 ac 59 00 00 02 e8 03 00 00 ff ff 00 00 00 00 00 00 00 00 e0 bf fe 0b 01 00 00 00");
RunRoundTrip("internationalized string caller", MxValueKind.String, "hello-native",
"37 01 00 05 00 36 d7 02 00 65 00 0a 00 6d 02 00 00 05 1e 00 00 00 1a 00 00 00 68 00 65 00 6c 00 6c 00 6f 00 2d 00 6e 00 61 00 74 00 69 00 76 00 65 00 00 00 ff ff 00 00 00 00 00 00 00 00 94 68 ff 0b 01 00 00 00");
RunRoundTrip("datetime", MxValueKind.DateTime, new DateTime(2026, 4, 25, 2, 30, 0),
"37 01 00 05 00 36 d7 02 00 9f 00 0a 00 62 49 00 00 05 2e 00 00 00 2a 00 00 00 34 00 2f 00 32 00 35 00 2f 00 32 00 30 00 32 00 36 00 20 00 32 00 3a 00 33 00 30 00 3a 00 30 00 30 00 20 00 41 00 4d 00 00 00 ff ff 00 00 00 00 00 00 00 00 58 94 b8 08 01 00 00 00");
RunRoundTrip("write2 int timestamp", MxValueKind.Int32, 114,
"37 01 00 05 00 36 d7 02 00 9b 00 0a 00 3e da 00 00 02 72 00 00 00 00 00 00 72 68 3a 83 d4 dc 01 20 2e d8 08 01 00 00 00",
timestamp: new DateTime(2026, 4, 25, 7, 15, 0, DateTimeKind.Utc));
RunTimestampedGenerated("write2 bool timestamp", MxValueKind.Boolean, false,
"37 01 00 05 00 36 d7 02 00 9a 00 0a 00 fa 7d 00 00 01 00 00 00 00 d3 c1 2f ab d4 dc 01 c9 dd a5 0b 01 00 00 00",
timestamp: new DateTime(2026, 4, 25, 8, 1, 2));
RunRoundTrip("write2 float timestamp", MxValueKind.Float32, 6.25f,
"37 01 00 05 00 36 d7 02 00 9c 00 0a 00 a6 ed 00 00 03 00 00 c8 40 00 00 80 af 1d 54 ab d4 dc 01 94 c4 a5 0b 01 00 00 00",
timestamp: new DateTime(2026, 4, 25, 8, 2, 3));
RunRoundTrip("write2 double timestamp", MxValueKind.Float64, 7.125d,
"37 01 00 05 00 36 d7 02 00 9d 00 0a 00 1e 95 00 00 04 00 00 00 00 00 80 1c 40 00 00 00 8c 79 78 ab d4 dc 01 a3 56 a6 0b 01 00 00 00",
timestamp: new DateTime(2026, 4, 25, 8, 3, 4));
RunRoundTrip("write2 string timestamp", MxValueKind.String, "Write2Alpha",
"37 01 00 05 00 36 d7 02 00 9e 00 0a 00 1a 94 00 00 05 1c 00 00 00 18 00 00 00 57 00 72 00 69 00 74 00 65 00 32 00 41 00 6c 00 70 00 68 00 61 00 00 00 00 00 80 68 d5 9c ab d4 dc 01 9f 6e a6 0b 01 00 00 00",
timestamp: new DateTime(2026, 4, 25, 8, 4, 5));
RunRoundTrip("int[]", MxValueKind.Int32Array, new[] { 201, 202, 203, 204, 205, 206, 207, 208, 209, 210 },
"37 01 00 05 00 36 d7 02 00 a4 00 0a 00 60 57 ff ff 42 00 00 00 00 0a 00 04 00 00 00 c9 00 00 00 ca 00 00 00 cb 00 00 00 cc 00 00 00 cd 00 00 00 ce 00 00 00 cf 00 00 00 d0 00 00 00 d1 00 00 00 d2 00 00 00 ff ff 00 00 00 00 00 00 00 00 4d c3 c4 08 01 00 00 00");
RunRoundTrip("bool[]", MxValueKind.BooleanArray, new[] { true, true, false, false, true, true, false, false, true, true },
"37 01 00 05 00 36 d7 02 00 a0 00 0a 00 fa 89 ff ff 41 00 00 00 00 0a 00 02 00 00 00 ff ff ff ff 00 00 00 00 ff ff ff ff 00 00 00 00 ff ff ff ff ff ff 00 00 00 00 00 00 00 00 a8 7f c5 08 01 00 00 00");
RunRoundTrip("bool[] mxaccess safearray projection", MxValueKind.BooleanArray, new[] { true, true, false, false, false, false, true, true, true, true },
"37 01 00 05 00 36 d7 02 00 a0 00 0a 00 fa 89 ff ff 41 00 00 00 00 0a 00 02 00 00 00 ff ff ff ff 00 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00 ac ec 08 0c 01 00 00 00");
RunRoundTrip("float[]", MxValueKind.Float32Array, new[] { 1.25f, 2.5f, 3.75f, 4.25f, 5.5f, 6.75f, 7.25f, 8.5f, 9.75f, 10.25f },
"37 01 00 05 00 36 d7 02 00 a3 00 0a 00 95 f1 ff ff 43 00 00 00 00 0a 00 04 00 00 00 00 00 a0 3f 00 00 20 40 00 00 70 40 00 00 88 40 00 00 b0 40 00 00 d8 40 00 00 e8 40 00 00 08 41 00 00 1c 41 00 00 24 41 ff ff 00 00 00 00 00 00 00 00 0c f4 c5 08 01 00 00 00");
RunRoundTrip("double[]", MxValueKind.Float64Array, new[] { 1.125d, 2.25d, 3.5d, 4.625d, 5.75d, 6.875d, 7.0d, 8.125d, 9.25d, 10.375d },
"37 01 00 05 00 36 d7 02 00 a2 00 0a 00 11 0e ff ff 44 00 00 00 00 0a 00 08 00 00 00 00 00 00 00 00 00 f2 3f 00 00 00 00 00 00 02 40 00 00 00 00 00 00 0c 40 00 00 00 00 00 80 12 40 00 00 00 00 00 00 17 40 00 00 00 00 00 80 1b 40 00 00 00 00 00 00 1c 40 00 00 00 00 00 40 20 40 00 00 00 00 00 80 22 40 00 00 00 00 00 c0 24 40 ff ff 00 00 00 00 00 00 00 00 cf 68 c6 08 01 00 00 00");
RunRoundTrip("string[]", MxValueKind.StringArray, new[] { "A01", "B02", "C03", "D04", "E05", "F06", "G07", "H08", "I09", "J10" },
"37 01 00 05 00 36 d7 02 00 a5 00 0a 00 5d 4b ff ff 45 00 00 00 00 0a 00 04 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 41 00 30 00 31 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 42 00 30 00 32 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 43 00 30 00 33 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 44 00 30 00 34 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 45 00 30 00 35 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 46 00 30 00 36 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 47 00 30 00 37 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 48 00 30 00 38 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 49 00 30 00 39 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 4a 00 31 00 30 00 00 00 ff ff 00 00 00 00 00 00 00 00 c3 da c6 08 01 00 00 00");
RunRoundTrip("write2 int[] timestamp", MxValueKind.Int32Array, new[] { 301, 302, 303, 304, 305, 306, 307, 308, 309, 310 },
"37 01 00 05 00 36 d7 02 00 a4 00 0a 00 60 57 ff ff 42 00 00 00 00 0a 00 04 00 00 00 2d 01 00 00 2e 01 00 00 2f 01 00 00 30 01 00 00 31 01 00 00 32 01 00 00 33 01 00 00 34 01 00 00 35 01 00 00 36 01 00 00 00 00 80 93 fc 76 ac d4 dc 01 43 35 ac 0b 01 00 00 00",
timestamp: new DateTime(2026, 4, 25, 8, 10, 11));
RunRoundTrip("write2 bool[] timestamp", MxValueKind.BooleanArray, new[] { true, true, false, false, true, true, false, false, true, true },
"37 01 00 05 00 36 d7 02 00 a0 00 0a 00 fa 89 ff ff 41 00 00 00 00 0a 00 02 00 00 00 ff ff ff ff 00 00 00 00 ff ff ff ff 00 00 00 00 ff ff ff ff 00 00 00 70 58 9b ac d4 dc 01 8b 1c ac 0b 01 00 00 00",
timestamp: new DateTime(2026, 4, 25, 8, 11, 12));
RunRoundTrip("write2 string[] timestamp", MxValueKind.StringArray, new[] { "AA1", "BB2", "CC3", "DD4", "EE5", "FF6", "GG7", "HH8", "II9", "JJ10" },
"37 01 00 05 00 36 d7 02 00 a5 00 0a 00 5d 4b ff ff 45 00 00 00 00 0a 00 04 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 41 00 41 00 31 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 42 00 42 00 32 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 43 00 43 00 33 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 44 00 44 00 34 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 45 00 45 00 35 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 46 00 46 00 36 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 47 00 47 00 37 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 48 00 48 00 38 00 00 00 11 00 00 00 05 0c 00 00 00 08 00 00 00 49 00 49 00 39 00 00 00 13 00 00 00 05 0e 00 00 00 0a 00 00 00 4a 00 4a 00 31 00 30 00 00 00 00 00 80 4c b4 bf ac d4 dc 01 10 a3 ac 0b 01 00 00 00",
timestamp: new DateTime(2026, 4, 25, 8, 12, 13));
RunRoundTrip("write2 float[] timestamp", MxValueKind.Float32Array, new[] { 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f, 7.5f, 8.5f, 9.5f, 10.5f },
"37 01 00 05 00 36 d7 02 00 a3 00 0a 00 95 f1 ff ff 43 00 00 00 00 0a 00 04 00 00 00 00 00 c0 3f 00 00 20 40 00 00 60 40 00 00 90 40 00 00 b0 40 00 00 d0 40 00 00 f0 40 00 00 08 41 00 00 18 41 00 00 28 41 00 00 00 29 10 e4 ac d4 dc 01 fa 6a af 0b 01 00 00 00",
timestamp: new DateTime(2026, 4, 25, 8, 13, 14));
RunRoundTrip("write2 double[] timestamp", MxValueKind.Float64Array, new[] { 1.5d, 2.5d, 3.5d, 4.5d, 5.5d, 6.5d, 7.5d, 8.5d, 9.5d, 10.5d },
"37 01 00 05 00 36 d7 02 00 a2 00 0a 00 11 0e ff ff 44 00 00 00 00 0a 00 08 00 00 00 00 00 00 00 00 00 f8 3f 00 00 00 00 00 00 04 40 00 00 00 00 00 00 0c 40 00 00 00 00 00 00 12 40 00 00 00 00 00 00 16 40 00 00 00 00 00 00 1a 40 00 00 00 00 00 00 1e 40 00 00 00 00 00 00 21 40 00 00 00 00 00 00 23 40 00 00 00 00 00 00 25 40 00 00 80 05 6c 08 ad d4 dc 01 42 52 af 0b 01 00 00 00",
timestamp: new DateTime(2026, 4, 25, 8, 14, 15));
RunRoundTrip("write2 datetime[] timestamp", MxValueKind.DateTimeArray, new[]
{
new DateTime(2026, 4, 25, 9, 0, 0),
new DateTime(2026, 4, 25, 9, 1, 0),
new DateTime(2026, 4, 25, 9, 2, 0),
new DateTime(2026, 4, 25, 9, 3, 0),
new DateTime(2026, 4, 25, 9, 4, 0),
new DateTime(2026, 4, 25, 9, 5, 0),
new DateTime(2026, 4, 25, 9, 6, 0),
new DateTime(2026, 4, 25, 9, 7, 0),
new DateTime(2026, 4, 25, 9, 8, 0),
new DateTime(2026, 4, 25, 9, 9, 0),
},
"37 01 00 05 00 36 d7 02 00 a1 00 0a 00 1b df ff ff 45 00 00 00 00 0a 00 04 00 00 00 33 00 00 00 05 2e 00 00 00 2a 00 00 00 34 00 2f 00 32 00 35 00 2f 00 32 00 30 00 32 00 36 00 20 00 39 00 3a 00 30 00 30 00 3a 00 30 00 30 00 20 00 41 00 4d 00 00 00 33 00 00 00 05 2e 00 00 00 2a 00 00 00 34 00 2f 00 32 00 35 00 2f 00 32 00 30 00 32 00 36 00 20 00 39 00 3a 00 30 00 31 00 3a 00 30 00 30 00 20 00 41 00 4d 00 00 00 33 00 00 00 05 2e 00 00 00 2a 00 00 00 34 00 2f 00 32 00 35 00 2f 00 32 00 30 00 32 00 36 00 20 00 39 00 3a 00 30 00 32 00 3a 00 30 00 30 00 20 00 41 00 4d 00 00 00 33 00 00 00 05 2e 00 00 00 2a 00 00 00 34 00 2f 00 32 00 35 00 2f 00 32 00 30 00 32 00 36 00 20 00 39 00 3a 00 30 00 33 00 3a 00 30 00 30 00 20 00 41 00 4d 00 00 00 33 00 00 00 05 2e 00 00 00 2a 00 00 00 34 00 2f 00 32 00 35 00 2f 00 32 00 30 00 32 00 36 00 20 00 39 00 3a 00 30 00 34 00 3a 00 30 00 30 00 20 00 41 00 4d 00 00 00 33 00 00 00 05 2e 00 00 00 2a 00 00 00 34 00 2f 00 32 00 35 00 2f 00 32 00 30 00 32 00 36 00 20 00 39 00 3a 00 30 00 35 00 3a 00 30 00 30 00 20 00 41 00 4d 00 00 00 33 00 00 00 05 2e 00 00 00 2a 00 00 00 34 00 2f 00 32 00 35 00 2f 00 32 00 30 00 32 00 36 00 20 00 39 00 3a 00 30 00 36 00 3a 00 30 00 30 00 20 00 41 00 4d 00 00 00 33 00 00 00 05 2e 00 00 00 2a 00 00 00 34 00 2f 00 32 00 35 00 2f 00 32 00 30 00 32 00 36 00 20 00 39 00 3a 00 30 00 37 00 3a 00 30 00 30 00 20 00 41 00 4d 00 00 00 33 00 00 00 05 2e 00 00 00 2a 00 00 00 34 00 2f 00 32 00 35 00 2f 00 32 00 30 00 32 00 36 00 20 00 39 00 3a 00 30 00 38 00 3a 00 30 00 30 00 20 00 41 00 4d 00 00 00 33 00 00 00 05 2e 00 00 00 2a 00 00 00 34 00 2f 00 32 00 35 00 2f 00 32 00 30 00 32 00 36 00 20 00 39 00 3a 00 30 00 39 00 3a 00 30 00 30 00 20 00 41 00 4d 00 00 00 00 00 00 e2 c7 2c ad d4 dc 01 35 07 b1 0b 01 00 00 00",
timestamp: new DateTime(2026, 4, 25, 8, 15, 16));
RunTransferEnvelopeRoundTrip(
"int transfer envelope",
"37 01 00 05 00 36 d7 02 00 9b 00 0a 00 3e da 00 00 02 17 cd 5b 07 ff ff 00 00 00 00 00 00 00 00 24 3c ed 08 01 00 00 00",
"01 00 28 00 00 00 00 00 00 00 03 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 00 00 00 01 00 00 00 02 00 00 00 01 02 00 00 30 75 00 00 37 01 00 05 00 36 d7 02 00 9b 00 0a 00 3e da 00 00 02 17 cd 5b 07 ff ff 00 00 00 00 00 00 00 00 24 3c ed 08 01 00 00 00");
RunObservedFrameTests();
RunItemControlMessageTests();
RunMxReferenceHandleTests();
RunGeneratedTransferEnvelopeTests();
RunReferenceRegistrationMessageTests();
RunProtectedWriteGenerationTests();
RunSecuredWrite2GenerationTests();
RunMxStatusDetailTests();
RunMxDataTypeSupportTests();
Console.WriteLine("MxNativeCodec observed write-body round trips passed.");
static void RunRoundTrip(string name, MxValueKind kind, object value, string hex, DateTime? timestamp = null)
{
byte[] observed = FromHex(hex);
var template = ObservedWriteBodyTemplate.FromObserved(kind, observed);
object decoded = template.Decode(observed);
AssertValue(name, value, decoded);
AssertEqual(name + " write index", 1, template.DecodeWriteIndex(observed));
byte[] encoded = template.Encode(value, writeIndex: 1);
AssertBytes(name, observed, encoded);
if (timestamp.HasValue)
{
var handle = HandleFromObservedWriteBody(observed);
uint clientToken = BinaryPrimitives.ReadUInt32LittleEndian(observed.AsSpan(observed.Length - 8, sizeof(uint)));
byte[] generated = NmxWriteMessage.EncodeTimestamped(
handle,
kind,
decoded,
timestamp.Value,
template.DecodeWriteIndex(observed),
clientToken);
AssertBytes(name + " generated", observed, generated);
}
else
{
var handle = HandleFromObservedWriteBody(observed);
uint clientToken = BinaryPrimitives.ReadUInt32LittleEndian(observed.AsSpan(observed.Length - 8, sizeof(uint)));
byte[] generated = NmxWriteMessage.Encode(
handle,
kind,
decoded,
template.DecodeWriteIndex(observed),
clientToken);
AssertBytes(name + " generated", observed, generated);
}
}
static void RunTimestampedGenerated(string name, MxValueKind kind, object value, string hex, DateTime timestamp)
{
byte[] observed = FromHex(hex);
var handle = HandleFromObservedWriteBody(observed);
uint clientToken = BinaryPrimitives.ReadUInt32LittleEndian(observed.AsSpan(observed.Length - 8, sizeof(uint)));
int writeIndex = BinaryPrimitives.ReadInt32LittleEndian(observed.AsSpan(observed.Length - sizeof(int), sizeof(int)));
byte[] generated = NmxWriteMessage.EncodeTimestamped(
handle,
kind,
value,
timestamp,
writeIndex,
clientToken);
AssertBytes(name + " generated", observed, generated);
}
static void RunTransferEnvelopeRoundTrip(string name, string innerHex, string transferHex)
{
byte[] inner = FromHex(innerHex);
byte[] transfer = FromHex(transferHex);
var template = NmxTransferEnvelopeTemplate.FromObserved(transfer);
AssertBytes(name + " decoded inner", inner, template.DecodeInner(transfer).Span);
AssertBytes(name, transfer, template.Encode(inner));
}
static void RunObservedFrameTests()
{
byte[] adviseTransfer = FromHex(
"01 00 27 00 00 00 00 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 00 00 00 01 00 00 00 02 00 00 00 01 02 00 00 30 75 00 00 " +
"1f 01 00 c0 ca 9c cd 32 65 b0 46 a5 85 a5 83 b2 e7 7a 5d 00 00 05 00 36 d7 02 00 9b 00 0a 00 3e da 00 00 03 00 00 00");
var adviseEnvelope = NmxObservedEnvelope.ParseTransferDataBody(adviseTransfer);
AssertEqual("advise declared inner", 39, adviseEnvelope.DeclaredInnerLength);
AssertEqual("advise actual inner", 39, adviseEnvelope.ActualInnerLength);
var advise = NmxObservedMessage.Parse(adviseEnvelope.InnerBody.Span);
AssertEqual("advise command", (byte)0x1f, advise.Command);
AssertEqual("advise command name", "AdviseSupervisory", advise.CommandName);
AssertEqual("advise version major", (byte)1, advise.VersionMajor);
AssertEqual("advise correlation", new Guid("cd9ccac0-6532-46b0-a585-a583b2e77a5d"), advise.ItemCorrelationId);
byte[] statusReceived = FromHex(
"6c 00 00 00 01 00 3e 00 00 00 00 00 00 00 5d 89 05 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 02 00 00 30 75 00 00 " +
"32 01 00 01 00 00 00 db 4e 14 ba e9 25 85 47 81 e4 e0 e7 1a d2 b1 aa c0 ca 9c cd 32 65 b0 46 a5 85 a5 83 b2 e7 7a 5d 03 00 00 00 03 00 00 00 c0 00 60 9a 60 95 84 d4 dc 01 02");
var statusEnvelope = NmxObservedEnvelope.ParseProcessDataReceivedBody(statusReceived);
AssertEqual("status total prefix", 108, statusEnvelope.TotalLengthPrefix!.Value);
AssertEqual("status declared inner", 62, statusEnvelope.DeclaredInnerLength);
AssertEqual("status actual inner", 58, statusEnvelope.ActualInnerLength);
var status = NmxObservedMessage.Parse(statusEnvelope.InnerBody.Span);
AssertEqual("status command", (byte)0x32, status.Command);
AssertEqual("status command name", "SubscriptionStatus", status.CommandName);
var typedStatus = NmxSubscriptionMessage.ParseProcessDataReceivedBody(statusReceived);
AssertEqual("typed status command", NmxSubscriptionMessage.SubscriptionStatusCommand, typedStatus.Command);
AssertEqual("typed status count", 1, typedStatus.RecordCount);
AssertEqual("typed status operation id", new Guid("ba144edb-25e9-4785-81e4-e0e71ad2b1aa"), typedStatus.OperationId);
AssertEqual("typed status item correlation", new Guid("cd9ccac0-6532-46b0-a585-a583b2e77a5d"), typedStatus.ItemCorrelationId!.Value);
AssertEqual("typed status record count", 1, typedStatus.Records.Count);
AssertEqual("typed status status", 3, typedStatus.Records[0].Status);
AssertEqual("typed status detail", 3, typedStatus.Records[0].DetailStatus!.Value);
AssertEqual("typed status quality", (ushort)0x00c0, typedStatus.Records[0].Quality);
AssertEqual("typed status wire kind", (byte)0x02, typedStatus.Records[0].WireKind);
AssertEqual("typed status value", null, typedStatus.Records[0].Value);
AssertEqual("typed status mx status", MxStatus.DataChangeOk, typedStatus.Records[0].ToDataChangeStatus());
byte[] dataUpdateReceived = FromHex(
"01 00 2a 00 00 00 00 00 00 00 cd 86 04 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 02 00 00 30 75 00 00 " +
"33 01 00 01 00 00 00 1c c8 68 ce e5 13 e8 48 89 87 21 91 a8 dc 42 93 03 00 00 00 c0 00 00 72 68 3a 83 d4 dc 01 02 72 00 00 00");
var dataUpdateEnvelope = NmxObservedEnvelope.ParseProcessDataReceivedBodyFlexible(dataUpdateReceived);
AssertEqual("data update has no length prefix", false, dataUpdateEnvelope.HasLengthPrefix);
AssertEqual("data update declared inner", 42, dataUpdateEnvelope.DeclaredInnerLength);
AssertEqual("data update actual inner", 42, dataUpdateEnvelope.ActualInnerLength);
var dataUpdate = NmxSubscriptionMessage.ParseProcessDataReceivedBody(dataUpdateReceived);
AssertEqual("data update command", NmxSubscriptionMessage.DataUpdateCommand, dataUpdate.Command);
AssertEqual("data update count", 1, dataUpdate.RecordCount);
AssertEqual("data update operation id", new Guid("ce68c81c-13e5-48e8-8987-2191a8dc4293"), dataUpdate.OperationId);
AssertEqual("data update item correlation", null, dataUpdate.ItemCorrelationId);
AssertEqual("data update status", 3, dataUpdate.Records[0].Status);
AssertEqual("data update detail", null, dataUpdate.Records[0].DetailStatus);
AssertEqual("data update quality", (ushort)0x00c0, dataUpdate.Records[0].Quality);
AssertEqual("data update timestamp", new DateTime(2026, 4, 25, 7, 15, 0, DateTimeKind.Utc), dataUpdate.Records[0].TimestampUtc);
AssertEqual("data update wire kind", (byte)0x02, dataUpdate.Records[0].WireKind);
AssertEqual("data update value", 114, dataUpdate.Records[0].Value!);
AssertEqual("data update mx status", MxStatus.DataChangeOk, dataUpdate.Records[0].ToDataChangeStatus());
byte[] writeCompleteReceived = FromHex(
"01 00 05 00 00 00 00 00 00 00 03 00 00 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 02 02 00 00 30 75 00 00 00 00 50 80 00");
AssertEqual(
"write complete operation parse",
true,
NmxOperationStatusMessage.TryParseProcessDataReceivedBody(writeCompleteReceived, out var writeComplete));
AssertEqual("write complete format", NmxOperationStatusFormat.StatusWord, writeComplete.Format);
AssertEqual("write complete command", (byte)0x00, writeComplete.Command);
AssertEqual("write complete status code", (ushort)0x8050, writeComplete.StatusCode);
AssertEqual("write complete completion code", (byte)0x00, writeComplete.CompletionCode);
AssertEqual("write complete mx status", MxStatus.WriteCompleteOk, writeComplete.Status);
AssertEqual("write complete mxaccess event", true, writeComplete.IsMxAccessWriteComplete);
byte[] writeWrongTypeStatusReceived = FromHex(
"33 00 00 00 01 00 05 00 00 00 00 00 00 00 03 00 00 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 f9 7f 00 00 02 02 00 00 30 75 00 00 41");
AssertEqual(
"write wrong-type status parse",
true,
NmxOperationStatusMessage.TryParseProcessDataReceivedBody(writeWrongTypeStatusReceived, out var writeWrongTypeStatus));
AssertEqual("write wrong-type format", NmxOperationStatusFormat.CompletionOnly, writeWrongTypeStatus.Format);
AssertEqual("write wrong-type status code", (ushort)0, writeWrongTypeStatus.StatusCode);
AssertEqual("write wrong-type completion code", (byte)0x41, writeWrongTypeStatus.CompletionCode);
AssertEqual("write wrong-type mx status success", (short)0, writeWrongTypeStatus.Status.Success);
AssertEqual("write wrong-type mx status detail", (short)0x41, writeWrongTypeStatus.Status.Detail);
AssertEqual("write wrong-type mxaccess event", false, writeWrongTypeStatus.IsMxAccessWriteComplete);
byte[] writeCompletionOnlyOkReceived = FromHex(
"33 00 00 00 01 00 05 00 00 00 00 00 00 00 03 00 00 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 f9 7f 00 00 02 02 00 00 30 75 00 00 00");
AssertEqual(
"write completion-only ok parse",
true,
NmxOperationStatusMessage.TryParseProcessDataReceivedBody(writeCompletionOnlyOkReceived, out var writeCompletionOnlyOk));
AssertEqual("write completion-only ok format", NmxOperationStatusFormat.CompletionOnly, writeCompletionOnlyOk.Format);
AssertEqual("write completion-only ok completion code", (byte)0x00, writeCompletionOnlyOk.CompletionCode);
AssertEqual("write completion-only ok mxaccess event", false, writeCompletionOnlyOk.IsMxAccessWriteComplete);
byte[] statusWithValueReceived = FromHex(
"01 00 3e 00 00 00 00 00 00 00 ca 86 04 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 02 00 00 30 75 00 00 " +
"32 01 00 01 00 00 00 1c c8 68 ce e5 13 e8 48 89 87 21 91 a8 dc 42 93 b0 b8 80 11 ae ae f8 40 9a c9 8b 3c 9e 79 f4 a4 03 00 00 00 03 00 00 00 c0 00 90 06 82 68 7b d4 dc 01 02 6f 00 00 00");
var statusWithValue = NmxSubscriptionMessage.ParseProcessDataReceivedBody(statusWithValueReceived);
AssertEqual("status with value command", NmxSubscriptionMessage.SubscriptionStatusCommand, statusWithValue.Command);
AssertEqual("status with value count", 1, statusWithValue.RecordCount);
AssertEqual("status with value operation id", new Guid("ce68c81c-13e5-48e8-8987-2191a8dc4293"), statusWithValue.OperationId);
AssertEqual("status with value item correlation", new Guid("1180b8b0-aeae-40f8-9ac9-8b3c9e79f4a4"), statusWithValue.ItemCorrelationId!.Value);
AssertEqual("status with value status", 3, statusWithValue.Records[0].Status);
AssertEqual("status with value detail", 3, statusWithValue.Records[0].DetailStatus!.Value);
AssertEqual("status with value quality", (ushort)0x00c0, statusWithValue.Records[0].Quality);
AssertEqual("status with value wire kind", (byte)0x02, statusWithValue.Records[0].WireKind);
AssertEqual("status with value value", 111, statusWithValue.Records[0].Value!);
byte[] multiRecordStatusReceived = FromHex(
"01 00 69 00 00 00 00 00 00 00 ef e4 08 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 02 00 00 30 75 00 00 " +
"32 01 00 02 00 00 00 13 27 1c 4c c8 d3 95 40 b9 dd 17 80 70 23 4a 9d 42 95 8e 96 10 37 0c 4a b8 f8 79 bf ac 46 b8 e4 01 00 00 00 03 00 00 00 c0 00 20 2e 5a 46 28 d3 dc 01 06 0a 00 00 00 00 a0 41 c3 55 bd dc 01 00 00 02 00 00 00 03 00 00 00 c0 00 80 18 5b 46 28 d3 dc 01 06 0a 00 00 00 80 c1 75 25 a5 bd dc 01 00 00");
var multiRecordStatus = NmxSubscriptionMessage.ParseProcessDataReceivedBody(multiRecordStatusReceived);
AssertEqual("multi status count", 2, multiRecordStatus.RecordCount);
AssertEqual("multi status parsed records", 2, multiRecordStatus.Records.Count);
AssertEqual("multi status first status", 1, multiRecordStatus.Records[0].Status);
AssertEqual("multi status second status", 2, multiRecordStatus.Records[1].Status);
AssertEqual("multi status first kind", (byte)0x06, multiRecordStatus.Records[0].WireKind);
AssertEqual("multi status second kind", (byte)0x06, multiRecordStatus.Records[1].WireKind);
AssertEqual("multi status first value type", true, multiRecordStatus.Records[0].Value is DateTime);
AssertEqual("multi status second value type", true, multiRecordStatus.Records[1].Value is DateTime);
byte[] boolUpdateReceived = FromHex(
"01 00 27 00 00 00 00 00 00 00 f9 74 04 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 02 00 00 30 75 00 00 " +
"33 01 00 01 00 00 00 88 b0 59 be 24 f0 f8 43 a7 89 b6 95 d8 11 13 9e 03 00 00 00 c0 00 f0 58 d4 21 7c d4 dc 01 01 ff");
AssertCallbackValue("bool update", boolUpdateReceived, true);
byte[] floatUpdateReceived = FromHex(
"01 00 2a 00 00 00 00 00 00 00 4d 75 04 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 02 00 00 30 75 00 00 " +
"33 01 00 01 00 00 00 e1 29 46 93 4a c7 6d 43 b8 9b ad df bc ee f8 61 03 00 00 00 c0 00 10 8f cb 38 7c d4 dc 01 03 00 00 a0 3f");
AssertCallbackValue("float update", floatUpdateReceived, 1.25f);
byte[] doubleUpdateReceived = FromHex(
"01 00 2e 00 00 00 00 00 00 00 a1 75 04 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 02 00 00 30 75 00 00 " +
"33 01 00 01 00 00 00 3b 39 15 ab 40 13 c5 4c 8d 6c b6 74 e5 9a db bf 03 00 00 00 c0 00 20 16 af 4f 7c d4 dc 01 04 00 00 00 00 00 00 f2 3f");
AssertCallbackValue("double update", doubleUpdateReceived, 1.125d);
byte[] stringUpdateReceived = FromHex(
"01 00 3e 00 00 00 00 00 00 00 f8 75 04 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 02 00 00 30 75 00 00 " +
"33 01 00 01 00 00 00 e1 40 1f 90 80 e4 7d 43 bf ab 6f a2 ea 23 97 ed 03 00 00 00 c0 00 10 5c 75 67 7c d4 dc 01 05 14 00 00 00 10 00 00 00 41 00 6c 00 70 00 68 00 61 00 4d 00 58 00 00 00");
AssertCallbackValue("string update", stringUpdateReceived, "AlphaMX");
byte[] stringStatusReceived = FromHex(
"01 00 60 00 00 00 00 00 00 00 f5 75 04 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 02 00 00 30 75 00 00 " +
"32 01 00 01 00 00 00 e1 40 1f 90 80 e4 7d 43 bf ab 6f a2 ea 23 97 ed b2 36 c6 09 ef 0d 00 43 9e 1d 5e d9 16 c8 7c cc 03 00 00 00 03 00 00 00 c0 00 b0 a0 e2 57 47 bd dc 01 05 22 00 00 00 1e 00 00 00 48 00 65 00 6c 00 6c 00 6f 00 46 00 72 00 6f 00 6d 00 4f 00 70 00 63 00 55 00 61 00 00 00");
AssertCallbackValue("string status", stringStatusReceived, "HelloFromOpcUa");
byte[] compactEmptyStringStatusReceived = FromHex(
"32 01 00 01 00 00 00 bd 26 31 07 1d d2 76 4d b4 48 29 dd a2 53 1e 29 e6 5c 35 81 5e f9 79 44 b3 5a d0 5d 9f c6 60 a8 03 00 00 00 00 00 00 00 c0 00 30 69 98 49 28 d3 dc 01 05 04 00 00 00");
var compactEmptyStringStatus = NmxSubscriptionMessage.ParseInner(compactEmptyStringStatusReceived);
AssertEqual("compact empty string kind", (byte)0x05, compactEmptyStringStatus.Records[0].WireKind);
AssertEqual("compact empty string value", string.Empty, compactEmptyStringStatus.Records[0].Value!);
byte[] elapsedTimeStatusReceived = FromHex(
"32 01 00 01 00 00 00 00 c0 ac 3a 1a 83 b4 48 88 e6 43 56 87 4b 87 97 45 d3 97 30 68 7f b9 48 b8 c4 bf cd 99 a7 a3 51 03 00 00 00 00 00 00 00 c0 00 90 5c d6 49 28 d3 dc 01 07 00 00 00 00");
var elapsedTimeStatus = NmxSubscriptionMessage.ParseInner(elapsedTimeStatusReceived);
AssertEqual("elapsed time kind", (byte)0x07, elapsedTimeStatus.Records[0].WireKind);
AssertEqual("elapsed time value", TimeSpan.Zero, elapsedTimeStatus.Records[0].Value!);
byte[] nonZeroElapsedTimeStatusReceived = FromHex(
"70 00 00 00 01 00 42 00 00 00 00 00 00 00 cd 89 05 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 02 00 00 30 75 00 00 " +
"32 01 00 01 00 00 00 80 d0 62 c4 97 13 d4 43 a0 bc 8b a5 a6 e2 69 86 7c 32 dd c9 b6 70 af 4a 89 a5 18 67 bc 51 66 71 03 00 00 00 00 00 00 00 c0 00 90 5c d6 49 28 d3 dc 01 07 00 e4 0b 54");
var nonZeroElapsedTimeStatus = NmxSubscriptionMessage.ParseProcessDataReceivedBody(nonZeroElapsedTimeStatusReceived);
AssertEqual("nonzero elapsed time kind", (byte)0x07, nonZeroElapsedTimeStatus.Records[0].WireKind);
AssertEqual("nonzero elapsed time value", TimeSpan.FromMilliseconds(0x540be400), nonZeroElapsedTimeStatus.Records[0].Value!);
byte[] intArrayUpdateReceived = FromHex(
"01 00 58 00 00 00 00 00 00 00 c0 7c 04 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 02 00 00 30 75 00 00 " +
"33 01 00 01 00 00 00 46 5a 30 af 6a 87 65 46 ac c5 4d 98 f2 e2 12 fe 03 00 00 00 c0 00 d0 ba 8f 68 7e d4 dc 01 42 00 00 00 00 0a 00 04 00 00 00 c9 00 00 00 ca 00 00 00 cb 00 00 00 cc 00 00 00 cd 00 00 00 ce 00 00 00 cf 00 00 00 d0 00 00 00 d1 00 00 00 d2 00 00 00");
AssertCallbackValue("int array update", intArrayUpdateReceived, new[] { 201, 202, 203, 204, 205, 206, 207, 208, 209, 210 });
byte[] boolArrayUpdateReceived = FromHex(
"01 00 44 00 00 00 00 00 00 00 23 7d 04 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 02 00 00 30 75 00 00 " +
"33 01 00 01 00 00 00 f6 8c cc e9 ef 7e 36 4e ab 35 e3 d0 27 51 db 55 03 00 00 00 c0 00 20 73 4c 85 7e d4 dc 01 41 00 00 00 00 0a 00 02 00 00 00 ff ff ff ff 00 00 00 00 ff ff ff ff 00 00 00 00 ff ff ff ff");
AssertCallbackValue("bool array update", boolArrayUpdateReceived, new[] { true, true, false, false, true, true, false, false, true, true });
byte[] floatArrayUpdateReceived = FromHex(
"01 00 58 00 00 00 00 00 00 00 62 7d 04 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 02 00 00 30 75 00 00 " +
"33 01 00 01 00 00 00 43 cd bf c8 a6 e1 a7 44 b7 14 99 a0 52 81 ec 43 03 00 00 00 c0 00 b0 47 0d 97 7e d4 dc 01 43 00 00 00 00 0a 00 04 00 00 00 00 00 a0 3f 00 00 20 40 00 00 70 40 00 00 88 40 00 00 b0 40 00 00 d8 40 00 00 e8 40 00 00 08 41 00 00 1c 41 00 00 24 41");
AssertCallbackValue("float array update", floatArrayUpdateReceived, new[] { 1.25f, 2.5f, 3.75f, 4.25f, 5.5f, 6.75f, 7.25f, 8.5f, 9.75f, 10.25f });
byte[] doubleArrayUpdateReceived = FromHex(
"01 00 80 00 00 00 00 00 00 00 a0 7d 04 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 02 00 00 30 75 00 00 " +
"33 01 00 01 00 00 00 eb 4d ab 7d 2e d8 02 49 b9 04 34 db b0 c9 41 06 03 00 00 00 c0 00 b0 bc cc a8 7e d4 dc 01 44 00 00 00 00 0a 00 08 00 00 00 00 00 00 00 00 00 f2 3f 00 00 00 00 00 00 02 40 00 00 00 00 00 00 0c 40 00 00 00 00 00 80 12 40 00 00 00 00 00 00 17 40 00 00 00 00 00 80 1b 40 00 00 00 00 00 00 1c 40 00 00 00 00 00 40 20 40 00 00 00 00 00 80 22 40 00 00 00 00 00 c0 24 40");
AssertCallbackValue("double array update", doubleArrayUpdateReceived, new[] { 1.125d, 2.25d, 3.5d, 4.625d, 5.75d, 6.875d, 7.0d, 8.125d, 9.25d, 10.375d });
byte[] dateTimeArrayUpdateReceived = FromHex(
"01 00 a8 00 00 00 00 00 00 00 e4 7e 04 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 02 00 00 30 75 00 00 " +
"33 01 00 01 00 00 00 c4 10 20 21 f5 14 45 42 aa 5a 7e 63 fc e1 d7 72 03 00 00 00 c0 00 40 85 bb 06 7f d4 dc 01 46 00 00 00 00 0a 00 0c 00 00 00 00 58 f7 21 81 d4 dc 01 00 00 00 00 00 9e ba 45 81 d4 dc 01 00 00 00 00 00 e4 7d 69 81 d4 dc 01 00 00 00 00 00 2a 41 8d 81 d4 dc 01 00 00 00 00 00 70 04 b1 81 d4 dc 01 00 00 00 00 00 b6 c7 d4 81 d4 dc 01 00 00 00 00 00 fc 8a f8 81 d4 dc 01 00 00 00 00 00 42 4e 1c 82 d4 dc 01 00 00 00 00 00 88 11 40 82 d4 dc 01 00 00 00 00 00 ce d4 63 82 d4 dc 01 00 00 00 00");
var dateTimeArrayUpdate = NmxSubscriptionMessage.ParseProcessDataReceivedBody(dateTimeArrayUpdateReceived);
AssertEqual("datetime array update value type", true, dateTimeArrayUpdate.Records[0].Value is DateTime[]);
AssertEqual("datetime array update count", 10, ((DateTime[])dateTimeArrayUpdate.Records[0].Value!).Length);
byte[] metadataTransfer = FromHex(
"01 00 3a 01 00 00 00 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 02 00 00 30 75 00 00 " +
"17 01 00 01 01 00 01 00 00 00 65 00 71 00 0a 00 00 00 00 00 08 6a 00 00 00 40 00 00 81 44 00 65 00 76 00 50 00 6c 00 61 00 74 00 66 00 6f 00 72 00 6d 00 2e 00 47 00 52 00 2e 00 54 00 69 00 6d 00 65 00 4f 00 66 00 4c 00 61 00 73 00 74 00 44 00 65 00 70 00 6c 00 6f 00 79 00 00 00 02 00 00 00 00 00 02 00 00 00 00 00 02 00 00 00 00 00 01 01 00 01 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 01 d0 fc 40 09 1f 01 00 c0 ca 9c cd 32 65 b0 46 a5 85 a5 83 b2 e7 7a 5d 00 00 01 00 00 00 " +
"17 01 00 01 01 00 01 00 00 00 65 00 71 00 0a 00 00 00 00 00 08 76 00 00 00 4c 00 00 81 44 00 65 00 76 00 50 00 6c 00 61 00 74 00 66 00 6f 00 72 00 6d 00 2e 00 47 00 52 00 2e 00 54 00 69 00 6d 00 65 00 4f 00 66 00 4c 00 61 00 73 00 74 00 43 00 6f 00 6e 00 66 00 69 00 67 00 43 00 68 00 61 00 6e 00 67 00 65 00 00 00 02 00 00 00 00 00 02 00 00 00 00 00 02 00 00 00 00 00 01 01 00 01 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 01 50 03 41 09 20 01 00 02 00 00 00");
var metadataEnvelope = NmxObservedEnvelope.ParseTransferDataBody(metadataTransfer);
var metadata = NmxObservedMessage.Parse(metadataEnvelope.InnerBody.Span);
AssertEqual("metadata command", (byte)0x17, metadata.Command);
AssertEqual("metadata command name", "MetadataQuery", metadata.CommandName);
AssertEqual("metadata string 1", true, metadata.Strings.Any(s => s.Value == "DevPlatform.GR.TimeOfLastDeploy"));
AssertEqual("metadata string 2", true, metadata.Strings.Any(s => s.Value == "DevPlatform.GR.TimeOfLastConfigChange"));
AssertBytes(
"observed pre-advise metadata encode",
metadataEnvelope.InnerBody.ToArray(),
NmxMetadataQueryMessage.EncodeObservedPreAdvise(new Guid("cd9ccac0-6532-46b0-a585-a583b2e77a5d")));
byte[] metadataResponseReceived = FromHex(
"C2 02 00 00 01 00 94 02 00 00 00 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 24 72 00 00 02 02 00 00 30 75 00 00 " +
"40 1F 50 80 08 A6 00 00 00 40 00 00 91 44 00 65 00 76 00 50 00 6C 00 61 00 74 00 66 00 6F 00 72 00 6D 00 2E 00 47 00 52 00 2E 00 54 00 69 00 6D 00 65 00 4F 00 66 00 4C 00 61 00 73 00 74 00 44 00 65 00 70 00 6C 00 6F 00 79 00 00 00 18 00 00 00 44 00 65 00 76 00 50 00 6C 00 61 00 74 00 66 00 6F 00 72 00 6D 00 00 00 28 00 00 00 47 00 52 00 2E 00 54 00 69 00 6D 00 65 00 4F 00 66 00 4C 00 61 00 73 00 74 00 44 00 65 00 70 00 6C 00 6F 00 79 00 00 00 02 00 00 00 00 00 01 01 00 01 00 01 00 53 F2 9A 00 6A 00 0A 00 5F F1 00 00 01 6C 00 00 00 41 00 6E 00 20 00 69 00 6E 00 74 00 65 00 72 00 6E 00 61 00 6C 00 20 00 65 00 72 00 72 00 6F 00 72 00 20 00 6F 00 63 00 63 00 75 00 72 00 72 00 65 00 64 00 20 00 69 00 6E 00 20 00 74 00 68 00 65 00 20 00 42 00 61 00 73 00 65 00 20 00 52 00 75 00 6E 00 74 00 69 00 6D 00 65 00 20 00 4F 00 62 00 6A 00 65 00 63 00 74 00 00 00 1F 00 00 50 80 01 00 01 00 01 00 30 75 00 00 D3 57 0D 95 6B 8F 7C 43 A6 09 63 4D 53 53 A2 C2 66 6B F0 7D 78 52 AC 46 82 E5 AF 0A CF 91 34 F5 40 1F 50 80 08 BE 00 00 00 4C 00 00 91 44 00 65 00 76 00 50 00 6C 00 61 00 74 00 66 00 6F 00 72 00 6D 00 2E 00 47 00 52 00 2E 00 54 00 69 00 6D 00 65 00 4F 00 66 00 4C 00 61 00 73 00 74 00 43 00 6F 00 6E 00 66 00 69 00 67 00 43 00 68 00 61 00 6E 00 67 00 65 00 00 00 18 00 00 00 44 00 65 00 76 00 50 00 6C 00 61 00 74 00 66 00 6F 00 72 00 6D 00 00 00 34 00 00 00 47 00 52 00 2E 00 54 00 69 00 6D 00 65 00 4F 00 66 00 4C 00 61 00 73 00 74 00 43 00 6F 00 6E 00 66 00 69 00 67 00 43 00 68 00 61 00 6E 00 67 00 65 00 00 00 02 00 00 00 00 00 01 01 00 01 00 01 00 53 F2 9A 00 6B 00 0A 00 87 3A 00 00 01 6C 00 00 00 41 00 6E 00 20 00 69 00 6E 00 74 00 65 00 72 00 6E 00 61 00 6C 00 20 00 65 00 72 00 72 00 6F 00 72 00 20 00 6F 00 63 00 63 00 75 00 72 00 72 00 65 00 64 00 20 00 69 00 6E 00 20 00 74 00 68 00 65 00 20 00 42 00 61 00 73 00 65 00 20 00 52 00 75 00 6E 00 74 00 69 00 6D 00 65 00 20 00 4F 00 62 00 6A 00 65 00 63 00 74 00 00 00 20 00 00 50 80 01 00 01 00 01 00");
var metadataResponse = NmxObservedMessage.Parse(metadataResponseReceived.AsSpan(46));
AssertEqual("metadata response command", (byte)0x40, metadataResponse.Command);
AssertEqual("metadata response command name", "MetadataResponse", metadataResponse.CommandName);
AssertEqual("metadata response string 1", true, metadataResponse.Strings.Any(s => s.Value == "DevPlatform.GR.TimeOfLastDeploy"));
AssertEqual("metadata response string 2", true, metadataResponse.Strings.Any(s => s.Value == "DevPlatform.GR.TimeOfLastConfigChange"));
AssertEqual("metadata response error string", true, metadataResponse.Strings.Any(s => s.Value == "An internal error occurred in the Base Runtime Object"));
}
static void RunItemControlMessageTests()
{
RunItemControlRoundTrip(
"TestInt advise",
"1f 01 00 c0 ca 9c cd 32 65 b0 46 a5 85 a5 83 b2 e7 7a 5d 00 00 05 00 36 d7 02 00 9b 00 0a 00 3e da 00 00 03 00 00 00",
NmxItemControlCommand.AdviseSupervisory,
new Guid("cd9ccac0-6532-46b0-a585-a583b2e77a5d"),
5,
0xd736,
2,
155,
10,
0xda3e,
0);
RunItemControlRoundTrip(
"TestInt unadvise",
"21 01 00 c0 ca 9c cd 32 65 b0 46 a5 85 a5 83 b2 e7 7a 5d 05 00 36 d7 02 00 9b 00 0a 00 3e da 00 00 03 00 00 00",
NmxItemControlCommand.UnAdvise,
new Guid("cd9ccac0-6532-46b0-a585-a583b2e77a5d"),
5,
0xd736,
2,
155,
10,
0xda3e,
0);
RunItemControlRoundTrip(
"TestBool advise",
"1f 01 00 46 86 88 6b e0 16 13 4f 9a 88 2b bc 8f ae 13 04 00 00 05 00 36 d7 02 00 9a 00 0a 00 fa 7d 00 00 03 00 00 00",
NmxItemControlCommand.AdviseSupervisory,
new Guid("6b888646-16e0-4f13-9a88-2bbc8fae1304"),
5,
0xd736,
2,
154,
10,
0x7dfa,
0);
RunItemControlRoundTrip(
"TestString advise",
"1f 01 00 7d 4e 8d dc 01 db 02 41 9e 5d a4 f3 44 10 c4 73 00 00 05 00 36 d7 02 00 9e 00 0a 00 1a 94 00 00 03 00 00 00",
NmxItemControlCommand.AdviseSupervisory,
new Guid("dc8d4e7d-db01-4102-9e5d-a4f34410c473"),
5,
0xd736,
2,
158,
10,
0x941a,
0);
}
static void RunMxStatusDetailTests()
{
var denied = new MxStatus(
Success: 0,
Category: MxStatusCategory.SecurityError,
DetectedBy: MxStatusSource.RespondingAutomationObject,
Detail: 33);
AssertEqual("status detail denied", "Write access denied", denied.DetailText!);
AssertEqual("status detail secured", "Secured Write", MxStatusDetails.GetKnownText(56)!);
AssertEqual("status detail conversion", "Conversion to intended data type is not supported", MxStatusDetails.GetKnownText(541)!);
AssertEqual("status detail configure classification", "Object must be offscan to modify attributes that have an MxSecurityConfigure security classification", MxStatusDetails.GetKnownText(8017)!);
AssertEqual("status detail unknown", null, MxStatusDetails.GetKnownText(999));
AssertEqual("invalid reference status success", (short)0, MxStatus.InvalidReferenceConfiguration.Success);
AssertEqual("invalid reference status category", MxStatusCategory.ConfigurationError, MxStatus.InvalidReferenceConfiguration.Category);
AssertEqual("invalid reference status source", MxStatusSource.RequestingLmx, MxStatus.InvalidReferenceConfiguration.DetectedBy);
AssertEqual("invalid reference status detail", (short)6, MxStatus.InvalidReferenceConfiguration.Detail);
}
static void RunMxDataTypeSupportTests()
{
AssertEqual("int type kind supported", true, NmxWriteMessage.TryGetValueKind((short)MxDataType.Integer, isArray: false, out var intKind));
AssertEqual("int type kind", MxValueKind.Int32, intKind);
AssertEqual("int array type kind supported", true, NmxWriteMessage.TryGetValueKind((short)MxDataType.Integer, isArray: true, out var intArrayKind));
AssertEqual("int array type kind", MxValueKind.Int32Array, intArrayKind);
AssertEqual("elapsed unsupported", false, NmxWriteMessage.TryGetValueKind((short)MxDataType.ElapsedTime, isArray: false, out _));
AssertEqual("internationalized string unsupported", false, NmxWriteMessage.TryGetValueKind((short)MxDataType.InternationalizedString, isArray: false, out _));
}
static void RunMxReferenceHandleTests()
{
byte[] observedHandle = FromHex("01 00 01 00 02 00 05 00 36 d7 02 00 9b 00 0a 00 3e da 00 00");
var handle = MxReferenceHandle.Parse(observedHandle);
AssertEqual("handle platform", (ushort)1, handle.PlatformId);
AssertEqual("handle engine", (ushort)2, handle.EngineId);
AssertEqual("handle object id", (ushort)5, handle.ObjectId);
AssertEqual("handle object signature", (ushort)0xd736, handle.ObjectSignature);
AssertEqual("handle primitive id", (short)2, handle.PrimitiveId);
AssertEqual("handle attribute id", (short)155, handle.AttributeId);
AssertEqual("handle property id", (short)10, handle.PropertyId);
AssertEqual("handle attribute signature", (ushort)0xda3e, handle.AttributeSignature);
AssertEqual("handle attribute index", (short)0, handle.AttributeIndex);
AssertBytes("handle encode", observedHandle, handle.Encode());
AssertEqual("object signature crc", (ushort)0xd736, MxReferenceHandle.ComputeNameSignature("TestChildObject"));
AssertEqual("int signature crc", (ushort)0xda3e, MxReferenceHandle.ComputeNameSignature("TestInt"));
AssertEqual("bool signature crc", (ushort)0x7dfa, MxReferenceHandle.ComputeNameSignature("TestBool"));
AssertEqual("string signature crc", (ushort)0x941a, MxReferenceHandle.ComputeNameSignature("TestString"));
AssertEqual("int array signature crc", (ushort)0x5760, MxReferenceHandle.ComputeNameSignature("TestIntArray"));
var synthesized = MxReferenceHandle.Create(
galaxyId: 1,
platformId: 1,
engineId: 2,
objectId: 5,
objectTagName: "TestChildObject",
primitiveId: 2,
attributeId: 155,
propertyId: 10,
attributeName: "TestInt",
isArray: false);
AssertEqual("synthesized handle", handle, synthesized);
var advise = NmxItemControlMessage.FromReferenceHandle(
NmxItemControlCommand.AdviseSupervisory,
new Guid("e56c1aff-d3a3-4a1d-921f-f12e18f0d95d"),
handle);
AssertEqual("handle to advise object id", handle.ObjectId, advise.ObjectId);
AssertEqual("handle to advise object signature", handle.ObjectSignature, advise.ObjectSignature);
AssertEqual("handle to advise primitive", handle.PrimitiveId, advise.PrimitiveId);
AssertEqual("handle to advise attr id", handle.AttributeId, advise.AttributeId);
AssertEqual("handle to advise property id", handle.PropertyId, advise.PropertyId);
AssertEqual("handle to advise attr signature", handle.AttributeSignature, advise.AttributeSignature);
AssertEqual("handle to advise attr index", handle.AttributeIndex, advise.AttributeIndex);
AssertEqual("advise to handle", handle, advise.ToReferenceHandle(engineId: 2));
AssertEqual("GR bool kind", MxValueKind.Boolean, NmxWriteMessage.GetValueKind(1, isArray: false));
AssertEqual("GR int kind", MxValueKind.Int32, NmxWriteMessage.GetValueKind(2, isArray: false));
AssertEqual("GR float kind", MxValueKind.Float32, NmxWriteMessage.GetValueKind(3, isArray: false));
AssertEqual("GR double kind", MxValueKind.Float64, NmxWriteMessage.GetValueKind(4, isArray: false));
AssertEqual("GR string kind", MxValueKind.String, NmxWriteMessage.GetValueKind(5, isArray: false));
AssertEqual("GR datetime kind", MxValueKind.DateTime, NmxWriteMessage.GetValueKind(6, isArray: false));
AssertEqual("GR int array kind", MxValueKind.Int32Array, NmxWriteMessage.GetValueKind(2, isArray: true));
}
static void RunGeneratedTransferEnvelopeTests()
{
byte[] inner = FromHex(
"1f 01 00 c0 ca 9c cd 32 65 b0 46 a5 85 a5 83 b2 e7 7a 5d 00 00 05 00 36 d7 02 00 9b 00 0a 00 3e da 00 00 03 00 00 00");
byte[] expected = FromHex(
"01 00 27 00 00 00 00 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 00 00 00 01 00 00 00 02 00 00 00 01 02 00 00 30 75 00 00 " +
"1f 01 00 c0 ca 9c cd 32 65 b0 46 a5 85 a5 83 b2 e7 7a 5d 00 00 05 00 36 d7 02 00 9b 00 0a 00 3e da 00 00 03 00 00 00");
byte[] generated = NmxTransferEnvelope.Encode(
NmxTransferMessageKind.ItemControl,
localEngineId: 0x7ffb,
targetGalaxyId: 1,
targetPlatformId: 1,
targetEngineId: 2,
inner);
AssertBytes("generated item-control transfer", expected, generated);
var parsed = NmxTransferEnvelope.Parse(generated);
AssertEqual("transfer kind", NmxTransferMessageKind.ItemControl, parsed.MessageKind);
AssertEqual("transfer source galaxy", 1, parsed.SourceGalaxyId);
AssertEqual("transfer source platform", 1, parsed.SourcePlatformId);
AssertEqual("transfer local engine", 0x7ffb, parsed.LocalEngineId);
AssertEqual("transfer target galaxy", 1, parsed.TargetGalaxyId);
AssertEqual("transfer target platform", 1, parsed.TargetPlatformId);
AssertEqual("transfer target engine", 2, parsed.TargetEngineId);
AssertEqual("transfer timeout", 30000, parsed.TimeoutMilliseconds);
AssertBytes("transfer inner", inner, parsed.InnerBody.Span);
byte[] unadviseInner = FromHex(
"21 01 00 c0 ca 9c cd 32 65 b0 46 a5 85 a5 83 b2 e7 7a 5d 05 00 36 d7 02 00 9b 00 0a 00 3e da 00 00 03 00 00 00");
byte[] expectedUnadvise = FromHex(
"01 00 25 00 00 00 00 00 00 00 03 00 00 00 01 00 00 00 01 00 00 00 fb 7f 00 00 01 00 00 00 01 00 00 00 02 00 00 00 01 02 00 00 30 75 00 00 " +
"21 01 00 c0 ca 9c cd 32 65 b0 46 a5 85 a5 83 b2 e7 7a 5d 05 00 36 d7 02 00 9b 00 0a 00 3e da 00 00 03 00 00 00");
AssertBytes(
"generated unadvise transfer",
expectedUnadvise,
NmxTransferEnvelope.Encode(
NmxTransferMessageKind.Write,
localEngineId: 0x7ffb,
targetGalaxyId: 1,
targetPlatformId: 1,
targetEngineId: 2,
unadviseInner));
}
static void RunReferenceRegistrationMessageTests()
{
byte[] observedNormal = FromHex(
"10 01 00 02 00 00 00 35 bd 0b 74 63 e8 54 41 ad 3a a0 68 61 a6 f9 9c ff ff 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 00 00 81 54 00 65 00 73 00 74 00 49 00 6e 00 74 00 00 00 00 00 00 00 00 00 00 00 20 00 00 00 54 00 65 00 73 00 74 00 43 00 68 00 69 00 6c 00 64 00 4f 00 62 00 6a 00 65 00 63 00 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01");
var normal = NmxReferenceRegistrationMessage.Parse(observedNormal);
AssertEqual("reference registration handle", 2, normal.ItemHandle);
AssertEqual("reference registration guid", new Guid("740bbd35-e863-4154-ad3a-a06861a6f99c"), normal.ItemCorrelationId);
AssertEqual("reference registration item", "TestInt", normal.ItemDefinition);
AssertEqual("reference registration context", "TestChildObject", normal.ItemContext);
AssertEqual("reference registration subscribe", true, normal.Subscribe);
AssertBytes("reference registration encode", observedNormal, normal.Encode());
byte[] observedBuffered = FromHex(
"10 01 00 01 00 00 00 90 29 63 fc 46 69 16 4d bc 65 19 f6 d3 06 07 37 ff ff 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 00 00 81 54 00 65 00 73 00 74 00 49 00 6e 00 74 00 2e 00 70 00 72 00 6f 00 70 00 65 00 72 00 74 00 79 00 28 00 62 00 75 00 66 00 66 00 65 00 72 00 29 00 00 00 00 00 00 00 00 00 00 00 20 00 00 00 54 00 65 00 73 00 74 00 43 00 68 00 69 00 6c 00 64 00 4f 00 62 00 6a 00 65 00 63 00 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01");
var buffered = NmxReferenceRegistrationMessage.Parse(observedBuffered);
AssertEqual("buffered reference registration handle", 1, buffered.ItemHandle);
AssertEqual("buffered reference registration guid", new Guid("fc632990-6946-4d16-bc65-19f6d3060737"), buffered.ItemCorrelationId);
AssertEqual("buffered reference registration item", "TestInt.property(buffer)", buffered.ItemDefinition);
AssertEqual("buffered reference registration context", "TestChildObject", buffered.ItemContext);
AssertEqual("buffered helper", "TestInt.property(buffer)", NmxReferenceRegistrationMessage.ToBufferedItemDefinition("TestInt"));
AssertBytes("buffered reference registration encode", observedBuffered, buffered.Encode());
byte[] observedBufferedWithContext = FromHex(
"10 01 00 01 00 00 00 66 9b 96 d6 a8 42 20 4b 9e 9b dc 3e 2b 84 4e 21 ff ff 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 44 00 00 81 54 00 65 00 73 00 74 00 48 00 69 00 73 00 74 00 6f 00 72 00 79 00 56 00 61 00 6c 00 75 00 65 00 2e 00 70 00 72 00 6f 00 70 00 65 00 72 00 74 00 79 00 28 00 62 00 75 00 66 00 66 00 65 00 72 00 29 00 00 00 00 00 00 00 00 00 00 00 20 00 00 00 54 00 65 00 73 00 74 00 4d 00 61 00 63 00 68 00 69 00 6e 00 65 00 5f 00 30 00 30 00 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01");
var bufferedWithContext = NmxReferenceRegistrationMessage.Parse(observedBufferedWithContext);
AssertEqual("context buffered registration handle", 1, bufferedWithContext.ItemHandle);
AssertEqual("context buffered registration guid", new Guid("d6969b66-42a8-4b20-9e9b-dc3e2b844e21"), bufferedWithContext.ItemCorrelationId);
AssertEqual("context buffered registration item", "TestHistoryValue.property(buffer)", bufferedWithContext.ItemDefinition);
AssertEqual("context buffered registration context", "TestMachine_001", bufferedWithContext.ItemContext);
AssertBytes("context buffered registration encode", observedBufferedWithContext, bufferedWithContext.Encode());
byte[] transfer = NmxTransferEnvelope.Encode(
NmxTransferMessageKind.ItemControl,
localEngineId: 0x7ff6,
targetGalaxyId: 1,
targetPlatformId: 1,
targetEngineId: 2,
observedBuffered);
AssertEqual("buffered registration transfer kind", NmxTransferMessageKind.ItemControl, NmxTransferEnvelope.Parse(transfer).MessageKind);
byte[] observedNormalResult = FromHex(
"11 01 00 02 00 00 00 35 bd 0b 74 63 e8 54 41 ad 3a a0 68 61 a6 f9 9c 00 a0 41 c3 55 bd dc 01 80 c1 75 25 a5 bd dc 01 01 08 56 00 00 00 10 00 00 81 54 00 65 00 73 00 74 00 49 00 6e 00 74 00 00 00 02 00 00 00 00 00 00 00 00 00 20 00 00 00 54 00 65 00 73 00 74 00 43 00 68 00 69 00 6c 00 64 00 4f 00 62 00 6a 00 65 00 63 00 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00");
var normalResult = NmxReferenceRegistrationResultMessage.Parse(observedNormalResult);
AssertEqual("normal result handle", 2, normalResult.ItemHandle);
AssertEqual("normal result item", "TestInt", normalResult.ItemDefinition);
AssertEqual("normal result type", 2, normalResult.MxDataType);
AssertEqual("normal result context", "TestChildObject", normalResult.ItemContext);
AssertEqual("normal result status category byte", (byte)1, normalResult.StatusCategory);
AssertEqual("normal result status detail byte", (byte)8, normalResult.StatusDetail);
byte[] observedBufferedResult = FromHex(
"11 01 00 01 00 00 00 90 29 63 fc 46 69 16 4d bc 65 19 f6 d3 06 07 37 00 a0 41 c3 55 bd dc 01 80 c1 75 25 a5 bd dc 01 01 08 78 00 00 00 32 00 00 81 54 00 65 00 73 00 74 00 49 00 6e 00 74 00 2e 00 70 00 72 00 6f 00 70 00 65 00 72 00 74 00 79 00 28 00 62 00 75 00 66 00 66 00 65 00 72 00 29 00 00 00 02 00 00 00 00 00 00 00 00 00 20 00 00 00 54 00 65 00 73 00 74 00 43 00 68 00 69 00 6c 00 64 00 4f 00 62 00 6a 00 65 00 63 00 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00");
var bufferedResult = NmxReferenceRegistrationResultMessage.Parse(observedBufferedResult);
AssertEqual("buffered result handle", 1, bufferedResult.ItemHandle);
AssertEqual("buffered result item", "TestInt.property(buffer)", bufferedResult.ItemDefinition);
AssertEqual("buffered result type", 2, bufferedResult.MxDataType);
AssertEqual("buffered result context", "TestChildObject", bufferedResult.ItemContext);
byte[] observedBufferedContextResult = FromHex(
"11 01 00 01 00 00 00 66 9b 96 d6 a8 42 20 4b 9e 9b dc 3e 2b 84 4e 21 00 a0 41 c3 55 bd dc 01 80 c1 75 25 a5 bd dc 01 01 08 8a 00 00 00 44 00 00 81 54 00 65 00 73 00 74 00 48 00 69 00 73 00 74 00 6f 00 72 00 79 00 56 00 61 00 6c 00 75 00 65 00 2e 00 70 00 72 00 6f 00 70 00 65 00 72 00 74 00 79 00 28 00 62 00 75 00 66 00 66 00 65 00 72 00 29 00 00 00 02 00 00 00 00 00 00 00 00 00 20 00 00 00 54 00 65 00 73 00 74 00 4d 00 61 00 63 00 68 00 69 00 6e 00 65 00 5f 00 30 00 30 00 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00");
var bufferedContextResult = NmxReferenceRegistrationResultMessage.Parse(observedBufferedContextResult);
AssertEqual("context buffered result handle", 1, bufferedContextResult.ItemHandle);
AssertEqual("context buffered result item", "TestHistoryValue.property(buffer)", bufferedContextResult.ItemDefinition);
AssertEqual("context buffered result type", 2, bufferedContextResult.MxDataType);
AssertEqual("context buffered result context", "TestMachine_001", bufferedContextResult.ItemContext);
}
static void RunProtectedWriteGenerationTests()
{
byte[] securedWrite = FromHex(
"37 01 00 06 00 08 f4 02 00 a6 00 0a 00 bb 67 00 00 01 ff ff ff 00 00 00 00 00 00 00 00 a1 fa d6 08 01 00 00 00");
var securedHandle = HandleFromObservedWriteBody(securedWrite);
AssertEqual("secured object signature", (ushort)0xf408, securedHandle.ObjectSignature);
AssertEqual("secured attr signature", (ushort)0x67bb, securedHandle.AttributeSignature);
AssertEqual("secured object signature crc", securedHandle.ObjectSignature, MxReferenceHandle.ComputeNameSignature("TestMachine_001"));
AssertEqual("secured attr signature crc", securedHandle.AttributeSignature, MxReferenceHandle.ComputeNameSignature("ProtectedValue"));
AssertBytes(
"secured generated bool write",
securedWrite,
NmxWriteMessage.Encode(
securedHandle,
MxValueKind.Boolean,
true,
writeIndex: 1,
clientToken: BinaryPrimitives.ReadUInt32LittleEndian(securedWrite.AsSpan(securedWrite.Length - 8, sizeof(uint)))));
byte[] verifiedWrite = FromHex(
"37 01 00 06 00 08 f4 02 00 a7 00 0a 00 26 8a 00 00 01 ff ff ff 00 00 00 00 00 00 00 00 67 64 d7 08 01 00 00 00");
var verifiedHandle = HandleFromObservedWriteBody(verifiedWrite);
AssertEqual("verified object signature", (ushort)0xf408, verifiedHandle.ObjectSignature);
AssertEqual("verified attr signature", (ushort)0x8a26, verifiedHandle.AttributeSignature);
AssertEqual("verified attr signature crc", verifiedHandle.AttributeSignature, MxReferenceHandle.ComputeNameSignature("ProtectedValue1"));
AssertBytes(
"verified generated bool write",
verifiedWrite,
NmxWriteMessage.Encode(
verifiedHandle,
MxValueKind.Boolean,
true,
writeIndex: 1,
clientToken: BinaryPrimitives.ReadUInt32LittleEndian(verifiedWrite.AsSpan(verifiedWrite.Length - 8, sizeof(uint)))));
}
static void RunSecuredWrite2GenerationTests()
{
byte[] secured = FromHex(
"38 01 00 06 00 08 f4 02 00 a6 00 0a 00 bb 67 00 00 01 ff 00 00 00 f6 bf e8 1b d5 dc 01 07 b9 a9 f4 72 6e ae 48 83 b5 bb de 91 8c 89 0f 7e 00 00 00 4d 00 78 00 46 00 72 00 69 00 64 00 61 00 54 00 72 00 61 00 63 00 65 00 2d 00 31 00 31 00 35 00 2d 00 66 00 72 00 69 00 64 00 61 00 2d 00 77 00 72 00 69 00 74 00 65 00 2d 00 73 00 65 00 63 00 75 00 72 00 65 00 64 00 32 00 2d 00 61 00 75 00 74 00 68 00 2d 00 70 00 72 00 6f 00 74 00 65 00 63 00 74 00 65 00 64 00 76 00 61 00 6c 00 75 00 65 00 2d 00 74 00 72 00 75 00 65 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ff 92 f4 cc 0c 01 00 00 00");
AssertSecuredWrite2(
"secured WriteSecured2 bool",
secured,
MxValueKind.Boolean,
true,
"MxFridaTrace-115-frida-write-secured2-auth-protectedvalue-true",
verifierToken: new byte[NmxSecuredWrite2Message.AuthenticatorTokenLength]);
byte[] verified = FromHex(
"38 01 00 06 00 08 f4 02 00 a7 00 0a 00 26 8a 00 00 01 ff 00 00 00 41 76 50 1c d5 dc 01 07 b9 a9 f4 72 6e ae 48 83 b5 bb de 91 8c 89 0f 88 00 00 00 4d 00 78 00 46 00 72 00 69 00 64 00 61 00 54 00 72 00 61 00 63 00 65 00 2d 00 31 00 31 00 36 00 2d 00 66 00 72 00 69 00 64 00 61 00 2d 00 77 00 72 00 69 00 74 00 65 00 2d 00 73 00 65 00 63 00 75 00 72 00 65 00 64 00 32 00 2d 00 61 00 75 00 74 00 68 00 2d 00 76 00 65 00 72 00 69 00 66 00 69 00 65 00 64 00 2d 00 70 00 72 00 6f 00 74 00 65 00 63 00 74 00 65 00 64 00 76 00 61 00 6c 00 75 00 65 00 31 00 00 00 07 b9 a9 f4 72 6e ae 48 83 b5 bb de 91 8c 89 0f ff ff 1d 9d cf 0c 01 00 00 00");
AssertSecuredWrite2(
"verified WriteSecured2 bool",
verified,
MxValueKind.Boolean,
true,
"MxFridaTrace-116-frida-write-secured2-auth-verified-protectedvalue1",
verifierToken: NmxSecuredWrite2Message.ObservedAuthenticatedUserToken);
byte[] securedInt = FromHex(
"38 01 00 05 00 36 d7 02 00 9b 00 0a 00 3e da 00 00 02 09 03 00 00 00 00 80 8d 64 10 1f d5 dc 01 07 b9 a9 f4 72 6e ae 48 83 b5 bb de 91 8c 89 0f 66 00 00 00 4d 00 78 00 46 00 72 00 69 00 64 00 61 00 54 00 72 00 61 00 63 00 65 00 2d 00 31 00 31 00 37 00 2d 00 66 00 72 00 69 00 64 00 61 00 2d 00 77 00 72 00 69 00 74 00 65 00 2d 00 73 00 65 00 63 00 75 00 72 00 65 00 64 00 32 00 2d 00 61 00 75 00 74 00 68 00 2d 00 74 00 65 00 73 00 74 00 69 00 6e 00 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ff 2a a4 e1 0c 01 00 00 00");
AssertSecuredWrite2(
"secured WriteSecured2 int",
securedInt,
MxValueKind.Int32,
777,
"MxFridaTrace-117-frida-write-secured2-auth-testint",
verifierToken: new byte[NmxSecuredWrite2Message.AuthenticatorTokenLength]);
}
static void AssertSecuredWrite2(
string name,
byte[] observed,
MxValueKind valueKind,
object value,
string clientName,
ReadOnlySpan<byte> verifierToken)
{
var handle = HandleFromObservedWriteBody(observed);
int prefixLength = valueKind == MxValueKind.Boolean ? 21 : 24;
long fileTime = BinaryPrimitives.ReadInt64LittleEndian(observed.AsSpan(prefixLength, sizeof(long)));
uint clientToken = BinaryPrimitives.ReadUInt32LittleEndian(observed.AsSpan(observed.Length - 8, sizeof(uint)));
int writeIndex = BinaryPrimitives.ReadInt32LittleEndian(observed.AsSpan(observed.Length - sizeof(int), sizeof(int)));
byte[] generated = NmxSecuredWrite2Message.Encode(
handle,
valueKind,
value,
DateTime.FromFileTime(fileTime),
clientName,
NmxSecuredWrite2Message.ObservedAuthenticatedUserToken,
verifierToken,
writeIndex,
clientToken);
AssertBytes(name, observed, generated);
}
static void RunItemControlRoundTrip(
string name,
string hex,
NmxItemControlCommand command,
Guid correlationId,
ushort objectId,
ushort objectSignature,
short primitiveId,
short attributeId,
short propertyId,
ushort attributeSignature,
short attributeIndex)
{
byte[] observed = FromHex(hex);
var parsed = NmxItemControlMessage.Parse(observed);
AssertEqual(name + " command", command, parsed.Command);
AssertEqual(name + " correlation", correlationId, parsed.ItemCorrelationId);
AssertEqual(name + " object id", objectId, parsed.ObjectId);
AssertEqual(name + " object signature", objectSignature, parsed.ObjectSignature);
AssertEqual(name + " primitive id", primitiveId, parsed.PrimitiveId);
AssertEqual(name + " attribute id", attributeId, parsed.AttributeId);
AssertEqual(name + " property id", propertyId, parsed.PropertyId);
AssertEqual(name + " attribute signature", attributeSignature, parsed.AttributeSignature);
AssertEqual(name + " attribute index", attributeIndex, parsed.AttributeIndex);
AssertEqual(name + " tail", 3u, parsed.Tail);
AssertBytes(name + " encode", observed, parsed.Encode());
}
static void AssertCallbackValue(string name, byte[] processDataBody, object expected)
{
var callback = NmxSubscriptionMessage.ParseProcessDataReceivedBody(processDataBody);
AssertEqual(name + " record count", 1, callback.Records.Count);
AssertEqual(name + " quality", (ushort)0x00c0, callback.Records[0].Quality);
AssertValue(name + " value", expected, callback.Records[0].Value!);
}
static byte[] FromHex(string hex)
{
string[] parts = hex.Split(' ', StringSplitOptions.RemoveEmptyEntries);
byte[] bytes = new byte[parts.Length];
for (int i = 0; i < parts.Length; i++)
{
bytes[i] = Convert.ToByte(parts[i], 16);
}
return bytes;
}
static MxReferenceHandle HandleFromObservedWriteBody(ReadOnlySpan<byte> body)
{
return new MxReferenceHandle(
GalaxyId: 1,
PlatformId: 1,
EngineId: 2,
ObjectId: BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(3, sizeof(ushort))),
ObjectSignature: BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(5, sizeof(ushort))),
PrimitiveId: BinaryPrimitives.ReadInt16LittleEndian(body.Slice(7, sizeof(short))),
AttributeId: BinaryPrimitives.ReadInt16LittleEndian(body.Slice(9, sizeof(short))),
PropertyId: BinaryPrimitives.ReadInt16LittleEndian(body.Slice(11, sizeof(short))),
AttributeSignature: BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(13, sizeof(ushort))),
AttributeIndex: BinaryPrimitives.ReadInt16LittleEndian(body.Slice(15, sizeof(short))));
}
static void AssertValue(string name, object expected, object actual)
{
bool equals = expected switch
{
float f => actual is float a && Math.Abs(f - a) < 0.0001f,
double d => actual is double a && Math.Abs(d - a) < 0.0000001d,
DateTime d => actual is DateTime a && d == a,
bool[] expectedValues => actual is bool[] actualValues && expectedValues.SequenceEqual(actualValues),
int[] expectedValues => actual is int[] actualValues && expectedValues.SequenceEqual(actualValues),
float[] expectedValues => actual is float[] actualValues && expectedValues.Zip(actualValues, static (e, a) => Math.Abs(e - a) < 0.0001f).All(static x => x),
double[] expectedValues => actual is double[] actualValues && expectedValues.Zip(actualValues, static (e, a) => Math.Abs(e - a) < 0.0000001d).All(static x => x),
string[] expectedValues => actual is string[] actualValues && expectedValues.SequenceEqual(actualValues),
DateTime[] expectedValues => actual is DateTime[] actualValues && expectedValues.SequenceEqual(actualValues),
_ => Equals(expected, actual),
};
if (!equals)
{
throw new InvalidOperationException($"{name}: expected decoded value {expected}, got {actual}.");
}
}
static void AssertEqual<T>(string name, T expected, T actual)
{
if (!EqualityComparer<T>.Default.Equals(expected, actual))
{
throw new InvalidOperationException($"{name}: expected {expected}, got {actual}.");
}
}
static void AssertBytes(string name, ReadOnlySpan<byte> expected, ReadOnlySpan<byte> actual)
{
if (!expected.SequenceEqual(actual))
{
int diff = 0;
while (diff < expected.Length && diff < actual.Length && expected[diff] == actual[diff])
{
diff++;
}
string expectedWindow = Convert.ToHexString(expected.Slice(diff, Math.Min(24, expected.Length - diff))).ToLowerInvariant();
string actualWindow = Convert.ToHexString(actual.Slice(diff, Math.Min(24, actual.Length - diff))).ToLowerInvariant();
throw new InvalidOperationException($"{name}: encoded bytes do not match observed bytes at offset {diff}; expected length {expected.Length}, actual length {actual.Length}; expected {expectedWindow}; actual {actualWindow}.");
}
}