2828 * ======================================================================*/
2929
3030using System ;
31+ using System . Collections ;
3132using System . Collections . Generic ;
3233using System . IO ;
3334using System . Linq ;
4243
4344namespace Quickstarts
4445{
45- public partial class ClientSamples
46+ /// <summary>
47+ /// Wraps connect testing functionality
48+ /// </summary>
49+ public sealed class ConnectTester : IDisposable
4650 {
47- public async Task < bool > RunAsync ( ManualResetEvent quitEvent , CancellationToken ct )
51+ public ConnectTester (
52+ ITelemetryContext telemetry ,
53+ ManualResetEvent quitEvent = null )
54+ {
55+ m_quitEvent = quitEvent ;
56+ m_telemetry = telemetry ;
57+ m_logger = telemetry . CreateLogger < ConnectTester > ( ) ;
58+ }
59+
60+ /// <inheritdoc/>
61+ public void Dispose ( )
62+ {
63+ m_reconnectHandler ? . Dispose ( ) ;
64+ }
65+
66+ /// <summary>
67+ /// Run the tests until cancelled or quit
68+ /// </summary>
69+ /// <param name="ct"></param>
70+ /// <returns></returns>
71+ /// <exception cref="InvalidOperationException"></exception>
72+ public async Task RunAsync ( CancellationToken ct )
4873 {
4974 try
5075 {
@@ -54,7 +79,6 @@ public async Task<bool> RunAsync(ManualResetEvent quitEvent, CancellationToken c
5479 kReconnectPeriodExponentialBackoff ) ;
5580
5681 m_logger . LogInformation ( "OPC UA Security Test Client" ) ;
57- CryptoTrace . Enabled = false ;
5882
5983 // The application name and config file names
6084 const string applicationName = "ConsoleReferenceClient" ;
@@ -95,8 +119,6 @@ public async Task<bool> RunAsync(ManualResetEvent quitEvent, CancellationToken c
95119 kServerUrl ,
96120 ct ) . ConfigureAwait ( false ) ;
97121
98- //endpoints = endpoints.Where(x => x.SecurityPolicyUri == TargetPolicy).ToList();
99-
100122 var endpointConfiguration = EndpointConfiguration . Create ( m_configuration ) ;
101123 var sessionFactory = new DefaultSessionFactory ( m_telemetry ) ;
102124 var userNameidentity = new UserIdentity ( kUserName , new UTF8Encoding ( false ) . GetBytes ( kPassword ) ) ;
@@ -204,54 +226,17 @@ public async Task<bool> RunAsync(ManualResetEvent quitEvent, CancellationToken c
204226 ii . SecurityMode ) ;
205227
206228 m_logger . LogWarning ( "{Line}" , new string ( '=' , 80 ) ) ;
207- //break;
208229 }
209-
210- //break;
211230 }
212231
213232 Console . WriteLine ( "Ctrl-C to stop." ) ;
214- quitEvent . WaitOne ( ) ;
233+ m_quitEvent . WaitOne ( ) ;
215234 }
216235 catch ( Exception e )
217236 {
218237 m_logger . LogError ( "Exception: {Message}" , e . Message ) ;
219238 m_logger . LogTrace ( "StackTrace: {StackTrace}" , e . StackTrace ) ;
220239 }
221-
222- return true ;
223- }
224-
225- private async Task < UserIdentity > LoadUserCertificateAsync (
226- string thumbprint ,
227- string password ,
228- CancellationToken ct )
229- {
230- CertificateTrustList store = m_configuration . SecurityConfiguration . TrustedUserCertificates ;
231- #if NET8_0_OR_GREATER
232- // get user certificate with matching thumbprint
233- X509Certificate2Collection certificates =
234- await store . GetCertificatesAsync ( m_telemetry , ct ) . ConfigureAwait ( false ) ;
235- X509Certificate2 hit = certificates
236- . Find ( X509FindType . FindByThumbprint , thumbprint , false )
237- . FirstOrDefault ( ) ;
238-
239- // create Certificate Identifier
240- var cid = new CertificateIdentifier ( hit )
241- {
242- StorePath = store . StorePath ,
243- StoreType = store . StoreType
244- } ;
245-
246- return await UserIdentity . CreateAsync (
247- cid ,
248- new CertificatePasswordProvider ( new UTF8Encoding ( false ) . GetBytes ( password ) ) ,
249- m_telemetry ,
250- ct ) . ConfigureAwait ( false ) ;
251- #else
252- await Task . Delay ( 1 , ct ) . ConfigureAwait ( false ) ;
253- throw new NotSupportedException ( "User certificate identity is only supported on .NET 8 or greater." ) ;
254- #endif
255240 }
256241
257242 internal async Task < SessionWrapper > RunTestAsync (
@@ -293,7 +278,8 @@ internal async Task<SessionWrapper> RunTestAsync(
293278 wrapper . Session . KeepAliveInterval = 10000 ;
294279 wrapper . Session . KeepAlive += Session_KeepAlive ;
295280
296- ArrayOf < ReferenceDescription > nodes = await BrowseFullAddressSpaceAsync (
281+ var samples = new ClientSamples ( m_telemetry , null , m_quitEvent ) ;
282+ ArrayOf < ReferenceDescription > nodes = await samples . BrowseFullAddressSpaceAsync (
297283 wrapper ,
298284 ObjectIds . ObjectsFolder ,
299285 null ,
@@ -302,6 +288,38 @@ internal async Task<SessionWrapper> RunTestAsync(
302288 return wrapper ;
303289 }
304290
291+ private async Task < UserIdentity > LoadUserCertificateAsync (
292+ string thumbprint ,
293+ string password ,
294+ CancellationToken ct )
295+ {
296+ CertificateTrustList store = m_configuration . SecurityConfiguration . TrustedUserCertificates ;
297+ #if NET8_0_OR_GREATER
298+ // get user certificate with matching thumbprint
299+ X509Certificate2Collection certificates =
300+ await store . GetCertificatesAsync ( m_telemetry , ct ) . ConfigureAwait ( false ) ;
301+ X509Certificate2 hit = certificates
302+ . Find ( X509FindType . FindByThumbprint , thumbprint , false )
303+ . FirstOrDefault ( ) ;
304+
305+ // create Certificate Identifier
306+ var cid = new CertificateIdentifier ( hit )
307+ {
308+ StorePath = store . StorePath ,
309+ StoreType = store . StoreType
310+ } ;
311+
312+ return await UserIdentity . CreateAsync (
313+ cid ,
314+ new CertificatePasswordProvider ( new UTF8Encoding ( false ) . GetBytes ( password ) ) ,
315+ m_telemetry ,
316+ ct ) . ConfigureAwait ( false ) ;
317+ #else
318+ await Task . Delay ( 1 , ct ) . ConfigureAwait ( false ) ;
319+ throw new NotSupportedException ( "User certificate identity is only supported on .NET 8 or greater." ) ;
320+ #endif
321+ }
322+
305323 private static async ValueTask < ArrayOf < EndpointDescription > > GetEndpointsAsync (
306324 ApplicationConfiguration application ,
307325 string discoveryUrl ,
@@ -425,7 +443,7 @@ private void Client_ReconnectComplete(object sender, EventArgs e)
425443 ) ;
426444 ISession session = m_wrapper . Session ;
427445 m_wrapper = new SessionWrapper { Session = m_reconnectHandler . Session } ;
428- Utils . SilentDispose ( session ) ;
446+ session ? . Dispose ( ) ;
429447 }
430448 else
431449 {
@@ -469,6 +487,9 @@ internal sealed class SessionWrapper : IUAClient
469487 private SessionReconnectHandler m_reconnectHandler ;
470488 private ApplicationConfiguration m_configuration ;
471489 private SessionWrapper m_wrapper ;
490+ private ILogger m_logger ;
491+ private ITelemetryContext m_telemetry ;
492+ private readonly ManualResetEvent m_quitEvent ;
472493
473494 private const string kServerUrl = "opc.tcp://localhost:62541" ;
474495 private const string kUserName = "sysadmin" ;
0 commit comments