Skip to content

Commit 3a7564d

Browse files
committed
N5.30 MMS Listener Skeleton Profile Foundation.
1 parent aecd4df commit 3a7564d

6 files changed

Lines changed: 139 additions & 10 deletions

File tree

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,5 +233,11 @@ Generate the first server-side virtual IED evidence profile:
233233
dotnet run --project .\apps\AR.Iec61850.Cli -- mms-server-readonly-profile --steps 5 --output .\.artifacts\out\mms-server-readonly.md --json .\.artifacts\out\mms-server-readonly.json
234234
```
235235

236+
MMS listener skeleton self-probe:
237+
238+
```powershell
239+
dotnet run --project .\apps\AR.Iec61850.Cli -- mms-listener-skeleton-profile --port 0 --output .\.artifacts\out\mms-listener-skeleton.md --json .\.artifacts\out\mms-listener-skeleton.json
240+
```
241+
236242
This is an offline alpha server model. It validates logical-device directory, logical-node directory, point reads, DataSet reads, RCB exposure, and read-only write rejection before a live TCP/MMS listener is added.
237243

ROADMAP.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ ARIEC61850 is a clean-room Apache-2 IEC 61850 engineering stack for .NET. The pr
44

55
## Current source milestone
66

7+
### N5.30 - MMS listener skeleton profile foundation
8+
9+
- Added `MmsReadOnlyListenerSkeleton`, a loopback TCP listener harness that exercises the server-side request lifecycle before the live MMS PDU decoder is attached.
10+
- Added `MmsReadOnlyListenerSkeletonProfile`, a typed evidence contract for bound endpoint, accepted connection count, request count, response status, write-guard verification, diagnostics, and probe steps.
11+
- Added `mms-listener-skeleton-profile`, a CLI command that starts an ephemeral loopback listener, runs deterministic read-only probes, and exports Markdown/JSON evidence.
12+
- Added deterministic unit tests for listener self-probe, invalid target handling, write guard verification, and Markdown evidence.
13+
- Kept the scope explicit: this is a transport/session skeleton and JSON-line probe harness, not a full MMS PDU listener yet.
14+
15+
Validation command for the new milestone:
16+
17+
```powershell
18+
dotnet run --project .\apps\AR.Iec61850.Cli -- mms-listener-skeleton-profile --port 0 --output .\.artifacts\out\mms-listener-skeleton.md --json .\.artifacts\out\mms-listener-skeleton.json
19+
```
20+
721

822
### N5.28 - Sampled Values diagnostics profile foundation
923

@@ -112,13 +126,14 @@ A public release is allowed only when these gates are true:
112126
- Add stream registry, sample counter continuity, sample-rate detection, ASDU/layout checks, RMS, phasor, jitter/dropout, and PTP-correlation hooks.
113127
- Keep the current SV publisher/injector as a test harness for the analyzer.
114128

115-
### N5.30 - Read-only MMS server alpha
129+
### N5.31 - TPKT/COTP/ACSE/MMS read-only listener alpha
116130

117-
- Add TCP/TPKT/COTP/ACSE/MMS accept path.
118-
- Serve domains, variables, access attributes, and named variable lists from a simulator model.
131+
- Attach TPKT framing to the listener skeleton.
132+
- Add COTP connection confirm and ACSE associate response.
133+
- Add MMS initiate response and confirmed read-directory/read request dispatch.
119134
- Keep write/control disabled until read-only discovery and report readback are stable.
120135

121-
### N5.31 - Simulator bridge
136+
### N5.32 - Simulator bridge
122137

123138
- Map simulator profiles into MMS server model, GOOSE publisher profiles, and SV publisher profiles.
124139
- Add scenario scheduler for value changes, quality changes, timestamp faults, report triggers, GOOSE transitions, and SV disturbances.

apps/AR.Iec61850.Cli/Program.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public static async Task<int> RunAsync(string[] args)
5151
"mms-engine-profile" => await MmsEngineProfileAsync(args[1..]).ConfigureAwait(false),
5252
"mms-report-readiness-profile" => await MmsReportReadinessProfileAsync(args[1..]).ConfigureAwait(false),
5353
"mms-server-readonly-profile" => MmsServerReadOnlyProfile(args[1..]),
54+
"mms-listener-skeleton-profile" => await MmsListenerSkeletonProfileAsync(args[1..]).ConfigureAwait(false),
5455
"mms-directory" => await MmsDirectoryAsync(args[1..]).ConfigureAwait(false),
5556
"mms-model-discover" => await MmsModelDiscoverAsync(args[1..]).ConfigureAwait(false),
5657
"mms-scl-export" => await MmsSclExportAsync(args[1..]).ConfigureAwait(false),
@@ -545,6 +546,78 @@ private static int MmsServerReadOnlyProfile(string[] args)
545546
return profile.IsReady && read.IsSuccess && dataSet.IsSuccess && !writeReject.IsSuccess ? 0 : 3;
546547
}
547548

549+
private static async Task<int> MmsListenerSkeletonProfileAsync(string[] args)
550+
{
551+
var options = CliOptions.Parse(args);
552+
var port = options.GetInt("port", 0);
553+
if (port is < 0 or > 65535)
554+
throw new ArgumentException("--port must be 0..65535. Use 0 for an ephemeral loopback port.");
555+
556+
var host = options.Get("host", "127.0.0.1");
557+
var steps = options.GetInt("steps", 0);
558+
var timeoutMs = options.GetInt("timeout-ms", 5000);
559+
if (timeoutMs <= 0)
560+
throw new ArgumentException("--timeout-ms must be greater than 0.");
561+
562+
var profileName = options.Get("name", "ARIEC61850 Virtual IED");
563+
var simulatorProfile = IedSimulatorProfile.CreateDefaultFeederProfile();
564+
var engine = new IedSimulatorEngine(simulatorProfile);
565+
var now = DateTimeOffset.UtcNow;
566+
for (var i = 0; i < steps; i++)
567+
engine.Step(now.AddMilliseconds(i * 20));
568+
569+
var snapshot = engine.CreateSnapshot(DateTimeOffset.UtcNow);
570+
var serverProfile = new MmsReadOnlyServerModelBuilder().Build(
571+
simulatorProfile,
572+
snapshot,
573+
new MmsReadOnlyServerProfileOptions
574+
{
575+
ServerName = profileName,
576+
Port = port == 0 ? 102 : port,
577+
IncludeSelfTest = true
578+
});
579+
580+
var listener = new MmsReadOnlyListenerSkeleton(serverProfile);
581+
var profile = await listener.RunSelfProbeAsync(new MmsReadOnlyListenerSkeletonOptions
582+
{
583+
Host = host,
584+
Port = port,
585+
ProbeTimeoutMilliseconds = timeoutMs
586+
}).ConfigureAwait(false);
587+
588+
Console.WriteLine(profile.Summary);
589+
Console.WriteLine("Mode: read-only TCP listener skeleton (loopback self-probe; JSON-line harness; no live MMS PDU decoder yet)." );
590+
Console.WriteLine($"Bound endpoint: {profile.Host}:{profile.BoundPort}");
591+
Console.WriteLine();
592+
Console.WriteLine("Probe steps:");
593+
foreach (var step in profile.ProbeSteps)
594+
Console.WriteLine($" {(step.IsTransportSuccess ? "OK" : "FAIL")} {step.Operation} {TextOrDash(step.Target)} serverSuccess={step.IsServerSuccess.ToString().ToLowerInvariant()} - {step.Message}");
595+
596+
if (profile.Diagnostics.Count > 0)
597+
{
598+
Console.WriteLine();
599+
Console.WriteLine("Diagnostics:");
600+
foreach (var diagnostic in profile.Diagnostics)
601+
Console.WriteLine($" {diagnostic.Severity} {diagnostic.Code}: {diagnostic.Message}");
602+
}
603+
604+
if (options.TryGet("output", out var markdownPath) && !string.IsNullOrWhiteSpace(markdownPath))
605+
{
606+
EnsureOutputDirectory(markdownPath);
607+
File.WriteAllText(markdownPath, profile.ToMarkdown());
608+
Console.WriteLine($"Markdown MMS listener skeleton profile: {Path.GetFullPath(markdownPath)}");
609+
}
610+
611+
if (options.TryGet("json", out var jsonPath) && !string.IsNullOrWhiteSpace(jsonPath))
612+
{
613+
EnsureOutputDirectory(jsonPath);
614+
File.WriteAllText(jsonPath, JsonSerializer.Serialize(profile, new JsonSerializerOptions { WriteIndented = true }));
615+
Console.WriteLine($"JSON MMS listener skeleton profile: {Path.GetFullPath(jsonPath)}");
616+
}
617+
618+
return profile.IsReady ? 0 : 3;
619+
}
620+
548621
private static int InspectPcap(string[] args)
549622
{
550623
if (args.Length < 1)
@@ -5521,6 +5594,7 @@ private static void WriteUsage()
55215594
Console.WriteLine(" mms-engine-profile <host-or-ip> [--port 102] [--timeout-ms 30000] [--max-report-probes N] [--read-datasets true] [--output profile.md] [--json profile.json]");
55225595
Console.WriteLine(" mms-report-readiness-profile <host-or-ip> [--port 102] [--timeout-ms 120000] [--rcb LD/LN.BR.name] [--dataset LD/LLN0.DataSet] [--strict-rcb] [--allow-urcb-fallback true|false] [--duration-sec 60] [--gi true|false] [--output report-readiness.md] [--json report-readiness.json] [--session-json session-profile.json]");
55235596
Console.WriteLine(" mms-server-readonly-profile [--port 102] [--name NAME] [--steps N] [--read LD/LN.DO.da] [--dataset LD/LLN0.DataSet] [--output mms-server.md] [--json mms-server.json]");
5597+
Console.WriteLine(" mms-listener-skeleton-profile [--host 127.0.0.1] [--port 0] [--timeout-ms 5000] [--steps N] [--output mms-listener.md] [--json mms-listener.json]");
55245598
Console.WriteLine(" mms-directory <host-or-ip> [--port 102] [--timeout-ms 30000] [--ln-limit N] [--raw-limit N] [--show-points]");
55255599
Console.WriteLine(" mms-model-discover <host-or-ip> [--port 102] [--timeout-ms 120000] [--max-report-probes 286] [--read-datasets true|false] [--read-types true|false] [--max-type-reads 256] [--type-read-source datasets|model|both] [--ied-name NAME] [--ap-name AP1] [--output .artifacts/out/ied-model-discovery]");
55265600
Console.WriteLine(" mms-scl-export <host-or-ip> [--port 102] [--ied-name NAME] [--ap-name AP1] [--scl-export-profile safe-connection|standard-discovery|full-model|simulator-seed] [--write-connection-companion true] [--connection-output .artifacts/out/scl/live-ied.safe-connection.iid] [--ld-name-mode auto|keep] [--output .artifacts/out/scl/live-ied.generated.iid] [--read-datasets true] [--read-types true] [--max-type-reads 512] [--include-osi true]");
@@ -5557,6 +5631,7 @@ private static void WriteUsage()
55575631
Console.WriteLine(" dotnet run --project apps/AR.Iec61850.Cli -- mms-engine-profile 192.0.2.10 --output .artifacts/out/engineering-profile.md --json .artifacts/out/engineering-profile.json");
55585632
Console.WriteLine(" dotnet run --project apps/AR.Iec61850.Cli -- mms-report-readiness-profile 192.0.2.10 --output .artifacts/out/report-readiness.md --json .artifacts/out/report-readiness.json --session-json .artifacts/out/report-session-profile.json");
55595633
Console.WriteLine(" dotnet run --project apps/AR.Iec61850.Cli -- mms-server-readonly-profile --steps 5 --output .artifacts/out/mms-server-readonly.md --json .artifacts/out/mms-server-readonly.json");
5634+
Console.WriteLine(" dotnet run --project apps/AR.Iec61850.Cli -- mms-listener-skeleton-profile --port 0 --output .artifacts/out/mms-listener-skeleton.md --json .artifacts/out/mms-listener-skeleton.json");
55605635
Console.WriteLine(" dotnet run --project apps/AR.Iec61850.Cli -- mms-directory 192.0.2.10 --show-points --raw-limit 40");
55615636
Console.WriteLine(" dotnet run --project apps/AR.Iec61850.Cli -- mms-model-discover 192.0.2.10 --max-report-probes 286 --read-types true --max-type-reads 256 --output .artifacts/out/ied-model-discovery");
55625637
Console.WriteLine(" dotnet run --project apps/AR.Iec61850.Cli -- mms-scl-export 192.0.2.10 --ied-name IED1 --scl-export-profile safe-connection --ld-name-mode auto --output .artifacts/out/scl/demo-ied.generated.iid");

docs/ENGINE_MATURITY_MATRIX.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ This matrix is the public engineering checklist for growing ARIEC61850 from prot
1313
| GOOSE | Encode/decode/publish/subscribe basics | Expected-vs-observed diagnostics | SCL-bound forensic engine |
1414
| Sampled Values | Encode/decode/publish/injector basics | Subscriber/analyzer engine | RMS/phasor/timing/continuity diagnostics |
1515
| SCL | Parser/exporter/diff basics | Deep type-template and communication resolver | Station dataflow graph and mapping validator |
16-
| Simulation | Offline deterministic profile engine | Read-only MMS server adapter | Virtual IED with reports, GOOSE, SV scenarios |
16+
| Simulation | Offline profile + read-only server model + loopback TCP listener skeleton | TPKT/COTP/ACSE/MMS read-only listener alpha | Virtual IED with reports, GOOSE, SV scenarios |
1717
| File/log/setting | Not yet mature | Read-only client browser first | Typed ACSI services with guarded writes |
1818
| Security diagnostics | Not a base feature yet | Rule-based semantic checks | Explainable cyber/semantic findings without black-box claims |
1919

@@ -95,4 +95,20 @@ Maturity impact: process-bus SV moves from visibility to actionable engineering
9595
## Server-side milestone
9696

9797
- MMS read-only server alpha: implemented as offline virtual IED profile + high-level service handler.
98-
- Next maturity gate: live TCP/TPKT/COTP/ACSE/MMS listener skeleton with read-only service mapping.
98+
- MMS listener skeleton profile: implemented as loopback TCP listener lifecycle + JSON-line probe harness + write guard evidence.
99+
- Next maturity gate: attach TPKT/COTP/ACSE/MMS decoding/encoding to the read-only listener while preserving the same service contract.
100+
101+
102+
## N5.30 — MMS Listener Skeleton Profile
103+
104+
This milestone adds the first live transport boundary for the simulator-backed server model. The listener binds to a loopback TCP endpoint, accepts a client session, dispatches deterministic read-only service requests, verifies write rejection, and exports Markdown/JSON evidence.
105+
106+
```text
107+
virtual IED profile
108+
→ read-only service handler
109+
→ TCP listener skeleton
110+
→ loopback probe
111+
→ listener evidence
112+
```
113+
114+
Scope boundary: the harness intentionally uses a JSON-line probe protocol. The next milestone should replace the probe decoder with TPKT/COTP/ACSE/MMS request handling while keeping the same read-only service semantics.

docs/QUICK_START.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,11 @@ dotnet run --project .\apps\AR.Iec61850.Cli -- mms-server-readonly-profile --ste
211211

212212
This command needs no IED and no network adapter. It builds a virtual IED model from the simulator profile and exercises read-only directory, read, DataSet read, and write-guard probes.
213213

214+
## MMS listener skeleton self-probe
215+
216+
```powershell
217+
dotnet run --project .\apps\AR.Iec61850.Cli -- mms-listener-skeleton-profile --port 0 --output .\.artifacts\out\mms-listener-skeleton.md --json .\.artifacts\out\mms-listener-skeleton.json
218+
```
219+
220+
This command starts a loopback TCP listener, sends deterministic read-only probe requests, verifies directory/read/DataSet responses, confirms write rejection, and exits. It is a listener/session lifecycle harness, not a full MMS PDU server yet.
221+

docs/ROADMAP.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,14 @@ A public release is allowed only when these gates are true:
112112
- Add stream registry, sample counter continuity, sample-rate detection, ASDU/layout checks, RMS, phasor, jitter/dropout, and PTP-correlation hooks.
113113
- Keep the current SV publisher/injector as a test harness for the analyzer.
114114

115-
### N5.30 - Read-only MMS server alpha
115+
### N5.31 - TPKT/COTP/ACSE/MMS read-only listener alpha
116116

117-
- Add TCP/TPKT/COTP/ACSE/MMS accept path.
118-
- Serve domains, variables, access attributes, and named variable lists from a simulator model.
117+
- Attach TPKT framing to the listener skeleton.
118+
- Add COTP connection confirm and ACSE associate response.
119+
- Add MMS initiate response and confirmed read-directory/read request dispatch.
119120
- Keep write/control disabled until read-only discovery and report readback are stable.
120121

121-
### N5.31 - Simulator bridge
122+
### N5.32 - Simulator bridge
122123

123124
- Map simulator profiles into MMS server model, GOOSE publisher profiles, and SV publisher profiles.
124125
- Add scenario scheduler for value changes, quality changes, timestamp faults, report triggers, GOOSE transitions, and SV disturbances.
@@ -139,6 +140,14 @@ ARIEC61850 engine repo
139140

140141
Product repos can later consume the engine as project references or NuGet packages without pulling protocol logic into UI code.
141142

143+
## N5.30 — MMS Listener Skeleton Profile
144+
145+
The engine now has a loopback TCP listener skeleton for the read-only virtual IED service handler. It can be tested without hardware using `mms-listener-skeleton-profile`, and it validates listener bind, accepted connection, request dispatch, read-only service responses, write rejection, and Markdown/JSON evidence.
146+
147+
```powershell
148+
dotnet run --project .\apps\AR.Iec61850.Cli -- mms-listener-skeleton-profile --port 0 --output .\.artifacts\out\mms-listener-skeleton.md --json .\.artifacts\out\mms-listener-skeleton.json
149+
```
150+
142151
## N5.29 — MMS Read-Only Server Alpha
143152

144153
The engine now has an offline server-side model profile that converts the simulator profile into a read-only virtual IED. It can be tested without hardware using `mms-server-readonly-profile`, and it validates directory, read, DataSet read, RCB exposure, and write rejection.

0 commit comments

Comments
 (0)