108 lines
4.4 KiB
C#
108 lines
4.4 KiB
C#
using System.IO;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests;
|
|
|
|
[Trait("Category", "Unit")]
|
|
public sealed class FramingTests
|
|
{
|
|
[Fact]
|
|
public async Task FrameWriter_round_trips_single_frame_through_FrameReader()
|
|
{
|
|
var buffer = new MemoryStream();
|
|
using (var writer = new FrameWriter(buffer, leaveOpen: true))
|
|
{
|
|
await writer.WriteAsync(FocasMessageKind.Hello,
|
|
new Hello { PeerName = "proxy", SharedSecret = "s3cr3t" }, TestContext.Current.CancellationToken);
|
|
}
|
|
|
|
buffer.Position = 0;
|
|
using var reader = new FrameReader(buffer, leaveOpen: true);
|
|
var frame = await reader.ReadFrameAsync(TestContext.Current.CancellationToken);
|
|
frame.ShouldNotBeNull();
|
|
frame!.Value.Kind.ShouldBe(FocasMessageKind.Hello);
|
|
var hello = FrameReader.Deserialize<Hello>(frame.Value.Body);
|
|
hello.PeerName.ShouldBe("proxy");
|
|
hello.SharedSecret.ShouldBe("s3cr3t");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FrameReader_returns_null_on_clean_EOF_at_frame_boundary()
|
|
{
|
|
using var empty = new MemoryStream();
|
|
using var reader = new FrameReader(empty, leaveOpen: true);
|
|
var frame = await reader.ReadFrameAsync(TestContext.Current.CancellationToken);
|
|
frame.ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FrameReader_throws_on_oversized_length_prefix()
|
|
{
|
|
var hostile = new byte[] { 0x7F, 0xFF, 0xFF, 0xFF, 0x01 }; // length > 16 MiB
|
|
using var stream = new MemoryStream(hostile);
|
|
using var reader = new FrameReader(stream, leaveOpen: true);
|
|
await Should.ThrowAsync<InvalidDataException>(async () =>
|
|
await reader.ReadFrameAsync(TestContext.Current.CancellationToken));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FrameReader_throws_on_mid_frame_eof()
|
|
{
|
|
var buffer = new MemoryStream();
|
|
using (var writer = new FrameWriter(buffer, leaveOpen: true))
|
|
{
|
|
await writer.WriteAsync(FocasMessageKind.Hello, new Hello { PeerName = "x" },
|
|
TestContext.Current.CancellationToken);
|
|
}
|
|
// Truncate so body is incomplete.
|
|
var truncated = buffer.ToArray()[..(buffer.ToArray().Length - 2)];
|
|
using var partial = new MemoryStream(truncated);
|
|
using var reader = new FrameReader(partial, leaveOpen: true);
|
|
await Should.ThrowAsync<EndOfStreamException>(async () =>
|
|
await reader.ReadFrameAsync(TestContext.Current.CancellationToken));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FrameWriter_serializes_concurrent_writes()
|
|
{
|
|
var buffer = new MemoryStream();
|
|
using var writer = new FrameWriter(buffer, leaveOpen: true);
|
|
|
|
var tasks = Enumerable.Range(0, 20).Select(i => writer.WriteAsync(
|
|
FocasMessageKind.Heartbeat,
|
|
new Heartbeat { MonotonicTicks = i },
|
|
TestContext.Current.CancellationToken)).ToArray();
|
|
await Task.WhenAll(tasks);
|
|
|
|
buffer.Position = 0;
|
|
using var reader = new FrameReader(buffer, leaveOpen: true);
|
|
var seen = new List<long>();
|
|
while (await reader.ReadFrameAsync(TestContext.Current.CancellationToken) is { } frame)
|
|
{
|
|
frame.Kind.ShouldBe(FocasMessageKind.Heartbeat);
|
|
seen.Add(FrameReader.Deserialize<Heartbeat>(frame.Body).MonotonicTicks);
|
|
}
|
|
seen.Count.ShouldBe(20);
|
|
seen.OrderBy(x => x).ShouldBe(Enumerable.Range(0, 20).Select(x => (long)x));
|
|
}
|
|
|
|
[Fact]
|
|
public void MessageKind_values_are_stable()
|
|
{
|
|
// Guardrail — if someone reorders/renumbers, the wire format breaks for deployed peers.
|
|
((byte)FocasMessageKind.Hello).ShouldBe((byte)0x01);
|
|
((byte)FocasMessageKind.Heartbeat).ShouldBe((byte)0x03);
|
|
((byte)FocasMessageKind.OpenSessionRequest).ShouldBe((byte)0x10);
|
|
((byte)FocasMessageKind.ReadRequest).ShouldBe((byte)0x30);
|
|
((byte)FocasMessageKind.WriteRequest).ShouldBe((byte)0x32);
|
|
((byte)FocasMessageKind.PmcBitWriteRequest).ShouldBe((byte)0x34);
|
|
((byte)FocasMessageKind.SubscribeRequest).ShouldBe((byte)0x40);
|
|
((byte)FocasMessageKind.OnDataChangeNotification).ShouldBe((byte)0x43);
|
|
((byte)FocasMessageKind.ProbeRequest).ShouldBe((byte)0x70);
|
|
((byte)FocasMessageKind.ErrorResponse).ShouldBe((byte)0xFE);
|
|
}
|
|
}
|