using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared.Contracts; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared.Tests; [Trait("Category", "Unit")] public sealed class FramingTests { [Fact] public async Task FrameWriter_FrameReader_round_trip_preserves_kind_and_body() { using var ms = new MemoryStream(); using (var writer = new FrameWriter(ms, leaveOpen: true)) { await writer.WriteAsync(MessageKind.Hello, new Hello { PeerName = "p", SharedSecret = "s" }, TestContext.Current.CancellationToken); await writer.WriteAsync(MessageKind.Heartbeat, new Heartbeat { SequenceNumber = 7, UtcUnixMs = 42 }, TestContext.Current.CancellationToken); } ms.Position = 0; using var reader = new FrameReader(ms, leaveOpen: true); var f1 = (await reader.ReadFrameAsync(TestContext.Current.CancellationToken))!.Value; f1.Kind.ShouldBe(MessageKind.Hello); FrameReader.Deserialize(f1.Body).PeerName.ShouldBe("p"); var f2 = (await reader.ReadFrameAsync(TestContext.Current.CancellationToken))!.Value; f2.Kind.ShouldBe(MessageKind.Heartbeat); FrameReader.Deserialize(f2.Body).SequenceNumber.ShouldBe(7L); var eof = await reader.ReadFrameAsync(TestContext.Current.CancellationToken); eof.ShouldBeNull(); } [Fact] public async Task FrameReader_rejects_frames_larger_than_the_cap() { using var ms = new MemoryStream(); var evilLen = Framing.MaxFrameBodyBytes + 1; ms.Write(new byte[] { (byte)((evilLen >> 24) & 0xFF), (byte)((evilLen >> 16) & 0xFF), (byte)((evilLen >> 8) & 0xFF), (byte)( evilLen & 0xFF), }, 0, 4); ms.WriteByte((byte)MessageKind.Hello); ms.Position = 0; using var reader = new FrameReader(ms, leaveOpen: true); await Should.ThrowAsync(() => reader.ReadFrameAsync(TestContext.Current.CancellationToken).AsTask()); } private static class TestContext { public static TestContextHelper Current { get; } = new(); } private sealed class TestContextHelper { public CancellationToken CancellationToken => CancellationToken.None; } } file static class TaskExtensions { public static Task AsTask(this ValueTask vt) => vt.AsTask(); public static Task AsTask(this Task t) => t; }