feat(opcua): OpcUaApplicationHost facade in OpcUaServer (full extraction tracked as F13)
This commit is contained in:
117
src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OpcUaApplicationHost.cs
Normal file
117
src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OpcUaApplicationHost.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Configuration;
|
||||
using Opc.Ua.Server;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer;
|
||||
|
||||
public sealed class OpcUaApplicationHostOptions
|
||||
{
|
||||
public string ApplicationName { get; set; } = "OtOpcUa";
|
||||
public string ApplicationUri { get; set; } = "urn:OtOpcUa";
|
||||
public string ProductUri { get; set; } = "https://zb.com/otopcua";
|
||||
|
||||
/// <summary>Listening port for the binary endpoint (default 4840).</summary>
|
||||
public int OpcUaPort { get; set; } = 4840;
|
||||
|
||||
/// <summary>Hostname or IP advertised in endpoint descriptions.</summary>
|
||||
public string PublicHostname { get; set; } = "0.0.0.0";
|
||||
|
||||
/// <summary>Application config XML path; when set, loaded instead of building from defaults.</summary>
|
||||
public string? ApplicationConfigPath { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thin facade over the OPC Foundation .NET Standard SDK's application bootstrap.
|
||||
/// Owns the <see cref="ApplicationInstance"/> + <see cref="ApplicationConfiguration"/> lifetime
|
||||
/// and starts a <see cref="StandardServer"/> with the supplied node-manager factory.
|
||||
///
|
||||
/// Full extraction from legacy <c>OtOpcUa.Server</c> (security wiring, ScriptedAlarmDescriptor
|
||||
/// pipeline, ResilienceController, history backend, observability hooks) is tracked as
|
||||
/// follow-up F13. This facade compiles + boots the SDK so Task 53 can wire the fused Host's
|
||||
/// driver-role startup against it.
|
||||
/// </summary>
|
||||
public sealed class OpcUaApplicationHost : IAsyncDisposable
|
||||
{
|
||||
private readonly OpcUaApplicationHostOptions _options;
|
||||
private readonly ILogger<OpcUaApplicationHost> _logger;
|
||||
private ApplicationInstance? _application;
|
||||
private StandardServer? _server;
|
||||
|
||||
public OpcUaApplicationHost(
|
||||
OpcUaApplicationHostOptions options,
|
||||
ILogger<OpcUaApplicationHost> logger)
|
||||
{
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public ApplicationInstance? ApplicationInstance => _application;
|
||||
public StandardServer? Server => _server;
|
||||
|
||||
public async Task StartAsync(StandardServer server, CancellationToken cancellationToken)
|
||||
{
|
||||
_server = server;
|
||||
_application = new ApplicationInstance
|
||||
{
|
||||
ApplicationName = _options.ApplicationName,
|
||||
ApplicationType = ApplicationType.Server,
|
||||
ConfigSectionName = "OtOpcUa",
|
||||
};
|
||||
|
||||
_ = await BuildConfigurationAsync(cancellationToken);
|
||||
// Certificate validation + auto-creation is part of the full extraction (F13).
|
||||
// For the facade we trust that the configured cert store already exists.
|
||||
await _application.Start(server).ConfigureAwait(false);
|
||||
|
||||
_logger.LogInformation("OPC UA server started on opc.tcp://{Host}:{Port}",
|
||||
_options.PublicHostname, _options.OpcUaPort);
|
||||
}
|
||||
|
||||
private async Task<ApplicationConfiguration> BuildConfigurationAsync(CancellationToken ct)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_options.ApplicationConfigPath))
|
||||
{
|
||||
return await _application!.LoadApplicationConfiguration(_options.ApplicationConfigPath, silent: true);
|
||||
}
|
||||
|
||||
// Minimal defaults — security and certificate stores hardcoded to local files in
|
||||
// the app's working directory. Full security wiring stays in legacy Server until F13.
|
||||
var config = new ApplicationConfiguration
|
||||
{
|
||||
ApplicationName = _options.ApplicationName,
|
||||
ApplicationUri = _options.ApplicationUri,
|
||||
ProductUri = _options.ProductUri,
|
||||
ApplicationType = ApplicationType.Server,
|
||||
ServerConfiguration = new ServerConfiguration
|
||||
{
|
||||
BaseAddresses = { $"opc.tcp://{_options.PublicHostname}:{_options.OpcUaPort}/OtOpcUa" },
|
||||
MinRequestThreadCount = 5,
|
||||
MaxRequestThreadCount = 100,
|
||||
MaxQueuedRequestCount = 200,
|
||||
},
|
||||
SecurityConfiguration = new SecurityConfiguration
|
||||
{
|
||||
ApplicationCertificate = new CertificateIdentifier { StoreType = "Directory", StorePath = "pki/own", SubjectName = $"CN={_options.ApplicationName}" },
|
||||
TrustedIssuerCertificates = new CertificateTrustList { StoreType = "Directory", StorePath = "pki/issuer" },
|
||||
TrustedPeerCertificates = new CertificateTrustList { StoreType = "Directory", StorePath = "pki/trusted" },
|
||||
RejectedCertificateStore = new CertificateTrustList { StoreType = "Directory", StorePath = "pki/rejected" },
|
||||
AutoAcceptUntrustedCertificates = false,
|
||||
},
|
||||
TransportQuotas = new TransportQuotas(),
|
||||
ClientConfiguration = new ClientConfiguration(),
|
||||
TraceConfiguration = new TraceConfiguration(),
|
||||
};
|
||||
|
||||
await config.Validate(ApplicationType.Server).ConfigureAwait(false);
|
||||
_application!.ApplicationConfiguration = config;
|
||||
return config;
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
try { _application?.Stop(); }
|
||||
catch (Exception ex) { _logger.LogWarning(ex, "OpcUaApplicationHost: Stop threw on dispose"); }
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user