99 lines
4.1 KiB
C#
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);
|
|
}
|