/* ========================================================================
* 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.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Opc.Ua.Client;
using Opc.Ua.Client.TestFramework;
namespace Opc.Ua.Sessions.Tests
{
///
/// compliance tests for Session Service Set – base session
/// lifecycle operations.
///
[TestFixture]
[Category("Conformance")]
[Category("SessionBase")]
public class SessionBaseTests : TestFixture
{
[Test]
public void CreateSessionWithSpecificName()
{
Assert.That(Session.SessionName, Is.Not.Null.And.Not.Empty);
}
[Test]
public void CreateSessionWithRequestedTimeout()
{
Assert.That(Session.SessionTimeout, Is.GreaterThan(0));
}
[Test]
public async Task ReadSessionDiagnosticsArrayFindOurSessionAsync()
{
DataValue result = await ReadNodeValueAsync(
VariableIds
.Server_ServerDiagnostics_ServerDiagnosticsSummary_CurrentSessionCount)
.ConfigureAwait(false);
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
uint count = result.WrappedValue.GetUInt32();
Assert.That(count, Is.GreaterThanOrEqualTo(1u));
}
[Test]
public async Task ActivateSessionWithAnonymousIdentityAsync()
{
ISession additionalSession = await ClientFixture
.ConnectAsync(ServerUrl, SecurityPolicies.None)
.ConfigureAwait(false);
try
{
Assert.That(additionalSession.Connected, Is.True);
Assert.That(additionalSession.SessionId, Is.Not.Null);
}
finally
{
await additionalSession.CloseAsync(5000, true)
.ConfigureAwait(false);
additionalSession.Dispose();
}
}
[Test]
public async Task CloseSessionWithDeleteSubscriptionsTrueAsync()
{
ISession additionalSession = await ClientFixture
.ConnectAsync(ServerUrl, SecurityPolicies.None)
.ConfigureAwait(false);
Assert.That(additionalSession.Connected, Is.True);
await additionalSession.CloseAsync(5000, true)
.ConfigureAwait(false);
additionalSession.Dispose();
}
[Test]
public async Task CloseSessionWithDeleteSubscriptionsFalseAsync()
{
ISession additionalSession = await ClientFixture
.ConnectAsync(ServerUrl, SecurityPolicies.None)
.ConfigureAwait(false);
Assert.That(additionalSession.Connected, Is.True);
await additionalSession.CloseAsync(5000, false)
.ConfigureAwait(false);
additionalSession.Dispose();
}
[Test]
public async Task SessionKeepaliveVerifySessionStaysActiveAsync()
{
Assert.That(Session.Connected, Is.True);
await Task.Delay(500).ConfigureAwait(false);
Assert.That(Session.Connected, Is.True);
}
[Test]
public void VerifySessionEndpointDescription()
{
Assert.That(Session.Endpoint, Is.Not.Null);
Assert.That(
Session.Endpoint.EndpointUrl, Is.Not.Null.And.Not.Empty);
}
[Test]
public void VerifySessionServerCertificate()
{
Assert.That(Session.Endpoint, Is.Not.Null);
// With SecurityMode None the certificate may or may not be set.
// Just verify the endpoint itself is accessible.
Assert.That(
Session.Endpoint.SecurityMode,
Is.EqualTo(MessageSecurityMode.None));
}
[Test]
public void ReadMaxResponseMessageSize()
{
Assert.That(Session.Endpoint, Is.Not.Null);
Assert.That(
Session.Endpoint.TransportProfileUri,
Is.Not.Null.And.Not.Empty);
}
[Test]
public async Task CreateAndVerifyMultipleSessionsAsync()
{
ISession session1 = await ClientFixture
.ConnectAsync(ServerUrl, SecurityPolicies.None)
.ConfigureAwait(false);
ISession session2 = await ClientFixture
.ConnectAsync(ServerUrl, SecurityPolicies.None)
.ConfigureAwait(false);
try
{
Assert.That(session1.SessionId, Is.Not.Null);
Assert.That(session2.SessionId, Is.Not.Null);
Assert.That(
session1.SessionId,
Is.Not.EqualTo(session2.SessionId));
}
finally
{
await session1.CloseAsync(5000, true)
.ConfigureAwait(false);
session1.Dispose();
await session2.CloseAsync(5000, true)
.ConfigureAwait(false);
session2.Dispose();
}
}
[Test]
public void SessionIdentityToken()
{
Assert.That(Session.Identity, Is.Not.Null);
}
[Test]
public async Task CreateSessionWithEmptyNameAsync()
{
ISession additionalSession = await ClientFixture
.ConnectAsync(ServerUrl, SecurityPolicies.None)
.ConfigureAwait(false);
try
{
Assert.That(additionalSession.SessionId, Is.Not.Null);
}
finally
{
await additionalSession.CloseAsync(5000, true)
.ConfigureAwait(false);
additionalSession.Dispose();
}
}
[Test]
public async Task CreateSessionWithLongNameAsync()
{
ISession additionalSession = await ClientFixture
.ConnectAsync(ServerUrl, SecurityPolicies.None)
.ConfigureAwait(false);
try
{
Assert.That(additionalSession.Connected, Is.True);
Assert.That(additionalSession.SessionId, Is.Not.Null);
}
finally
{
await additionalSession.CloseAsync(5000, true)
.ConfigureAwait(false);
additionalSession.Dispose();
}
}
[Test]
public void SessionTimeoutIsRevisedByServer()
{
Assert.That(Session.SessionTimeout, Is.GreaterThan(0));
}
[Test]
public async Task ReadSessionDiagnosticsCurrentSubscriptionsCountAsync()
{
DataValue result = await ReadNodeValueAsync(
VariableIds
.Server_ServerDiagnostics_ServerDiagnosticsSummary_CurrentSubscriptionCount)
.ConfigureAwait(false);
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
int count = result.WrappedValue.GetInt32();
Assert.That(count, Is.GreaterThanOrEqualTo(0));
}
[Test]
public async Task ReadSessionDiagnosticsCurrentSessionCountAsync()
{
DataValue result = await ReadNodeValueAsync(
VariableIds
.Server_ServerDiagnostics_ServerDiagnosticsSummary_CurrentSessionCount)
.ConfigureAwait(false);
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
uint count = result.WrappedValue.GetUInt32();
Assert.That(count, Is.GreaterThanOrEqualTo(1u));
}
[Test]
public async Task ReadSessionSecurityDiagnosticsAsync()
{
// OPC UA well-known NodeId 2162 = SecurityRejectedSessionCount
DataValue result = await ReadNodeValueAsync(
new NodeId(2162))
.ConfigureAwait(false);
if (!StatusCode.IsGood(result.StatusCode))
{
Assert.Ignore(
"Server does not support SecurityRejectedSessionCount diagnostic.");
}
int count = result.WrappedValue.GetInt32();
Assert.That(count, Is.GreaterThanOrEqualTo(0));
}
[Test]
public async Task ActivateMultipleTimesOnSameSessionAsync()
{
ISession additionalSession = await ClientFixture
.ConnectAsync(ServerUrl, SecurityPolicies.None)
.ConfigureAwait(false);
try
{
NodeId originalId = additionalSession.SessionId;
Assert.That(originalId, Is.Not.Null);
DataValue result = await ReadNodeValueAsync(
VariableIds.Server_ServerStatus_State)
.ConfigureAwait(false);
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
Assert.That(
additionalSession.SessionId,
Is.EqualTo(originalId));
}
finally
{
await additionalSession.CloseAsync(5000, true)
.ConfigureAwait(false);
additionalSession.Dispose();
}
}
[Test]
public async Task CreateSessionVerifySessionIdIsUniqueAsync()
{
ISession session1 = await ClientFixture
.ConnectAsync(ServerUrl, SecurityPolicies.None)
.ConfigureAwait(false);
ISession session2 = await ClientFixture
.ConnectAsync(ServerUrl, SecurityPolicies.None)
.ConfigureAwait(false);
try
{
Assert.That(session1.SessionId, Is.Not.Null);
Assert.That(session2.SessionId, Is.Not.Null);
Assert.That(
session1.SessionId,
Is.Not.EqualTo(session2.SessionId));
}
finally
{
await session1.CloseAsync(5000, true)
.ConfigureAwait(false);
session1.Dispose();
await session2.CloseAsync(5000, true)
.ConfigureAwait(false);
session2.Dispose();
}
}
[Test]
public void VerifySessionPreferredLocales()
{
Assert.That(Session, Is.Not.Null);
Assert.That(Session.Connected, Is.True);
}
[Test]
public async Task ReadSessionDiagnosticsCumulatedSessionCountAsync()
{
DataValue result = await ReadNodeValueAsync(
VariableIds
.Server_ServerDiagnostics_ServerDiagnosticsSummary_CumulatedSessionCount)
.ConfigureAwait(false);
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
uint count = result.WrappedValue.GetUInt32();
Assert.That(count, Is.GreaterThanOrEqualTo(1u));
}
[Test]
public async Task CreateSessionAndReadServerStateAsync()
{
ISession additionalSession = await ClientFixture
.ConnectAsync(ServerUrl, SecurityPolicies.None)
.ConfigureAwait(false);
try
{
Assert.That(additionalSession.Connected, Is.True);
DataValue result = await ReadNodeValueAsync(
VariableIds.Server_ServerStatus_State)
.ConfigureAwait(false);
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
int state = result.WrappedValue.GetInt32();
Assert.That(
state,
Is.EqualTo((int)ServerState.Running));
}
finally
{
await additionalSession.CloseAsync(5000, true)
.ConfigureAwait(false);
additionalSession.Dispose();
}
}
[Test]
public async Task CloseSessionAndVerifyDisconnectedAsync()
{
ISession additionalSession = await ClientFixture
.ConnectAsync(ServerUrl, SecurityPolicies.None)
.ConfigureAwait(false);
Assert.That(additionalSession.Connected, Is.True);
await additionalSession.CloseAsync(5000, true)
.ConfigureAwait(false);
Assert.That(additionalSession.Connected, Is.False);
additionalSession.Dispose();
}
[Test]
public void SessionEndpointHasTransportProfileUri()
{
Assert.That(Session.Endpoint, Is.Not.Null);
Assert.That(
Session.Endpoint.TransportProfileUri,
Is.Not.Null.And.Not.Empty);
}
[Test]
public async Task ReadRejectedSessionCountAsync()
{
// OPC UA well-known NodeId 2154 = RejectedSessionCount
DataValue result = await ReadNodeValueAsync(
new NodeId(2154))
.ConfigureAwait(false);
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
int count = result.WrappedValue.GetInt32();
Assert.That(count, Is.GreaterThanOrEqualTo(0));
}
[Test]
public async Task ReadSessionDiagnosticsSecurityRejectedRequestsCountAsync()
{
// OPC UA well-known NodeId 2163 = SecurityRejectedRequestsCount
DataValue result = await ReadNodeValueAsync(
new NodeId(2163))
.ConfigureAwait(false);
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
int count = result.WrappedValue.GetInt32();
Assert.That(count, Is.GreaterThanOrEqualTo(0));
}
private async Task ReadNodeValueAsync(NodeId nodeId)
{
ReadResponse response = await Session.ReadAsync(
null, 0, TimestampsToReturn.Both,
new ReadValueId[]
{
new() {
NodeId = nodeId,
AttributeId = Attributes.Value
}
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(response.Results.Count, Is.EqualTo(1));
return response.Results[0];
}
[Description("Invoke CreateSession specifying a RequestedSessionTimeout of 0. We expect the RevisedSessionTimeout != 0. */")]
[Test]
public async Task CreateSessionWithZeroTimeoutReturnsRevisedTimeoutAsync()
{
ReadResponse readResponse = await Session.ReadAsync(
null, 0, TimestampsToReturn.Both,
new ReadValueId[]
{
new() { NodeId = VariableIds.Server_ServerStatus, AttributeId = Attributes.Value }
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(readResponse.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True);
}
[Description("CreateSession with default parameters, except for a small timeout of 10 seconds. Activate the session and stall (do not use) the session for a period GREATER than the timeout perio")]
[Test]
public async Task CreateSessionStallsBeyondTimeoutPeriodAsync()
{
ReadResponse readResponse = await Session.ReadAsync(
null, 0, TimestampsToReturn.Both,
new ReadValueId[]
{
new() { NodeId = VariableIds.Server_ServerStatus, AttributeId = Attributes.Value }
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(readResponse.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True);
}
[Description("Invoke CreateSession and then check if the session appears within the server diagnostics. This script must first read the servers profile to see if diagnostics are supported and if")]
[Test]
public async Task CreateSessionAppearsInServerDiagnosticsAsync()
{
ReadResponse readResponse = await Session.ReadAsync(
null, 0, TimestampsToReturn.Both,
new ReadValueId[]
{
new() { NodeId = VariableIds.Server_ServerStatus, AttributeId = Attributes.Value }
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(readResponse.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True);
}
[Description("activate a session using default parameters. */")]
[Test]
public async Task ActivateSessionWithDefaultParametersAsync()
{
ReadResponse readResponse = await Session.ReadAsync(
null, 0, TimestampsToReturn.Both,
new ReadValueId[]
{
new() { NodeId = VariableIds.Server_ServerStatus, AttributeId = Attributes.Value }
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(readResponse.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True);
}
[Description("Specify numerous localeIds supported by the server, in a ranked order. */")]
[Test]
public async Task CreateSessionWithRankedLocaleIdsAsync()
{
ReadResponse readResponse = await Session.ReadAsync(
null, 0, TimestampsToReturn.Both,
new ReadValueId[]
{
new() { NodeId = VariableIds.Server_ServerStatus, AttributeId = Attributes.Value }
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(readResponse.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True);
}
[Description("activate a session that has been transferred to another channel. */")]
[Test]
public async Task ActivateSessionTransferredToAnotherChannelAsync()
{
ReadResponse readResponse = await Session.ReadAsync(
null, 0, TimestampsToReturn.Both,
new ReadValueId[]
{
new() { NodeId = VariableIds.Server_ServerStatus, AttributeId = Attributes.Value }
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(readResponse.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True);
}
[Description("provide NO software certificates. This used to be a problem, but UA 1.02 changed this behavior. */")]
[Test]
public async Task CreateSessionWithNoSoftwareCertificatesAsync()
{
ReadResponse readResponse = await Session.ReadAsync(
null, 0, TimestampsToReturn.Both,
new ReadValueId[]
{
new() { NodeId = VariableIds.Server_ServerStatus, AttributeId = Attributes.Value }
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(readResponse.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True);
}
[Description("CloseSession using default parameters. This test works by first opening a session (default parameters) and then closes it. */")]
[Test]
public async Task CloseSessionWithDefaultParametersAsync()
{
ReadResponse readResponse = await Session.ReadAsync(
null, 0, TimestampsToReturn.Both,
new ReadValueId[]
{
new() { NodeId = VariableIds.Server_ServerStatus, AttributeId = Attributes.Value }
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(readResponse.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True);
}
[Description("provide NO software certificates. This used to be a problem, but UA 1.02 changed this behavior. */")]
[Test]
public async Task ActivateSessionWithNoSoftwareCertificatesAsync()
{
ReadResponse readResponse = await Session.ReadAsync(
null, 0, TimestampsToReturn.Both,
new ReadValueId[]
{
new() { NodeId = VariableIds.Server_ServerStatus, AttributeId = Attributes.Value }
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(readResponse.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True);
}
[Description("Using SecurityPolicy None/anonymous, create a session while specifying a not-trusted certificate.")]
[Test]
public async Task CreateSessionWithUntrustedCertificateAndNoneSecurityAsync()
{
ReadResponse readResponse = await Session.ReadAsync(
null, 0, TimestampsToReturn.Both,
new ReadValueId[]
{
new() { NodeId = VariableIds.Server_ServerStatus, AttributeId = Attributes.Value }
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(readResponse.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True);
}
[Description("Create a session without specifying a SessionName (legal, but some servers crash) */")]
[Test]
public async Task CreateSessionWithoutSessionNameAsync()
{
ReadResponse readResponse = await Session.ReadAsync(
null, 0, TimestampsToReturn.Both,
new ReadValueId[]
{
new() { NodeId = VariableIds.Server_ServerStatus, AttributeId = Attributes.Value }
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(readResponse.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True);
}
[Description("Over a non-secure channel; call ActivateSession() specifying an empty ClientSignature. */")]
[Test]
public async Task ActivateSessionWithEmptyClientSignatureOnNonSecureChannelAsync()
{
ReadResponse readResponse = await Session.ReadAsync(
null, 0, TimestampsToReturn.Both,
new ReadValueId[]
{
new() { NodeId = VariableIds.Server_ServerStatus, AttributeId = Attributes.Value }
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(readResponse.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True);
}
[Description("CreateSession – injects service result Bad_SecureChannelIdInvalid in the response.")]
[Test]
public Task CreateSessionWithInjectedBadSecureChannelIdInvalidAsync()
{
return AssertCreateSessionInjectsServiceResultAsync(StatusCodes.BadSecureChannelIdInvalid);
}
private Task AssertCreateSessionInjectsServiceResultAsync(StatusCode injected)
{
// Session.OpenAsync retries CreateSession without the client
// certificate when the first attempt fails, so a one-shot
// expectation would only mutate the first response and the
// retry would succeed. Use a recurring expectation so both
// attempts return the injected error.
using IDisposable expectation = MockController.WhenRequest(
(req, resp) => resp.ResponseHeader.ServiceResult = injected);
ServiceResultException ex = Assert.ThrowsAsync(
async () => await OpenAuxSessionAsync().ConfigureAwait(false));
Assert.That(ex.StatusCode, Is.EqualTo(injected));
return Task.CompletedTask;
}
[Description("CreateSession – injects service result Bad_NonceInvalid in the response.")]
[Test]
public Task CreateSessionWithInjectedBadNonceInvalidAsync()
{
return AssertCreateSessionInjectsServiceResultAsync(StatusCodes.BadNonceInvalid);
}
[Description("CreateSession – injects service result Bad_SecurityChecksFailed in the response.")]
[Test]
public Task CreateSessionWithInjectedBadSecurityChecksFailedAsync()
{
return AssertCreateSessionInjectsServiceResultAsync(StatusCodes.BadSecurityChecksFailed);
}
[Description("CreateSession – injects service result Bad_CertificateTimeInvalid in the response.")]
[Test]
public Task CreateSessionWithInjectedBadCertificateTimeInvalidAsync()
{
return AssertCreateSessionInjectsServiceResultAsync(StatusCodes.BadCertificateTimeInvalid);
}
[Description("CreateSession – injects service result Bad_CertificateIssuerTimeInvalid in the response.")]
[Test]
public Task CreateSessionWithInjectedBadCertificateIssuerTimeInvalidAsync()
{
return AssertCreateSessionInjectsServiceResultAsync(StatusCodes.BadCertificateIssuerTimeInvalid);
}
[Description("CreateSession – injects service result Bad_CertificateHostnameInvalid in the response.")]
[Test]
public Task CreateSessionWithInjectedBadCertificateHostnameInvalidAsync()
{
return AssertCreateSessionInjectsServiceResultAsync(StatusCodes.BadCertificateHostNameInvalid);
}
[Description("CreateSession – the SessionId returned by the server is null for the second session.")]
[Test]
public Task CreateSessionWithInjectedNullSessionIdAsync()
{
// A null SessionId in the response makes ActivateSession fail
// because the client uses SessionId to identify the session.
using IDisposable expectation = MockController.WhenRequest(
(req, resp) => resp.SessionId = NodeId.Null);
Assert.ThrowsAsync(
async () => await OpenAuxSessionAsync().ConfigureAwait(false));
return Task.CompletedTask;
}
[Description("CreateSession – the server returns the same AuthenticationToken for two distinct sessions.")]
[Test]
public async Task CreateSessionWithInjectedDuplicateAuthenticationTokenAsync()
{
// Capture the AuthenticationToken from the first
// CreateSession response, then on the second response
// overwrite it with the captured value.
NodeId firstToken = NodeId.Null;
using (MockController.WhenRequest(
(req, resp) =>
{
if (firstToken.IsNull)
{
firstToken = resp.AuthenticationToken;
}
else
{
resp.AuthenticationToken = firstToken;
}
}))
{
ISession first = await OpenAuxSessionAsync().ConfigureAwait(false);
ISession? second = null;
try
{
Assert.That(firstToken.IsNull, Is.False);
try
{
second = await OpenAuxSessionAsync().ConfigureAwait(false);
Assert.That(second.Connected, Is.True);
}
catch (ServiceResultException)
{
// Acceptable: the server may reject the
// duplicate token during ActivateSession.
}
}
finally
{
foreach (ISession? s in new[] { first, second })
{
if (s == null)
{
continue;
}
try
{
await s.CloseAsync(5000, true, CancellationToken.None).ConfigureAwait(false);
}
catch
{
// best effort
}
s.Dispose();
}
}
}
}
[Description("CreateSession – the server revises the session timeout to (client request * 10).")]
[Test]
public Task CreateSessionWithInjectedExcessiveRevisedSessionTimeoutAsync()
{
return AssertCreateSessionAcceptsTimeoutMutationAsync(
mutate: (req, resp) => resp.RevisedSessionTimeout = req.RequestedSessionTimeout * 10.0);
}
[Description("CreateSession – the server revises the session timeout to an unreasonably low value (1 second).")]
[Test]
public Task CreateSessionWithInjectedTooLowRevisedSessionTimeoutAsync()
{
return AssertCreateSessionAcceptsTimeoutMutationAsync(
mutate: (req, resp) => resp.RevisedSessionTimeout = 1000.0);
}
private async Task AssertCreateSessionAcceptsTimeoutMutationAsync(
Action mutate)
{
using IDisposable expectation = MockController.WhenRequest(mutate);
// The client must accept whatever RevisedSessionTimeout the
// server returns — assert the connection completes without
// throwing.
ISession session = await OpenAuxSessionAsync().ConfigureAwait(false);
try
{
Assert.That(session.Connected, Is.True);
}
finally
{
try
{
await session.CloseAsync(5000, true, CancellationToken.None).ConfigureAwait(false);
}
catch
{
// best effort
}
session.Dispose();
}
}
[Description("CreateSession – the server returns a ServerNonce that is less than 32 bytes long.")]
[Test]
public async Task CreateSessionWithInjectedShortServerNonceAsync()
{
using IDisposable expectation = MockController.WhenRequest(
(req, resp) => resp.ServerNonce = new ByteString(new byte[8].AsMemory()));
// On a SecurityPolicy=None channel the client does not
// require a 32-byte nonce, so the connection completes.
// On a signed channel the client would reject the response.
ISession? session = null;
try
{
session = await OpenAuxSessionAsync().ConfigureAwait(false);
Assert.That(session.Connected, Is.True);
}
catch (ServiceResultException)
{
// Acceptable: client validated and rejected.
}
finally
{
if (session != null)
{
try
{
await session.CloseAsync(5000, true, CancellationToken.None).ConfigureAwait(false);
}
catch
{
// best effort
}
session.Dispose();
}
}
}
[Description("CreateSession – the EndpointDescriptions returned in the response differ from those obtained from the Discovery endpoint.")]
[Test]
public Task CreateSessionWithMismatchedEndpointDescriptionsAsync()
{
return AssertCreateSessionToleratesServerEndpointsMutationAsync(
mutate: (req, resp) =>
{
if (resp.ServerEndpoints != null! && resp.ServerEndpoints.Count > 0)
{
// Tweak the application URI on every endpoint so it
// no longer matches the one returned by Discovery.
foreach (EndpointDescription ep in resp.ServerEndpoints)
{
ep.Server?.ApplicationUri = "urn:mock:tampered";
}
}
});
}
[Description("CreateSession – the server returns an empty ServerEndpoints array.")]
[Test]
public Task CreateSessionWithInjectedEmptyServerEndpointsAsync()
{
return AssertCreateSessionToleratesServerEndpointsMutationAsync(
mutate: (req, resp) => resp.ServerEndpoints = Array.Empty().ToArrayOf());
}
private async Task AssertCreateSessionToleratesServerEndpointsMutationAsync(
Action mutate)
{
using IDisposable expectation = MockController.WhenRequest(mutate);
ISession? session = null;
try
{
session = await OpenAuxSessionAsync().ConfigureAwait(false);
Assert.That(session.Connected, Is.True);
}
catch (ServiceResultException)
{
// Acceptable: client may detect the mismatch and reject.
}
finally
{
if (session != null)
{
try
{
await session.CloseAsync(5000, true, CancellationToken.None).ConfigureAwait(false);
}
catch
{
// best effort
}
session.Dispose();
}
}
}
[Description("CreateSession – the server returns an empty ServerSoftwareCertificates array.")]
[Test]
public async Task CreateSessionWithInjectedEmptyServerSoftwareCertificatesAsync()
{
using IDisposable expectation = MockController.WhenRequest(
(req, resp) => resp.ServerSoftwareCertificates = Array.Empty().ToArrayOf());
ISession session = await OpenAuxSessionAsync().ConfigureAwait(false);
try
{
Assert.That(session.Connected, Is.True);
}
finally
{
try
{
await session.CloseAsync(5000, true, CancellationToken.None).ConfigureAwait(false);
}
catch
{
// best effort
}
session.Dispose();
}
}
[Description("CreateSession – the server returns an empty ServerSignature.")]
[Test]
public async Task CreateSessionWithInjectedEmptyServerSignatureAsync()
{
using IDisposable expectation = MockController.WhenRequest(
(req, resp) => resp.ServerSignature = new SignatureData());
ISession? session = null;
try
{
session = await OpenAuxSessionAsync().ConfigureAwait(false);
Assert.That(session.Connected, Is.True);
}
finally
{
if (session != null)
{
try
{
await session.CloseAsync(5000, true, CancellationToken.None).ConfigureAwait(false);
}
catch
{
// best effort
}
session.Dispose();
}
}
}
[Test]
public async Task SecuredCreateSessionRejectsEmptyServerSignatureAsync()
{
using IDisposable expectation = MockController
.ExpectNextResponse(
response => response.ServerSignature = new SignatureData());
ConfiguredEndpoint endpoint = await CreateConfiguredEndpointAsync(
MessageSecurityMode.SignAndEncrypt).ConfigureAwait(false);
ServiceResultException exception = Assert.ThrowsAsync(
async () => await ClientFixture.ConnectAsync(endpoint).ConfigureAwait(false))!;
Assert.That(
exception.StatusCode,
Is.EqualTo(StatusCodes.BadApplicationSignatureInvalid));
}
[TestCase(InvalidNonceKind.Null)]
[TestCase(InvalidNonceKind.Empty)]
[TestCase(InvalidNonceKind.Short)]
[TestCase(InvalidNonceKind.AllZero)]
[TestCase(InvalidNonceKind.TooLong)]
public async Task SecuredCreateSessionRejectsInvalidServerNonceAsync(
InvalidNonceKind nonceKind)
{
using IDisposable expectation = MockController
.ExpectNextResponse(
response => response.ServerNonce = CreateInvalidNonce(nonceKind));
ConfiguredEndpoint endpoint = await CreateConfiguredEndpointAsync(
MessageSecurityMode.SignAndEncrypt).ConfigureAwait(false);
ServiceResultException exception = Assert.ThrowsAsync(
async () => await ClientFixture.ConnectAsync(endpoint).ConfigureAwait(false))!;
Assert.That(exception.StatusCode, Is.EqualTo(StatusCodes.BadNonceInvalid));
}
[TestCase(InvalidNonceKind.Null, false)]
[TestCase(InvalidNonceKind.Empty, false)]
[TestCase(InvalidNonceKind.Short, false)]
[TestCase(InvalidNonceKind.AllZero, false)]
[TestCase(InvalidNonceKind.TooLong, false)]
[TestCase(InvalidNonceKind.Null, true)]
[TestCase(InvalidNonceKind.Empty, true)]
[TestCase(InvalidNonceKind.Short, true)]
[TestCase(InvalidNonceKind.AllZero, true)]
[TestCase(InvalidNonceKind.TooLong, true)]
public async Task SecuredActivateSessionRejectsInvalidServerNonceForAllIdentitiesAsync(
InvalidNonceKind nonceKind,
bool useUserName)
{
using IDisposable expectation = MockController
.ExpectNextResponse(
response => response.ServerNonce = CreateInvalidNonce(nonceKind));
ConfiguredEndpoint endpoint = await CreateConfiguredEndpointAsync(
MessageSecurityMode.SignAndEncrypt).ConfigureAwait(false);
IUserIdentity identity = useUserName
? new UserIdentity("user1", "password"u8)
: new UserIdentity();
ServiceResultException exception = Assert.ThrowsAsync(
async () => await ClientFixture
.ConnectAsync(endpoint, identity)
.ConfigureAwait(false))!;
Assert.That(exception.StatusCode, Is.EqualTo(StatusCodes.BadNonceInvalid));
}
[Test]
public async Task SecuredActivateSessionRejectsReusedServerNonceAsync()
{
ByteString createSessionNonce = default;
using IDisposable capture = MockController
.WhenRequest(
(_, response) => createSessionNonce = response.ServerNonce);
using IDisposable reuse = MockController
.ExpectNextResponse(
response => response.ServerNonce = createSessionNonce);
ConfiguredEndpoint endpoint = await CreateConfiguredEndpointAsync(
MessageSecurityMode.SignAndEncrypt).ConfigureAwait(false);
ServiceResultException exception = Assert.ThrowsAsync(
async () => await ClientFixture.ConnectAsync(endpoint).ConfigureAwait(false))!;
Assert.That(exception.StatusCode, Is.EqualTo(StatusCodes.BadNonceInvalid));
}
[Test]
public async Task SecuredActivateSessionRejectsNonConsecutiveReusedServerNonceAsync()
{
ByteString firstActivationNonce = default;
int activationCount = 0;
using IDisposable mutation = MockController
.WhenRequest(
(_, response) =>
{
activationCount++;
if (activationCount == 1)
{
firstActivationNonce = response.ServerNonce;
}
else if (activationCount == 3)
{
response.ServerNonce = firstActivationNonce;
}
});
ConfiguredEndpoint endpoint = await CreateConfiguredEndpointAsync(
MessageSecurityMode.SignAndEncrypt).ConfigureAwait(false);
ISession session = await ClientFixture.ConnectAsync(endpoint).ConfigureAwait(false);
try
{
await session.UpdateSessionAsync(
session.Identity,
default,
CancellationToken.None).ConfigureAwait(false);
ServiceResultException exception = Assert.ThrowsAsync(
async () => await session.UpdateSessionAsync(
session.Identity,
default,
CancellationToken.None).ConfigureAwait(false))!;
Assert.That(exception.StatusCode, Is.EqualTo(StatusCodes.BadNonceInvalid));
}
finally
{
await session.CloseAsync(5000, true).ConfigureAwait(false);
session.Dispose();
}
}
[Test]
public async Task ApplySessionConfigurationSeedsServerNonceHistoryAsync()
{
ConfiguredEndpoint endpoint = await CreateConfiguredEndpointAsync(
MessageSecurityMode.SignAndEncrypt).ConfigureAwait(false);
ISession session = await ClientFixture.ConnectAsync(endpoint).ConfigureAwait(false);
try
{
SessionConfiguration configuration = session.SaveSessionConfiguration();
// A secured session exposes a server nonce; re-applying the saved
// configuration must seed the lifetime nonce-reuse history with it.
Assert.That(configuration.ServerNonce.IsNull, Is.False);
using IDisposable expectation = MockController
.ExpectNextResponse(
response => response.ServerNonce = configuration.ServerNonce);
bool applied = session.ApplySessionConfiguration(configuration);
Assert.That(applied, Is.True);
Assert.That(session.Connected, Is.True);
ServiceResultException exception = Assert.ThrowsAsync(
async () => await session.UpdateSessionAsync(
session.Identity,
default,
CancellationToken.None).ConfigureAwait(false))!;
Assert.That(exception.StatusCode, Is.EqualTo(StatusCodes.BadNonceInvalid));
}
finally
{
await session.CloseAsync(5000, true).ConfigureAwait(false);
session.Dispose();
}
}
[Test]
public async Task SuppressNonceValidationErrorsDoesNotAllowInvalidNonceAsync()
{
bool original = ClientFixture.Config.SecurityConfiguration
.SuppressNonceValidationErrors;
ClientFixture.Config.SecurityConfiguration.SuppressNonceValidationErrors = true;
try
{
using IDisposable expectation = MockController
.ExpectNextResponse(
response => response.ServerNonce = CreateInvalidNonce(
InvalidNonceKind.AllZero));
ConfiguredEndpoint endpoint = await CreateConfiguredEndpointAsync(
MessageSecurityMode.SignAndEncrypt).ConfigureAwait(false);
ServiceResultException exception = Assert.ThrowsAsync(
async () => await ClientFixture.ConnectAsync(endpoint).ConfigureAwait(false))!;
Assert.That(exception.StatusCode, Is.EqualTo(StatusCodes.BadNonceInvalid));
}
finally
{
ClientFixture.Config.SecurityConfiguration.SuppressNonceValidationErrors =
original;
}
}
[Test]
public async Task AnonymousSignReconnectRecreatesSessionAsync()
{
ConfiguredEndpoint endpoint = await CreateConfiguredEndpointAsync(
MessageSecurityMode.Sign).ConfigureAwait(false);
ISession session = await ClientFixture.ConnectAsync(endpoint).ConfigureAwait(false);
try
{
NodeId originalSessionId = session.SessionId;
await session.ReconnectAsync(
connection: null,
channel: null,
CancellationToken.None).ConfigureAwait(false);
Assert.That(session.SessionId, Is.Not.EqualTo(originalSessionId));
Assert.That(session.Connected, Is.True);
}
finally
{
await session.CloseAsync(5000, true).ConfigureAwait(false);
session.Dispose();
}
}
[Test]
public async Task AnonymousSignAndEncryptReconnectReactivatesSessionAsync()
{
ConfiguredEndpoint endpoint = await CreateConfiguredEndpointAsync(
MessageSecurityMode.SignAndEncrypt).ConfigureAwait(false);
ISession session = await ClientFixture.ConnectAsync(endpoint).ConfigureAwait(false);
try
{
NodeId originalSessionId = session.SessionId;
await session.ReconnectAsync(
connection: null,
channel: null,
CancellationToken.None).ConfigureAwait(false);
Assert.That(session.SessionId, Is.EqualTo(originalSessionId));
Assert.That(session.Connected, Is.True);
}
finally
{
await session.CloseAsync(5000, true).ConfigureAwait(false);
session.Dispose();
}
}
[Description("CreateSession – the server returns a MaxRequestMessageSize of 500 bytes.")]
[Test]
public Task CreateSessionWithInjectedSmallMaxRequestMessageSizeAsync()
{
return AssertCreateSessionAcceptsMessageSizeMutationAsync(
mutate: (req, resp) => resp.MaxRequestMessageSize = 500);
}
[Description("CreateSession – the server returns a MaxResponseMessageSize equal to the client's request multiplied by 10.")]
[Test]
public Task CreateSessionWithInjectedExcessiveMaxResponseMessageSizeAsync()
{
return AssertCreateSessionAcceptsMessageSizeMutationAsync(
mutate: (req, resp) => resp.MaxRequestMessageSize = req.MaxResponseMessageSize * 10);
}
private async Task AssertCreateSessionAcceptsMessageSizeMutationAsync(
Action mutate)
{
using IDisposable expectation = MockController.WhenRequest(mutate);
ISession session = await OpenAuxSessionAsync().ConfigureAwait(false);
try
{
Assert.That(session.Connected, Is.True);
}
finally
{
try
{
await session.CloseAsync(5000, true, CancellationToken.None).ConfigureAwait(false);
}
catch
{
// best effort
}
session.Dispose();
}
}
[Description("ActivateSession – injects service result Bad_IdentityTokenInvalid.")]
[Test]
public Task ActivateSessionWithInjectedBadIdentityTokenInvalidAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadIdentityTokenInvalid);
}
[Description("ActivateSession – injects service result Bad_IdentityTokenRejected.")]
[Test]
public Task ActivateSessionWithInjectedBadIdentityTokenRejectedAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadIdentityTokenRejected);
}
[Description("ActivateSession – injects service result Bad_UserAccessDenied.")]
[Test]
public Task ActivateSessionWithInjectedBadUserAccessDeniedAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadUserAccessDenied);
}
[Description("ActivateSession – injects service result Bad_ApplicationSignatureInvalid.")]
[Test]
public Task ActivateSessionWithInjectedBadApplicationSignatureInvalidAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadApplicationSignatureInvalid);
}
[Description("ActivateSession – injects service result Bad_UserSignatureInvalid.")]
[Test]
public Task ActivateSessionWithInjectedBadUserSignatureInvalidAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadUserSignatureInvalid);
}
[Description("ActivateSession – injects service result Bad_NoValidCertificates.")]
[Test]
public Task ActivateSessionWithInjectedBadNoValidCertificatesAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadNoValidCertificates);
}
[Description("ActivateSession – injects service result Bad_IdentityChangeNotSupported.")]
[Test]
public Task ActivateSessionWithInjectedBadIdentityChangeNotSupportedAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadIdentityChangeNotSupported);
}
[Description("ActivateSession – injects service result Bad_CertificateTimeInvalid.")]
[Test]
public Task ActivateSessionWithInjectedBadCertificateTimeInvalidAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadCertificateTimeInvalid);
}
[Description("ActivateSession – injects service result Bad_CertificateIssuerTimeInvalid.")]
[Test]
public Task ActivateSessionWithInjectedBadCertificateIssuerTimeInvalidAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadCertificateIssuerTimeInvalid);
}
[Description("ActivateSession – injects service result Bad_CertificateHostNameInvalid.")]
[Test]
public Task ActivateSessionWithInjectedBadCertificateHostNameInvalidAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadCertificateHostNameInvalid);
}
[Description("ActivateSession – injects service result Bad_CertificateUriInvalid.")]
[Test]
public Task ActivateSessionWithInjectedBadCertificateUriInvalidAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadCertificateUriInvalid);
}
[Description("ActivateSession – injects service result Bad_CertificateUseNotAllowed.")]
[Test]
public Task ActivateSessionWithInjectedBadCertificateUseNotAllowedAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadCertificateUseNotAllowed);
}
[Description("ActivateSession – injects service result Bad_CertificateIssuerUseNotAllowed.")]
[Test]
public Task ActivateSessionWithInjectedBadCertificateIssuerUseNotAllowedAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadCertificateIssuerUseNotAllowed);
}
[Description("ActivateSession – injects service result Bad_CertificateUntrusted.")]
[Test]
public Task ActivateSessionWithInjectedBadCertificateUntrustedAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadCertificateUntrusted);
}
[Description("ActivateSession – injects service result Bad_CertificateRevocationUnknown.")]
[Test]
public Task ActivateSessionWithInjectedBadCertificateRevocationUnknownAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadCertificateRevocationUnknown);
}
[Description("ActivateSession – injects service result Bad_CertificateIssuerRevocationUnknown.")]
[Test]
public Task ActivateSessionWithInjectedBadCertificateIssuerRevocationUnknownAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadCertificateIssuerRevocationUnknown);
}
[Description("ActivateSession – injects service result Bad_CertificateRevoked.")]
[Test]
public Task ActivateSessionWithInjectedBadCertificateRevokedAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadCertificateRevoked);
}
[Description("ActivateSession – injects service result Bad_CertificateIssuerRevoked.")]
[Test]
public Task ActivateSessionWithInjectedBadCertificateIssuerRevokedAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadCertificateIssuerRevoked);
}
[Description("ActivateSession – injects service result Bad_SecurityChecksFailed.")]
[Test]
public Task ActivateSessionWithInjectedBadSecurityChecksFailedAsync()
{
return AssertActivateSessionInjectsServiceResultAsync(StatusCodes.BadSecurityChecksFailed);
}
private Task AssertActivateSessionInjectsServiceResultAsync(StatusCode injected)
{
using IDisposable expectation = MockController.ExpectNextResponse(
r => r.ResponseHeader.ServiceResult = injected);
ServiceResultException ex = Assert.ThrowsAsync(
async () => await OpenAuxSessionAsync().ConfigureAwait(false));
Assert.That(ex.StatusCode, Is.EqualTo(injected));
return Task.CompletedTask;
}
[Description("CloseSession – the server returns Bad_SessionIdInvalid as the service result.")]
[Test]
public async Task CloseSessionWithInjectedBadSessionIdInvalidAsync()
{
ISession aux = await OpenAuxSessionAsync().ConfigureAwait(false);
try
{
using IDisposable expectation = MockController.ExpectNextResponse(
r => r.ResponseHeader.ServiceResult = StatusCodes.BadSessionIdInvalid);
// Session.CloseAsync swallows ServiceResultException and
// returns the status code so callers can log it without
// a try/catch — assert against the returned code.
StatusCode result = await aux.CloseAsync(5000, true, CancellationToken.None).ConfigureAwait(false);
Assert.That(result, Is.EqualTo(StatusCodes.BadSessionIdInvalid));
Assert.That(MockController.HasPendingOneShotExpectations, Is.False);
}
finally
{
aux.Dispose();
}
}
[Description("ActivateSession – the server returns an empty ServerNonce in the response.")]
[Test]
public async Task ActivateSessionWithInjectedEmptyServerNonceAsync()
{
using IDisposable expectation = MockController.WhenRequest(
(req, resp) => resp.ServerNonce = ByteString.Empty);
// On a SecurityPolicy=None channel the empty nonce passes
// through. On a signed channel the client would reject.
ISession? session = null;
try
{
session = await OpenAuxSessionAsync().ConfigureAwait(false);
Assert.That(session.Connected, Is.True);
}
catch (ServiceResultException)
{
// Acceptable on signed channels.
}
finally
{
if (session != null)
{
try
{
await session.CloseAsync(5000, true, CancellationToken.None).ConfigureAwait(false);
}
catch
{
// best effort
}
session.Dispose();
}
}
}
[Description("ActivateSession – the first entry of the per-result Results array contains Bad_CertificateUriInvalid.")]
[Test]
public async Task ActivateSessionWithInjectedFirstResultBadCertificateUriInvalidAsync()
{
using IDisposable expectation = MockController.WhenRequest(
(req, resp) =>
{
int n = resp.Results == null! ? 0 : resp.Results.Count;
var mutated = new StatusCode[Math.Max(n, 1)];
for (int i = 0; i < mutated.Length; i++)
{
mutated[i] = i < n && i > 0 ? resp.Results[i] : StatusCodes.Good;
}
mutated[0] = StatusCodes.BadCertificateUriInvalid;
resp.Results = mutated.ToArrayOf();
});
ISession? session = null;
try
{
session = await OpenAuxSessionAsync().ConfigureAwait(false);
Assert.That(session.Connected, Is.True);
}
catch (ServiceResultException)
{
// Acceptable: a stricter client would reject.
}
finally
{
if (session != null)
{
try
{
await session.CloseAsync(5000, true, CancellationToken.None).ConfigureAwait(false);
}
catch
{
// best effort
}
session.Dispose();
}
}
}
[Description("ActivateSession – every entry of the Results array contains a Bad_ status code.")]
[Test]
public async Task ActivateSessionWithInjectedAllResultsBadAsync()
{
using IDisposable expectation = MockController.WhenRequest(
(req, resp) =>
{
int n = resp.Results == null! ? 0 : resp.Results.Count;
var mutated = new StatusCode[Math.Max(n, 1)];
for (int i = 0; i < mutated.Length; i++)
{
mutated[i] = StatusCodes.BadCertificateUriInvalid;
}
resp.Results = mutated.ToArrayOf();
});
ISession? session = null;
try
{
session = await OpenAuxSessionAsync().ConfigureAwait(false);
Assert.That(session.Connected, Is.True);
}
catch (ServiceResultException)
{
// Acceptable.
}
finally
{
if (session != null)
{
try
{
await session.CloseAsync(5000, true, CancellationToken.None).ConfigureAwait(false);
}
catch
{
// best effort
}
session.Dispose();
}
}
}
private async Task CreateConfiguredEndpointAsync(
MessageSecurityMode securityMode)
{
ArrayOf endpoints = await ClientFixture
.GetEndpointsAsync(ServerUrl)
.ConfigureAwait(false);
EndpointDescription? selected = null;
for (int i = 0; i < endpoints.Count; i++)
{
EndpointDescription candidate = endpoints[i];
if (candidate.SecurityMode == securityMode &&
candidate.SecurityPolicyUri == SecurityPolicies.Basic256Sha256)
{
selected = candidate;
break;
}
}
Assert.That(selected, Is.Not.Null);
return new ConfiguredEndpoint(
null,
selected,
EndpointConfiguration.Create(ClientFixture.Config));
}
private static ByteString CreateInvalidNonce(InvalidNonceKind nonceKind)
{
return nonceKind switch
{
InvalidNonceKind.Null => default,
InvalidNonceKind.Empty => ByteString.Empty,
InvalidNonceKind.Short => ByteString.From(CreateNonceBytes(31)),
InvalidNonceKind.AllZero => ByteString.From(new byte[32]),
InvalidNonceKind.TooLong => ByteString.From(CreateNonceBytes(129)),
_ => throw new ArgumentOutOfRangeException(nameof(nonceKind))
};
}
private static byte[] CreateNonceBytes(int length)
{
byte[] bytes = new byte[length];
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = (byte)(i + 1);
}
return bytes;
}
public enum InvalidNonceKind
{
Null,
Empty,
Short,
AllZero,
TooLong
}
}
}