/* ======================================================================== * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.IO; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Opc.Ua.Configuration; using Opc.Ua.Gds.Client; using Opc.Ua.Security.Certificates; namespace Opc.Ua.Gds.Tests { public sealed class ServerConfigurationPushTestClient : IDisposable { public ServerPushConfigurationClient PushClient { get; private set; } = null!; public static bool AutoAccept { get; set; } public ServerConfigurationPushTestClient(bool autoAccept, ITelemetryContext telemetry) { AutoAccept = autoAccept; m_telemetry = telemetry; m_logger = telemetry.CreateLogger(); } public IUserIdentity AppUser { get; private set; } = null!; public IUserIdentity SysAdminUser { get; private set; } = null!; public string TempStorePath { get; private set; } = null!; public ApplicationConfiguration Config { get; private set; } = null!; public string EndpointUrl { get; private set; } = null!; public void Dispose() { PushClient?.Dispose(); m_application?.DisposeAsync().AsTask().GetAwaiter().GetResult(); m_application = null!; } public async Task LoadClientConfigurationAsync(int port = -1) { ApplicationInstance.MessageDlg = new ApplicationMessageDlg(m_logger); await DisconnectClientAsync().ConfigureAwait(false); if (m_application != null) { await m_application.DisposeAsync().ConfigureAwait(false); m_application = null!; } m_application = new ApplicationInstance(m_telemetry) { ApplicationName = "Server Configuration Push Test Client", ApplicationType = ApplicationType.Client, ConfigSectionName = "Opc.Ua.ServerConfigurationPushTestClient" }; #if USE_FILE_CONFIG // load the application configuration. Config = await m_application.LoadApplicationConfigurationAsync(false) .ConfigureAwait(false); #else string root = Path.Combine(Path.GetTempPath(), "OPC"); string pkiRoot = Path.Combine(root, "pki"); var clientConfig = new ServerConfigurationPushTestClientConfiguration { ServerUrl = "opc.tcp://localhost:58810/GlobalDiscoveryTestServer", AppUserName = string.Empty, AppPassword = string.Empty, SysAdminUserName = "sysadmin", SysAdminPassword = "demo", TempStorePath = Path.Combine(pkiRoot, "temp") }; var transportQuotas = new TransportQuotas { OperationTimeout = 120000, MaxStringLength = 1048576, MaxByteStringLength = 1048576, MaxArrayLength = 65535, MaxMessageSize = 4194304, MaxBufferSize = 65535, ChannelLifetime = 300000, SecurityTokenLifetime = 3600000 }; ArrayOf applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates( "CN=Server Configuration Push Test Client, O=OPC Foundation", CertificateStoreType.Directory, pkiRoot); // build the application configuration. Config = await m_application .Build( "urn:localhost:opcfoundation.org:ServerConfigurationPushTestClient", "http://opcfoundation.org/UA/ServerConfigurationPushTestClient") .SetTransportQuotas(transportQuotas) .AsClient() .AddSecurityConfiguration(applicationCerts, pkiRoot, pkiRoot) .SetAutoAcceptUntrustedCertificates(true) .SetRejectSHA1SignedCertificates(false) .SetRejectUnknownRevocationStatus(true) .SetMinimumCertificateKeySize(1024) .AddExtension(null, clientConfig) .CreateAsync() .ConfigureAwait(false); #endif // check the application certificate. bool haveAppCertificate = await m_application .CheckApplicationInstanceCertificatesAsync(true) .ConfigureAwait(false); if (!haveAppCertificate) { throw new InvalidOperationException("Application instance certificate invalid!"); } Config.CertificateManager?.AcceptError = AcceptCertificate; ServerConfigurationPushTestClientConfiguration clientConfiguration = m_application.ApplicationConfiguration! .ParseExtension()!; PushClient = new ServerPushConfigurationClient(m_application.ApplicationConfiguration); EndpointUrl = TestUtils.PatchOnlyGDSEndpointUrlPort( clientConfiguration!.ServerUrl, port); if (string.IsNullOrEmpty(clientConfiguration.AppUserName)) { AppUser = new UserIdentity(); } else { AppUser = new UserIdentity( clientConfiguration.AppUserName, Encoding.UTF8.GetBytes(clientConfiguration.AppPassword)); } SysAdminUser = new UserIdentity( clientConfiguration.SysAdminUserName, Encoding.UTF8.GetBytes(clientConfiguration.SysAdminPassword)); TempStorePath = clientConfiguration.TempStorePath; } public async Task DisconnectClientAsync() { m_logger.LogInformation("Disconnect Session. Waiting for exit..."); if (PushClient != null) { ServerPushConfigurationClient pushClient = PushClient; PushClient = null!; try { await pushClient.DisconnectAsync().ConfigureAwait(false); } finally { await pushClient.DisposeAsync().ConfigureAwait(false); } } } private bool AcceptCertificate(Certificate certificate, ServiceResult error) { if (error.StatusCode == StatusCodes.BadCertificateUntrusted) { if (AutoAccept) { if (m_logger.IsEnabled(LogLevel.Information)) { m_logger.LogInformation("Accepted Certificate: {Subject}", certificate.Subject); } return true; } if (m_logger.IsEnabled(LogLevel.Information)) { m_logger.LogInformation("Rejected Certificate: {Subject}", certificate.Subject); } } return false; } /// /// Sets the PushClient's endpoint by SecurityPolicy URI and optional MessageSecurityMode. /// /// /// public async Task ConnectAsync( string securityPolicyUri, MessageSecurityMode securityMode = MessageSecurityMode.SignAndEncrypt) { if (Config == null) { throw new InvalidOperationException("Client configuration must be loaded before setting endpoint."); } await PushClient.DisconnectAsync().ConfigureAwait(false); var endpointConfiguration = EndpointConfiguration.Create(Config); using DiscoveryClient discoveryClient = await DiscoveryClient.CreateAsync( new Uri(EndpointUrl), endpointConfiguration, m_telemetry).ConfigureAwait(false); ArrayOf endpoints = await discoveryClient.GetEndpointsAsync(default).ConfigureAwait(false); await discoveryClient.CloseAsync().ConfigureAwait(false); EndpointDescription? selectedEndpoint = null; foreach (EndpointDescription ep in endpoints) { if (ep.SecurityPolicyUri == securityPolicyUri && ep.SecurityMode == securityMode) { selectedEndpoint = ep; break; } } if (selectedEndpoint == null) { throw new ArgumentException( $"No endpoint found for SecurityPolicyUri '{securityPolicyUri}' and SecurityMode '{securityMode}'."); } PushClient.Endpoint = new ConfiguredEndpoint(null, selectedEndpoint, endpointConfiguration); await PushClient.ConnectAsync().ConfigureAwait(false); } private ApplicationInstance m_application = null!; private readonly ITelemetryContext m_telemetry; private readonly ILogger m_logger; } /// /// Stores the configuration the data access node manager. /// [DataType(Namespace = Ua.Namespaces.OpcUaConfig)] public partial class ServerConfigurationPushTestClientConfiguration { [DataTypeField(Order = 1)] public string ServerUrl { get; set; } = null!; [DataTypeField(Order = 2)] public string AppUserName { get; set; } = null!; [DataTypeField(Order = 3)] public string AppPassword { get; set; } = null!; [DataTypeField(Order = 4)] public string SysAdminUserName { get; set; } = null!; [DataTypeField(Order = 5)] public string SysAdminPassword { get; set; } = null!; [DataTypeField(Order = 6)] public string TempStorePath { get; set; } = null!; } }