Skip to content

Add clean-room SNTP clock sync service - #148

Merged
masarray merged 21 commits into
mainfrom
agent/sntp-clock-service-p0
Aug 13, 2026
Merged

Add clean-room SNTP clock sync service#148
masarray merged 21 commits into
mainfrom
agent/sntp-clock-service-p0

Conversation

@masarray

@masarray masarray commented Aug 13, 2026

Copy link
Copy Markdown
Owner

What changed

  • added a clean-room SNTPv4 packet codec written specifically for ARSAS from RFC wire semantics; no third-party NTP engine or copied GPL implementation
  • added smart station-bus IPv4 route/interface selection using the Windows routing decision first, with same-subnet fallback and directed-broadcast calculation
  • added an isolated UDP/123 commissioning clock service with Mode 4 unicast replies and Mode 5 broadcasts
  • auto-starts Clock Sync after an IED reaches the connected state without coupling SNTP to MMS/GOOSE/SV protocol code
  • sends an immediate broadcast when an IED connects, then periodic 64-second broadcasts
  • copies Version, Poll, and client Transmit timestamp fields according to SNTP server reply semantics
  • records first SNTP client request per IED so ARSAS can distinguish request observed from merely broadcast advertised
  • forces the default commissioning profile to SIPROTEC compatibility stratum 2 / LOCL for synchronized Mode 4 and Mode 5 packets; this is a compatibility advertisement, not a claim that the laptop is a GPS/PTP/stratum-1 reference
  • protects the SIPROTEC compatibility stratum with regression assertions so future refactors cannot silently restore a high-stratum default
  • detects large Windows wall-clock steps; unsafe broadcast is suppressed and an affected unicast request receives an unsynchronized response (LI=3, stratum=0, INIT, zero server timestamps)
  • binds UDP/123 exclusively on the selected station-bus address; Windows Time or another NTP owner is diagnosed but never stopped/reconfigured automatically
  • SNTP failures are fail-open for IEC 61850: association/monitoring continues normally
  • added packet, timestamp, unsynchronized-state, route/broadcast tests and implementation documentation

SIPROTEC compatibility rationale

Siemens SIPROTEC PIXIT behavior treats high-stratum SNTP sources as questionable/unsynchronized. Field commissioning feedback also shows SIPROTEC installations that do not accept stratum 3 as the practical laptop clock source. ARSAS therefore uses stratum 2 by default while retaining LOCL and explicit diagnostics that this is a local commissioning clock, not UTC/GNSS/PTP traceability.

Integration footprint

The existing application is intentionally left almost untouched. MainWindow.IoTesting.cs has one added initializer call; Clock Sync itself lives in new isolated files under Services, tests, and docs.

Validation

Previous head 65a5c4f6180956aa699d161e1764ee225ba6a90c passed:

  • Build ARSAS
  • source/license boundary gate
  • full solution build
  • full ARSAS regression suite: 157/157 passed
  • all 6 SNTP regression tests passed
  • IO List Testing focused build/tests
  • SV evidence focused build/tests
  • portable x64 publish
  • portable EXE smoke test

Current head d3efa96d93bf23cba7ec072dfcbe8a4704ce5110 adds the SIPROTEC stratum-2 compatibility lock and is being revalidated by the same CI gates before merge.

Field validation required on the release candidate

Capture UDP/123 in Wireshark and verify behavior with at least one real SIPROTEC IED:

  1. Mode 5 broadcast is emitted on the intended station-bus NIC immediately after connection with stratum 2.
  2. A unicast SNTP client request to the ARSAS laptop receives a valid Mode 4 reply with stratum 2.
  3. SIPROTEC clock converges to the laptop time and no longer remains ClockNotSynchronized because of the advertised source quality.
  4. Existing MMS, reporting, GOOSE, SV, and IO/FAT behavior remains unchanged during clock service activity.

P0 is intentionally IPv4 and first-station-bus-interface scoped. Multi-NIC raw/Npcap serving and PTP/IEC 61850-9-3 monitoring are separate future phases.

@masarray
masarray marked this pull request as ready for review August 13, 2026 03:56

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 65a5c4f618

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread MainWindow.ClockSync.cs
Comment on lines +52 to +53
if (device.IsConnected)
ScheduleClockSyncReconcile(device);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Skip demo devices before starting SNTP

When demo mode adds its simulated devices, each is already marked IsConnected = true and IsDemo = true before Devices.Add (MainWindow.Demo.cs lines 209-222), so this unconditional scheduling starts a real UDP/123 service for fake addresses such as 192.168.10.11. If that subnet is absent, route resolution can select the machine's default adapter, causing demo mode to bind UDP/123 and transmit recurring time broadcasts onto an unrelated real network; exclude demo devices from clock-sync reconciliation.

Useful? React with 👍 / 👎.

Comment on lines +315 to +319
catch (SocketException ex)
{
if (!cancellationToken.IsCancellationRequested)
SetState(SntpClockServiceState.Faulted, $"SNTP broadcast failed: {ex.SocketErrorCode}.");
break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retry broadcasts after transient socket errors

If a directed-broadcast send raises any SocketException, for example during a temporary adapter outage, this break permanently terminates the broadcast task. The UDP service remains assigned to _udp, so subsequent connected-device reconciliations only set the immediate-broadcast flag and never recreate the task; mode-5 broadcasts therefore remain disabled even after the network recovers. Retry with backoff or restart the broadcast loop instead of exiting permanently.

Useful? React with 👍 / 👎.

@masarray
masarray merged commit b54af09 into main Aug 13, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant