Fix OPC UA adapter: pass connection details, certificate stores, endpoint discovery

- DataConnectionActor now stores and passes connection details to adapter ConnectAsync
- DataConnectionManagerActor passes connection details when creating actor
- RealOpcUaClient uses DiscoveryClient for endpoint selection with no-security preference
- Added certificate trust store paths to prevent TrustedIssuerCertificates error
- Sanitize connection names for Akka actor paths (replace spaces)
This commit is contained in:
Joseph Doherty
2026-03-17 12:19:44 -04:00
parent 8e1d0816b3
commit 1b06a4971e
3 changed files with 35 additions and 9 deletions

View File

@@ -58,14 +58,18 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers
private int _totalSubscribed;
private int _resolvedTags;
private readonly IDictionary<string, string> _connectionDetails;
public DataConnectionActor(
string connectionName,
IDataConnection adapter,
DataConnectionOptions options)
DataConnectionOptions options,
IDictionary<string, string>? connectionDetails = null)
{
_connectionName = connectionName;
_adapter = adapter;
_options = options;
_connectionDetails = connectionDetails ?? new Dictionary<string, string>();
}
protected override void PreStart()
@@ -204,7 +208,7 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers
{
_log.Debug("[{0}] Attempting connection...", _connectionName);
var self = Self;
_adapter.ConnectAsync(new Dictionary<string, string>()).ContinueWith(t =>
_adapter.ConnectAsync(_connectionDetails).ContinueWith(t =>
{
if (t.IsCompletedSuccessfully)
return new ConnectResult(true, null);

View File

@@ -44,9 +44,13 @@ public class DataConnectionManagerActor : ReceiveActor
var adapter = _factory.Create(command.ProtocolType, command.ConnectionDetails);
var props = Props.Create(() => new DataConnectionActor(
command.ConnectionName, adapter, _options));
command.ConnectionName, adapter, _options, command.ConnectionDetails));
var actorRef = Context.ActorOf(props, command.ConnectionName);
// Sanitize name for Akka actor path (replace spaces and invalid chars)
var actorName = new string(command.ConnectionName
.Select(c => char.IsLetterOrDigit(c) || "-_.*$+:@&=,!~';()".Contains(c) ? c : '-')
.ToArray());
var actorRef = Context.ActorOf(props, actorName);
_connectionActors[command.ConnectionName] = actorRef;
_log.Info("Created DataConnectionActor for {0} (protocol={1})",

View File

@@ -26,7 +26,10 @@ public class RealOpcUaClient : IOpcUaClient
SecurityConfiguration = new SecurityConfiguration
{
AutoAcceptUntrustedCertificates = true,
ApplicationCertificate = new CertificateIdentifier()
ApplicationCertificate = new CertificateIdentifier(),
TrustedIssuerCertificates = new CertificateTrustList { StorePath = Path.Combine(Path.GetTempPath(), "ScadaLink", "pki", "issuers") },
TrustedPeerCertificates = new CertificateTrustList { StorePath = Path.Combine(Path.GetTempPath(), "ScadaLink", "pki", "trusted") },
RejectedCertificateStore = new CertificateTrustList { StorePath = Path.Combine(Path.GetTempPath(), "ScadaLink", "pki", "rejected") }
},
ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 },
TransportQuotas = new TransportQuotas { OperationTimeout = 15000 }
@@ -35,11 +38,26 @@ public class RealOpcUaClient : IOpcUaClient
await appConfig.ValidateAsync(ApplicationType.Client);
appConfig.CertificateValidator.CertificateValidation += (_, e) => e.Accept = true;
var endpoint = new EndpointDescription(endpointUrl)
// Discover endpoints from the server, pick the no-security one
EndpointDescription? endpoint;
try
{
SecurityMode = MessageSecurityMode.None,
SecurityPolicyUri = SecurityPolicies.None
};
#pragma warning disable CS0618
using var discoveryClient = DiscoveryClient.Create(new Uri(endpointUrl));
#pragma warning restore CS0618
#pragma warning disable CS0618
var endpoints = discoveryClient.GetEndpoints(null);
#pragma warning restore CS0618
endpoint = endpoints
.Where(e => e.SecurityMode == MessageSecurityMode.None)
.FirstOrDefault() ?? endpoints.FirstOrDefault();
}
catch
{
// Fallback: construct endpoint description manually
endpoint = new EndpointDescription(endpointUrl);
}
var endpointConfig = EndpointConfiguration.Create(appConfig);
var configuredEndpoint = new ConfiguredEndpoint(null, endpoint, endpointConfig);