Implements Client.Shared (IOpcUaClientService with connection lifecycle, failover, browse, read/write, subscriptions, alarms, history, redundancy), Client.CLI (8 CliFx commands mirroring tools/opcuacli-dotnet), and Client.UI (Avalonia desktop app with tree browser, read/write, subscriptions, alarms, and history tabs). All three target .NET 10 and are covered by 249 unit tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
63 lines
2.1 KiB
C#
63 lines
2.1 KiB
C#
using Opc.Ua;
|
|
using Opc.Ua.Client;
|
|
using Serilog;
|
|
|
|
namespace ZB.MOM.WW.LmxOpcUa.Client.Shared.Adapters;
|
|
|
|
/// <summary>
|
|
/// Production endpoint discovery that queries the real server.
|
|
/// </summary>
|
|
internal sealed class DefaultEndpointDiscovery : IEndpointDiscovery
|
|
{
|
|
private static readonly ILogger Logger = Log.ForContext<DefaultEndpointDiscovery>();
|
|
|
|
public EndpointDescription SelectEndpoint(ApplicationConfiguration config, string endpointUrl, MessageSecurityMode requestedMode)
|
|
{
|
|
if (requestedMode == MessageSecurityMode.None)
|
|
{
|
|
#pragma warning disable CS0618 // Acceptable for endpoint selection
|
|
return CoreClientUtils.SelectEndpoint(config, endpointUrl, false);
|
|
#pragma warning restore CS0618
|
|
}
|
|
|
|
using var client = DiscoveryClient.Create(new Uri(endpointUrl));
|
|
var allEndpoints = client.GetEndpoints(null);
|
|
|
|
EndpointDescription? best = null;
|
|
|
|
foreach (var ep in allEndpoints)
|
|
{
|
|
if (ep.SecurityMode != requestedMode)
|
|
continue;
|
|
|
|
if (best == null)
|
|
{
|
|
best = ep;
|
|
continue;
|
|
}
|
|
|
|
if (ep.SecurityPolicyUri == SecurityPolicies.Basic256Sha256)
|
|
best = ep;
|
|
}
|
|
|
|
if (best == null)
|
|
{
|
|
var available = string.Join(", ", allEndpoints.Select(e => $"{e.SecurityMode}/{e.SecurityPolicyUri}"));
|
|
throw new InvalidOperationException(
|
|
$"No endpoint found with security mode '{requestedMode}'. Available endpoints: {available}");
|
|
}
|
|
|
|
// Rewrite endpoint URL hostname to match user-supplied hostname
|
|
var serverUri = new Uri(best.EndpointUrl);
|
|
var requestedUri = new Uri(endpointUrl);
|
|
if (serverUri.Host != requestedUri.Host)
|
|
{
|
|
var builder = new UriBuilder(best.EndpointUrl) { Host = requestedUri.Host };
|
|
best.EndpointUrl = builder.ToString();
|
|
Logger.Debug("Rewrote endpoint host from {ServerHost} to {RequestedHost}", serverUri.Host, requestedUri.Host);
|
|
}
|
|
|
|
return best;
|
|
}
|
|
}
|