Files
2022-03-30 11:30:34 +02:00

319 lines
12 KiB
C#

/* ========================================================================
* Copyright (c) 2005-2020 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.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using Opc.Ua.Configuration;
using Opc.Ua.Server.Tests;
namespace Opc.Ua.Client.Tests
{
/// <summary>
/// Client fixture for tests.
/// </summary>
public class ClientFixture
{
private NUnitTraceLogger m_traceLogger;
public ApplicationConfiguration Config { get; private set; }
public ConfiguredEndpoint Endpoint { get; private set; }
public string EndpointUrl { get; private set; }
public string ReverseConnectUri { get; private set; }
public ReverseConnectManager ReverseConnectManager { get; private set; }
public uint SessionTimeout { get; set; } = 10000;
public int OperationTimeout { get; set; } = 10000;
public int TraceMasks { get; set; } = Utils.TraceMasks.Error | Utils.TraceMasks.StackTrace | Utils.TraceMasks.Security | Utils.TraceMasks.Information;
#region Public Methods
/// <summary>
/// Load the default client configuration.
/// </summary>
public async Task LoadClientConfiguration(string pkiRoot = null, string clientName = "TestClient")
{
ApplicationInstance application = new ApplicationInstance {
ApplicationName = clientName
};
pkiRoot = pkiRoot ?? Path.Combine("%LocalApplicationData%", "OPC", "pki");
// build the application configuration.
Config = await application
.Build(
"urn:localhost:opcfoundation.org:" + clientName,
"http://opcfoundation.org/UA/" + clientName)
.AsClient()
.AddSecurityConfiguration(
"CN=" + clientName + ", O=OPC Foundation, DC=localhost",
pkiRoot)
.SetAutoAcceptUntrustedCertificates(true)
.SetRejectSHA1SignedCertificates(false)
.SetMinimumCertificateKeySize(1024)
.SetOutputFilePath(Path.Combine(pkiRoot, "Logs", "Opc.Ua.Client.Tests.log.txt"))
.SetTraceMasks(TraceMasks)
.Create().ConfigureAwait(false);
// check the application certificate.
bool haveAppCertificate = await application.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false);
if (!haveAppCertificate)
{
throw new Exception("Application instance certificate invalid!");
}
ReverseConnectManager = new ReverseConnectManager();
}
/// <summary>
/// Start a host for reverse connections on random port.
/// </summary>
public async Task StartReverseConnectHost()
{
Random random = new Random();
int testPort = ServerFixtureUtils.GetNextFreeIPPort();
bool retryStartServer = false;
int serverStartRetries = 25;
do
{
try
{
var reverseConnectUri = new Uri("opc.tcp://localhost:" + testPort);
ReverseConnectManager.AddEndpoint(reverseConnectUri);
ReverseConnectManager.StartService(Config);
ReverseConnectUri = reverseConnectUri.ToString();
}
catch (ServiceResultException sre)
{
serverStartRetries--;
if (serverStartRetries == 0 ||
sre.StatusCode != StatusCodes.BadNoCommunication)
{
throw;
}
testPort = random.Next(ServerFixtureUtils.MinTestPort, ServerFixtureUtils.MaxTestPort);
retryStartServer = true;
}
await Task.Delay(random.Next(100, 1000)).ConfigureAwait(false);
} while (retryStartServer);
}
/// <summary>
/// Connects the specified endpoint URL.
/// </summary>
/// <param name="endpointUrl">The endpoint URL.</param>
public async Task<Session> Connect(string endpointUrl)
{
if (String.IsNullOrEmpty(endpointUrl))
{
throw new ArgumentNullException(nameof(endpointUrl));
}
if (!Uri.IsWellFormedUriString(endpointUrl, UriKind.Absolute))
{
throw new ArgumentException(endpointUrl + " is not a valid URL.", nameof(endpointUrl));
}
bool serverHalted;
do
{
serverHalted = false;
try
{
EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint(Config, endpointUrl, true);
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(Config);
ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);
return await ConnectAsync(endpoint).ConfigureAwait(false);
}
catch (ServiceResultException e)
{
if (e.StatusCode == StatusCodes.BadServerHalted)
{
serverHalted = true;
await Task.Delay(1000).ConfigureAwait(false);
}
else
{
throw;
}
}
} while (serverHalted);
throw new ServiceResultException(StatusCodes.BadNoCommunication);
}
/// <summary>
/// Connects the url endpoint with specified security profile.
/// </summary>
public async Task<Session> ConnectAsync(Uri url, string securityProfile, EndpointDescriptionCollection endpoints = null)
{
return await ConnectAsync(await GetEndpointAsync(url, securityProfile, endpoints).ConfigureAwait(false)).ConfigureAwait(false);
}
/// <summary>
/// Connects the specified endpoint.
/// </summary>
/// <param name="endpoint">The configured endpoint.</param>
public async Task<Session> ConnectAsync(ConfiguredEndpoint endpoint)
{
if (endpoint == null)
{
endpoint = Endpoint;
if (endpoint == null)
{
throw new ArgumentNullException(nameof(endpoint));
}
}
var session = await Session.Create(
Config, endpoint, false, false,
Config.ApplicationName, SessionTimeout, null, null).ConfigureAwait(false);
Endpoint = session.ConfiguredEndpoint;
session.KeepAlive += Session_KeepAlive;
session.ReturnDiagnostics = DiagnosticsMasks.SymbolicIdAndText;
EndpointUrl = session.ConfiguredEndpoint.EndpointUrl.ToString();
return session;
}
/// <summary>
/// Get configured endpoint from url with security profile.
/// </summary>
/// <param name="url"></param>
/// <param name="securityPolicy"></param>
/// <param name="endpoints"></param>
public async Task<ConfiguredEndpoint> GetEndpointAsync(
Uri url,
string securityPolicy,
EndpointDescriptionCollection endpoints = null)
{
if (endpoints == null)
{
endpoints = await GetEndpoints(url).ConfigureAwait(false);
}
var endpointDescription = SelectEndpoint(Config, endpoints, url, securityPolicy);
if (endpointDescription == null)
{
Assert.Ignore("The endpoint is not supported by the server.");
}
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(Config);
endpointConfiguration.OperationTimeout = OperationTimeout;
return new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);
}
/// <summary>
/// Select a security endpoint from description.
/// </summary>
public static EndpointDescription SelectEndpoint(
ApplicationConfiguration configuration,
EndpointDescriptionCollection endpoints,
Uri url,
string securityPolicy)
{
EndpointDescription selectedEndpoint = null;
// select the best endpoint to use based on the selected URL and the UseSecurity checkbox.
foreach (var endpoint in endpoints)
{
// check for a match on the URL scheme.
if (endpoint.EndpointUrl.StartsWith(url.Scheme))
{
// skip unsupported security policies
if (SecurityPolicies.GetDisplayName(endpoint.SecurityPolicyUri) == null)
{
continue;
}
// pick the first available endpoint by default.
if (selectedEndpoint == null &&
securityPolicy.Equals(endpoint.SecurityPolicyUri))
{
selectedEndpoint = endpoint;
continue;
}
if (selectedEndpoint?.SecurityMode < endpoint.SecurityMode &&
securityPolicy.Equals(endpoint.SecurityPolicyUri))
{
selectedEndpoint = endpoint;
}
}
}
// return the selected endpoint.
return selectedEndpoint;
}
/// <summary>
/// Get endpoints from discovery endpoint.
/// </summary>
/// <param name="url">The url of the discovery endpoint.</param>
public async Task<EndpointDescriptionCollection> GetEndpoints(Uri url)
{
var endpointConfiguration = EndpointConfiguration.Create();
endpointConfiguration.OperationTimeout = OperationTimeout;
using (var client = DiscoveryClient.Create(url, endpointConfiguration))
{
return await client.GetEndpointsAsync(null).ConfigureAwait(false);
}
}
/// <summary>
/// Connect the nunit writer with the logger.
/// </summary>
public void SetTraceOutput(TextWriter writer)
{
if (m_traceLogger == null)
{
m_traceLogger = NUnitTraceLogger.Create(writer, Config, TraceMasks);
}
else
{
m_traceLogger.SetWriter(writer);
}
}
#endregion
#region Private Methods
private void Session_KeepAlive(Session session, KeepAliveEventArgs e)
{
if (ServiceResult.IsBad(e.Status))
{
session?.Dispose();
}
}
#endregion
}
}