diff --git a/Directory.Packages.props b/Directory.Packages.props index 07591140..17b9745e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -74,7 +74,7 @@ - + diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OpcUaApplicationHost.cs b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OpcUaApplicationHost.cs index 5bd459dd..77ca866b 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OpcUaApplicationHost.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OpcUaApplicationHost.cs @@ -1,3 +1,4 @@ +using System.Text; using Microsoft.Extensions.Logging; using Opc.Ua; using Opc.Ua.Configuration; @@ -125,7 +126,10 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable public async Task StartAsync(StandardServer server, CancellationToken cancellationToken) { _server = server; - _application = new ApplicationInstance + // 1.5.378 requires an ITelemetryContext on the ApplicationInstance ctor (the parameterless ctor + // is obsolete). DefaultTelemetry.Create wires the SDK's internal logging; an empty builder keeps + // the SDK's trace off our ILogger (the host keeps its own _logger) — sufficient for the bootstrap. + _application = new ApplicationInstance(DefaultTelemetry.Create(_ => { })) { ApplicationName = _options.ApplicationName, ApplicationType = ApplicationType.Server, @@ -134,7 +138,7 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable _ = await BuildConfigurationAsync(cancellationToken); await EnsureApplicationCertificateAsync(cancellationToken).ConfigureAwait(false); - await _application.Start(server).ConfigureAwait(false); + await _application.StartAsync(server).ConfigureAwait(false); AttachUserAuthenticator(); PopulateServerArray(); @@ -223,7 +227,7 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable } } - private void OnImpersonateUser(Session session, ImpersonateEventArgs args) => + private void OnImpersonateUser(ISession session, ImpersonateEventArgs args) => HandleImpersonation(_userAuthenticator, args, _logger); /// @@ -248,7 +252,10 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable string password; try { - password = token.DecryptedPassword ?? string.Empty; + // 1.5.378 exposes DecryptedPassword as raw bytes (was string); UserName token passwords + // are UTF-8 on the wire. + var decryptedBytes = token.DecryptedPassword; + password = decryptedBytes is null ? string.Empty : Encoding.UTF8.GetString(decryptedBytes); } catch (Exception ex) { @@ -299,8 +306,8 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable { // silent: false → SDK logs cert creation events through its own trace plumbing. // minimumKeySize/lifetimeInMonths: 0 → use SDK defaults (2048-bit, 12-month lifetime). - var ok = await _application!.CheckApplicationInstanceCertificate( - silent: false, minimumKeySize: 0, lifeTimeInMonths: 0, ct: cancellationToken).ConfigureAwait(false); + var ok = await _application!.CheckApplicationInstanceCertificatesAsync( + false, null, cancellationToken).ConfigureAwait(false); if (!ok) { throw new InvalidOperationException( @@ -313,7 +320,7 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable { if (!string.IsNullOrWhiteSpace(_options.ApplicationConfigPath)) { - return await _application!.LoadApplicationConfiguration(_options.ApplicationConfigPath, silent: true); + return await _application!.LoadApplicationConfigurationAsync(_options.ApplicationConfigPath, true, ct); } var serverConfig = new ServerConfiguration @@ -358,7 +365,7 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable TraceConfiguration = new TraceConfiguration(), }; - await config.Validate(ApplicationType.Server).ConfigureAwait(false); + await config.ValidateAsync(ApplicationType.Server, ct).ConfigureAwait(false); _application!.ApplicationConfiguration = config; return config; } @@ -430,7 +437,7 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable } /// Disposes the application host and cleans up resources. - public ValueTask DisposeAsync() + public async ValueTask DisposeAsync() { if (_impersonateHandler is not null && _server?.CurrentInstance?.SessionManager is { } sessionManager) { @@ -439,8 +446,11 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable } _impersonateHandler = null; - try { _application?.Stop(); } - catch (Exception ex) { _logger.LogWarning(ex, "OpcUaApplicationHost: Stop threw on dispose"); } - return ValueTask.CompletedTask; + if (_application is not null) + { + // 1.5.378: ApplicationInstance.Stop() → StopAsync(). + try { await _application.StopAsync().ConfigureAwait(false); } + catch (Exception ex) { _logger.LogWarning(ex, "OpcUaApplicationHost: Stop threw on dispose"); } + } } } diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/ZB.MOM.WW.OtOpcUa.OpcUaServer.csproj b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/ZB.MOM.WW.OtOpcUa.OpcUaServer.csproj index c94c0b29..54dae8ad 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/ZB.MOM.WW.OtOpcUa.OpcUaServer.csproj +++ b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/ZB.MOM.WW.OtOpcUa.OpcUaServer.csproj @@ -7,11 +7,12 @@ - - + + diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/DualEndpointTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/DualEndpointTests.cs index 5b529eb0..8de20862 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/DualEndpointTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/DualEndpointTests.cs @@ -7,7 +7,6 @@ using Opc.Ua.Server; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.OpcUaServer; -using ClientSession = Opc.Ua.Client.Session; namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests; @@ -85,23 +84,27 @@ public sealed class DualEndpointTests }, ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60_000 }, }; - await appConfig.Validate(ApplicationType.Client); + await appConfig.ValidateAsync(ApplicationType.Client, default); appConfig.CertificateValidator.CertificateValidation += (_, e) => e.Accept = true; - var endpoint = CoreClientUtils.SelectEndpoint(appConfig, endpointUrl, useSecurity: false); + // 1.5.378: the discovery/session/read client surface moved to async. + var endpoint = await CoreClientUtils.SelectEndpointAsync( + appConfig, endpointUrl, false, DefaultTelemetry.Create(_ => { }), default); var endpointConfiguration = EndpointConfiguration.Create(appConfig); var configuredEndpoint = new ConfiguredEndpoint(null, endpoint, endpointConfiguration); - using var session = await ClientSession.Create( + using var session = await new DefaultSessionFactory(DefaultTelemetry.Create(_ => { })).CreateAsync( appConfig, configuredEndpoint, updateBeforeConnect: false, + checkDomain: false, sessionName: "DualEndpointTests", sessionTimeout: 60_000, identity: new UserIdentity(new AnonymousIdentityToken()), - preferredLocales: null); + preferredLocales: null, + ct: default); - var value = session.ReadValue(VariableIds.Server_ServerArray); + var value = await session.ReadValueAsync(VariableIds.Server_ServerArray, default); return (string[])value.Value; } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests.csproj b/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests.csproj index 3d51e7ad..4f154d1c 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests.csproj +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests.csproj @@ -11,12 +11,11 @@ - - - + + + all diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests/OpcUaApplicationHostImpersonationTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests/OpcUaApplicationHostImpersonationTests.cs index f44bf71d..a7bb5d9e 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests/OpcUaApplicationHostImpersonationTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests/OpcUaApplicationHostImpersonationTests.cs @@ -1,3 +1,4 @@ +using System.Text; using Microsoft.Extensions.Logging.Abstractions; using Opc.Ua; using Opc.Ua.Server; @@ -21,7 +22,7 @@ public sealed class OpcUaApplicationHostImpersonationTests [Fact] public void HandleImpersonation_username_success_sets_identity_and_no_validation_error() { - var token = new UserNameIdentityToken { UserName = "alice", DecryptedPassword = "secret" }; + var token = new UserNameIdentityToken { UserName = "alice", DecryptedPassword = Encoding.UTF8.GetBytes("secret") }; var args = new ImpersonateEventArgs(token, UserNamePolicy, new EndpointDescription()); var authenticator = new RecordingAuthenticator( OpcUaUserAuthResult.Allow("Alice", new[] { "ReadOnly", "WriteOperate" })); @@ -38,7 +39,7 @@ public sealed class OpcUaApplicationHostImpersonationTests [Fact] public void HandleImpersonation_username_denial_sets_validation_error_and_no_identity() { - var token = new UserNameIdentityToken { UserName = "mallory", DecryptedPassword = "wrong" }; + var token = new UserNameIdentityToken { UserName = "mallory", DecryptedPassword = Encoding.UTF8.GetBytes("wrong") }; var args = new ImpersonateEventArgs(token, UserNamePolicy, new EndpointDescription()); var authenticator = new RecordingAuthenticator(OpcUaUserAuthResult.Deny("Invalid credentials")); @@ -68,7 +69,7 @@ public sealed class OpcUaApplicationHostImpersonationTests [Fact] public void HandleImpersonation_authenticator_throwing_results_in_rejection() { - var token = new UserNameIdentityToken { UserName = "bob", DecryptedPassword = "x" }; + var token = new UserNameIdentityToken { UserName = "bob", DecryptedPassword = Encoding.UTF8.GetBytes("x") }; var args = new ImpersonateEventArgs(token, UserNamePolicy, new EndpointDescription()); var authenticator = new ThrowingAuthenticator(new InvalidOperationException("LDAP unreachable")); @@ -82,7 +83,7 @@ public sealed class OpcUaApplicationHostImpersonationTests [Fact] public void HandleImpersonation_null_username_treated_as_empty_string() { - var token = new UserNameIdentityToken { UserName = null, DecryptedPassword = "abc" }; + var token = new UserNameIdentityToken { UserName = null, DecryptedPassword = Encoding.UTF8.GetBytes("abc") }; var args = new ImpersonateEventArgs(token, UserNamePolicy, new EndpointDescription()); var authenticator = new RecordingAuthenticator(OpcUaUserAuthResult.Deny("no user"));