From 7ce7505a3618ebbe51be9ac6d41c210d9d6cc9d4 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 12 Jun 2026 11:19:46 -0400 Subject: [PATCH] feat(historian-host): bind TCP host/port/tls config --- src/Server/ZB.MOM.WW.OtOpcUa.Host/Program.cs | 5 ++++- .../ZB.MOM.WW.OtOpcUa.Host/appsettings.json | 4 ++++ .../Historian/AlarmHistorianOptions.cs | 12 +++++++++++ .../AlarmHistorianRegistrationTests.cs | 20 +++++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Host/Program.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Host/Program.cs index 5a9344b1..ed042f26 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Host/Program.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Host/Program.cs @@ -94,7 +94,10 @@ if (hasDriver) builder.Services.AddAlarmHistorian( builder.Configuration, (opts, sp) => new WonderwareHistorianClient( - new WonderwareHistorianClientOptions(opts.PipeName, opts.SharedSecret), + new WonderwareHistorianClientOptions(opts.PipeName, opts.SharedSecret) + { + Host = opts.Host, Port = opts.Port, UseTls = opts.UseTls, ServerCertThumbprint = opts.ServerCertThumbprint, + }, sp.GetService>())); // Bind every cross-platform driver factory before AddAkka resolves IDriverFactory — replaces diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Host/appsettings.json b/src/Server/ZB.MOM.WW.OtOpcUa.Host/appsettings.json index 0f44ec79..8f373da2 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Host/appsettings.json +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Host/appsettings.json @@ -15,6 +15,10 @@ "Enabled": false, "DatabasePath": "alarm-historian.db", "PipeName": "OtOpcUaHistorian", + "Host": "localhost", + "Port": 32569, + "UseTls": false, + "ServerCertThumbprint": null, "SharedSecret": "", "DrainIntervalSeconds": 5, "Capacity": 1000000, diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/AlarmHistorianOptions.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/AlarmHistorianOptions.cs index b99d55fa..aeaa33de 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/AlarmHistorianOptions.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/AlarmHistorianOptions.cs @@ -28,6 +28,18 @@ public sealed class AlarmHistorianOptions /// Named-pipe name the Wonderware historian sidecar listens on. public string PipeName { get; init; } = "OtOpcUaHistorian"; + /// TCP hostname or IP address the Wonderware historian sidecar listens on. + public string Host { get; init; } = "localhost"; + + /// TCP port the Wonderware historian sidecar listens on. + public int Port { get; init; } = 32569; + + /// When true, the client connects over TLS. + public bool UseTls { get; init; } + + /// Expected TLS server certificate thumbprint (hex, no spaces). Null or empty disables pinning. + public string? ServerCertThumbprint { get; init; } + /// Per-process shared secret the sidecar verifies in the Hello frame. public string SharedSecret { get; init; } = ""; diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/AlarmHistorianRegistrationTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/AlarmHistorianRegistrationTests.cs index 0d8db9a1..9e6368db 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/AlarmHistorianRegistrationTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/AlarmHistorianRegistrationTests.cs @@ -169,4 +169,24 @@ public sealed class AlarmHistorianRegistrationTests warnings.ShouldContain(w => w.Contains("DatabasePath")); warnings.Count.ShouldBeGreaterThanOrEqualTo(2); } + + [Fact] + public void Section_binds_tcp_host_port_tls_fields() + { + var config = ConfigFrom(new Dictionary + { + ["AlarmHistorian:Host"] = "historian.example.com", + ["AlarmHistorian:Port"] = "12345", + ["AlarmHistorian:UseTls"] = "true", + ["AlarmHistorian:ServerCertThumbprint"] = "AABBCCDDEEFF", + }); + + var opts = config.GetSection(AlarmHistorianOptions.SectionName).Get(); + + opts.ShouldNotBeNull(); + opts.Host.ShouldBe("historian.example.com"); + opts.Port.ShouldBe(12345); + opts.UseTls.ShouldBeTrue(); + opts.ServerCertThumbprint.ShouldBe("AABBCCDDEEFF"); + } }