feat(dcl): Layer D OpcUaGlobalOptions for app-wide identity + cert paths
New deployment-wide options bound from the "OpcUa" section of appsettings.json: - ApplicationName (default "ScadaLink-DCL") - TrustedIssuerStorePath / TrustedPeerStorePath / RejectedCertificateStorePath Empty paths fall back to Path.GetTempPath()/ScadaLink/pki/* so dev runs work without explicit config — same defaults the hardcoded values previously used. Wiring: - ServiceCollectionExtensions binds OpcUaGlobalOptions to the OpcUa section. - DataConnectionFactory takes IOptions<OpcUaGlobalOptions> and constructs RealOpcUaClientFactory with the snapshot. - RealOpcUaClient(globalOptions) replaces the hardcoded ApplicationName and the three CertificateTrustList store paths in ApplicationConfiguration. - Parameterless ctors on factory and client preserved for the existing test suite (32/32 DCL tests still green).
This commit is contained in:
@@ -17,6 +17,12 @@ public class RealOpcUaClient : IOpcUaClient
|
|||||||
private readonly Dictionary<string, Action<string, object?, DateTime, uint>> _callbacks = new();
|
private readonly Dictionary<string, Action<string, object?, DateTime, uint>> _callbacks = new();
|
||||||
private volatile bool _connectionLostFired;
|
private volatile bool _connectionLostFired;
|
||||||
private OpcUaConnectionOptions _options = new();
|
private OpcUaConnectionOptions _options = new();
|
||||||
|
private readonly OpcUaGlobalOptions _globalOptions;
|
||||||
|
|
||||||
|
public RealOpcUaClient(OpcUaGlobalOptions? globalOptions = null)
|
||||||
|
{
|
||||||
|
_globalOptions = globalOptions ?? new OpcUaGlobalOptions();
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsConnected => _session?.Connected ?? false;
|
public bool IsConnected => _session?.Connected ?? false;
|
||||||
public event Action? ConnectionLost;
|
public event Action? ConnectionLost;
|
||||||
@@ -34,15 +40,17 @@ public class RealOpcUaClient : IOpcUaClient
|
|||||||
|
|
||||||
var appConfig = new ApplicationConfiguration
|
var appConfig = new ApplicationConfiguration
|
||||||
{
|
{
|
||||||
ApplicationName = "ScadaLink-DCL",
|
ApplicationName = string.IsNullOrWhiteSpace(_globalOptions.ApplicationName)
|
||||||
|
? "ScadaLink-DCL"
|
||||||
|
: _globalOptions.ApplicationName,
|
||||||
ApplicationType = ApplicationType.Client,
|
ApplicationType = ApplicationType.Client,
|
||||||
SecurityConfiguration = new SecurityConfiguration
|
SecurityConfiguration = new SecurityConfiguration
|
||||||
{
|
{
|
||||||
AutoAcceptUntrustedCertificates = opts.AutoAcceptUntrustedCerts,
|
AutoAcceptUntrustedCertificates = opts.AutoAcceptUntrustedCerts,
|
||||||
ApplicationCertificate = new CertificateIdentifier(),
|
ApplicationCertificate = new CertificateIdentifier(),
|
||||||
TrustedIssuerCertificates = new CertificateTrustList { StorePath = Path.Combine(Path.GetTempPath(), "ScadaLink", "pki", "issuers") },
|
TrustedIssuerCertificates = new CertificateTrustList { StorePath = ResolveStorePath(_globalOptions.TrustedIssuerStorePath, "issuers") },
|
||||||
TrustedPeerCertificates = new CertificateTrustList { StorePath = Path.Combine(Path.GetTempPath(), "ScadaLink", "pki", "trusted") },
|
TrustedPeerCertificates = new CertificateTrustList { StorePath = ResolveStorePath(_globalOptions.TrustedPeerStorePath, "trusted") },
|
||||||
RejectedCertificateStore = new CertificateTrustList { StorePath = Path.Combine(Path.GetTempPath(), "ScadaLink", "pki", "rejected") }
|
RejectedCertificateStore = new CertificateTrustList { StorePath = ResolveStorePath(_globalOptions.RejectedCertificateStorePath, "rejected") }
|
||||||
},
|
},
|
||||||
ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = opts.SessionTimeoutMs },
|
ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = opts.SessionTimeoutMs },
|
||||||
TransportQuotas = new TransportQuotas { OperationTimeout = opts.OperationTimeoutMs }
|
TransportQuotas = new TransportQuotas { OperationTimeout = opts.OperationTimeoutMs }
|
||||||
@@ -270,6 +278,11 @@ public class RealOpcUaClient : IOpcUaClient
|
|||||||
"BOTH" => TimestampsToReturn.Both,
|
"BOTH" => TimestampsToReturn.Both,
|
||||||
_ => TimestampsToReturn.Source
|
_ => TimestampsToReturn.Source
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static string ResolveStorePath(string configured, string fallbackLeaf) =>
|
||||||
|
string.IsNullOrWhiteSpace(configured)
|
||||||
|
? Path.Combine(Path.GetTempPath(), "ScadaLink", "pki", fallbackLeaf)
|
||||||
|
: configured;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -277,5 +290,13 @@ public class RealOpcUaClient : IOpcUaClient
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class RealOpcUaClientFactory : IOpcUaClientFactory
|
public class RealOpcUaClientFactory : IOpcUaClientFactory
|
||||||
{
|
{
|
||||||
public IOpcUaClient Create() => new RealOpcUaClient();
|
private readonly OpcUaGlobalOptions _globalOptions;
|
||||||
|
|
||||||
|
public RealOpcUaClientFactory() : this(new OpcUaGlobalOptions()) { }
|
||||||
|
public RealOpcUaClientFactory(OpcUaGlobalOptions globalOptions)
|
||||||
|
{
|
||||||
|
_globalOptions = globalOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IOpcUaClient Create() => new RealOpcUaClient(_globalOptions);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using ScadaLink.Commons.Interfaces.Protocol;
|
using ScadaLink.Commons.Interfaces.Protocol;
|
||||||
using ScadaLink.DataConnectionLayer.Adapters;
|
using ScadaLink.DataConnectionLayer.Adapters;
|
||||||
|
|
||||||
@@ -14,12 +15,17 @@ public class DataConnectionFactory : IDataConnectionFactory
|
|||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
|
|
||||||
public DataConnectionFactory(ILoggerFactory loggerFactory)
|
public DataConnectionFactory(ILoggerFactory loggerFactory)
|
||||||
|
: this(loggerFactory, Options.Create(new OpcUaGlobalOptions())) { }
|
||||||
|
|
||||||
|
public DataConnectionFactory(ILoggerFactory loggerFactory, IOptions<OpcUaGlobalOptions> opcUaGlobalOptions)
|
||||||
{
|
{
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
|
var globalOptions = opcUaGlobalOptions.Value;
|
||||||
|
|
||||||
// Register built-in protocols
|
// Register built-in protocols
|
||||||
RegisterAdapter("OpcUa", details => new OpcUaDataConnection(
|
RegisterAdapter("OpcUa", details => new OpcUaDataConnection(
|
||||||
new RealOpcUaClientFactory(), _loggerFactory.CreateLogger<OpcUaDataConnection>()));
|
new RealOpcUaClientFactory(globalOptions),
|
||||||
|
_loggerFactory.CreateLogger<OpcUaDataConnection>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
15
src/ScadaLink.DataConnectionLayer/OpcUaGlobalOptions.cs
Normal file
15
src/ScadaLink.DataConnectionLayer/OpcUaGlobalOptions.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace ScadaLink.DataConnectionLayer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deployment-wide OPC UA application identity. Bound from the "OpcUa" section
|
||||||
|
/// of appsettings.json. Per-endpoint behavior lives on OpcUaEndpointConfig.
|
||||||
|
/// Empty paths fall back to a default under Path.GetTempPath() so dev runs
|
||||||
|
/// work without explicit configuration.
|
||||||
|
/// </summary>
|
||||||
|
public class OpcUaGlobalOptions
|
||||||
|
{
|
||||||
|
public string ApplicationName { get; set; } = "ScadaLink-DCL";
|
||||||
|
public string TrustedIssuerStorePath { get; set; } = "";
|
||||||
|
public string TrustedPeerStorePath { get; set; } = "";
|
||||||
|
public string RejectedCertificateStorePath { get; set; } = "";
|
||||||
|
}
|
||||||
@@ -9,6 +9,9 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddOptions<DataConnectionOptions>()
|
services.AddOptions<DataConnectionOptions>()
|
||||||
.BindConfiguration("DataConnectionLayer");
|
.BindConfiguration("DataConnectionLayer");
|
||||||
|
|
||||||
|
services.AddOptions<OpcUaGlobalOptions>()
|
||||||
|
.BindConfiguration("OpcUa");
|
||||||
|
|
||||||
// WP-34: Register the factory for protocol extensibility
|
// WP-34: Register the factory for protocol extensibility
|
||||||
services.AddSingleton<IDataConnectionFactory, DataConnectionFactory>();
|
services.AddSingleton<IDataConnectionFactory, DataConnectionFactory>();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user