Initial project state: .NET reference, design, Rust port (M0+M1), evidence
rust / build / test / clippy / fmt (push) Has been cancelled
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:
@@ -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>
|
||||
@@ -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}.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user