Files
lmxopcua/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests/SessionHistoryContinuationStoreTests.cs
T

99 lines
4.1 KiB
C#

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.Logging.Abstractions;
using Opc.Ua;
using Opc.Ua.Server;
using Shouldly;
using Xunit;
using Opc.Ua.Security.Certificates;
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests;
public sealed class SessionHistoryContinuationStoreTests : IAsyncLifetime
{
private readonly string _pki = Path.Combine(Path.GetTempPath(), $"otua-sdk2-cp-{Guid.NewGuid():N}");
private OpcUaApplicationHost _host = null!;
private OtOpcUaSdkServer _server = null!;
private Certificate _certificate = null!;
private Nonce _nonce = null!;
private readonly SessionHistoryContinuationStore _store = new();
public async ValueTask InitializeAsync()
{
var started = await OpcUaHostStart.StartAsync(port => new OpcUaApplicationHost(new OpcUaApplicationHostOptions
{
ApplicationName = "SDK2.Continuation", ApplicationUri = "urn:SDK2.Continuation",
PublicHostname = "localhost", OpcUaPort = port, PkiStoreRoot = _pki,
}, NullLogger<OpcUaApplicationHost>.Instance), () => new OtOpcUaSdkServer(), TestContext.Current.CancellationToken);
_host = started.Host;
_server = started.Server;
using var key = RSA.Create(2048);
var request = new CertificateRequest("CN=SDK2.Continuation.Unit", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
_certificate = Certificate.From(request.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(1)));
_nonce = Nonce.CreateNonce(32);
}
public async ValueTask DisposeAsync()
{
_nonce.Dispose();
_certificate.Dispose();
await _host.DisposeAsync();
Directory.Delete(_pki, true);
}
[Fact]
public void Real_session_store_transfers_an_owned_single_use_wrapper()
{
using var session = Session();
var state = State();
var token = _store.Save(session, state)!;
token.Length.ShouldBe(16);
using var point = session.ContinuationPoints.RestoreHistory(ByteString.From(token));
point.ShouldNotBeNull();
point.Id.ShouldBe(new Guid(token));
_store.TryTake(session, token).ShouldBeNull();
var next = _store.Save(session, state)!;
_store.TryTake(session, next).ShouldBeSameAs(state);
_store.TryTake(session, next).ShouldBeNull();
}
[Fact]
public void Real_sessions_isolate_points_evict_oldest_and_release_explicitly()
{
using var first = Session();
using var second = Session();
var oldest = _store.Save(first, State())!;
var middle = _store.Save(first, State())!;
var latest = _store.Save(first, State())!;
_store.TryTake(first, oldest).ShouldBeNull();
_store.TryTake(second, middle).ShouldBeNull();
_store.TryTake(first, middle).ShouldNotBeNull();
_store.Release(first, latest);
_store.TryTake(first, latest).ShouldBeNull();
_store.Release(first, latest);
_store.TryTake(first, [1, 2, 3]).ShouldBeNull();
}
[Fact]
public void Closing_the_real_session_drops_abandoned_points()
{
var session = Session();
var token = _store.Save(session, State())!;
session.Dispose();
_store.TryTake(session, token).ShouldBeNull();
}
private Session Session()
{
var endpoint = new EndpointDescription { EndpointUrl = "opc.tcp://localhost:4840", SecurityMode = MessageSecurityMode.None };
using var context = new OperationContext(new RequestHeader(),
new SecureChannelContext(Guid.NewGuid().ToString(), endpoint, RequestEncoding.Binary),
RequestType.CreateSession, RequestLifetime.None);
return new Session(context, _server.CurrentInstance, _certificate, new NodeId(Guid.NewGuid()), default,
_nonce, "SDK2 continuation unit", new ApplicationDescription(), endpoint.EndpointUrl,
null!, null!, 60_000, 2, 2);
}
private static HistoryContinuationState State() => new("tag", DateTime.UtcNow, DateTime.UtcNow.AddHours(1), 0, 2);
}