using DodoSSH.Client.Ssh; namespace DodoSSH.Client.Terminal.Tests; /// /// The wire format between the host and the renderer page. /// /// /// Byte order is asserted explicitly rather than only round-tripped. Both ends of this protocol are /// ours today, but the JavaScript side reads the header with a DataView, whose default is /// big-endian — a round-trip-only test would pass just as happily with a little-endian header that the /// page then misreads. /// public sealed class TerminalFrameTests { [Fact] public void AFrame_RoundTrips() { var frame = TerminalFrame.Create((byte)TerminalServerOpcode.Output, 42, "hello"u8); TerminalFrame.TryRead(frame, out var opcode, out var sessionId, out var payload).ShouldBeTrue(); opcode.ShouldBe((byte)TerminalServerOpcode.Output); sessionId.ShouldBe(42u); payload.ToArray().ShouldBe("hello"u8.ToArray()); } [Fact] public void TheSessionId_IsBigEndian() { var frame = TerminalFrame.Create((byte)TerminalServerOpcode.Output, 0x01020304, []); frame[1].ShouldBe((byte)0x01); frame[2].ShouldBe((byte)0x02); frame[3].ShouldBe((byte)0x03); frame[4].ShouldBe((byte)0x04); } [Fact] public void AnEmptyPayload_IsLegal() { // SessionOpened carries nothing. var frame = TerminalFrame.Create((byte)TerminalServerOpcode.SessionOpened, 1, []); frame.Length.ShouldBe(TerminalFrame.HeaderLength); TerminalFrame.TryRead(frame, out _, out _, out var payload).ShouldBeTrue(); payload.Length.ShouldBe(0); } [Theory] [InlineData(0)] [InlineData(1)] [InlineData(4)] public void AFrameShorterThanTheHeader_IsRejected(int length) { // These arrive from a WebView page, so a malformed frame is untrusted input to drop rather // than an exceptional condition. TerminalFrame.TryRead(new byte[length], out _, out _, out _).ShouldBeFalse(); } [Fact] public void Write_RejectsATooSmallDestination() { Should.Throw(() => TerminalFrame.Write(new byte[4], 1, 1, "payload"u8)); } [Fact] public void AnAcknowledgement_RoundTrips() { var payload = TerminalFrame.CreateAcknowledgementPayload(123_456); TerminalFrame.TryReadAcknowledgement(payload, out var rendered).ShouldBeTrue(); rendered.ShouldBe(123_456u); } [Theory] [InlineData(0)] [InlineData(3)] [InlineData(5)] public void AnAcknowledgementOfTheWrongLength_IsRejected(int length) { TerminalFrame.TryReadAcknowledgement(new byte[length], out _).ShouldBeFalse(); } [Fact] public void AResize_RoundTrips() { var size = new TerminalSize(132, 43, 1320, 1075); var payload = TerminalFrame.CreateResizePayload(size); TerminalFrame.TryReadResize(payload, out var decoded).ShouldBeTrue(); decoded.ShouldBe(size); } [Theory] [InlineData(0)] [InlineData(7)] [InlineData(9)] public void AResizeOfTheWrongLength_IsRejected(int length) { TerminalFrame.TryReadResize(new byte[length], out _).ShouldBeFalse(); } [Fact] public void AResizePayload_OrdersColumnsBeforeRows() { // The page builds this by hand, and columns-before-rows is the SSH convention. Swapping them // produces a terminal that is 24 columns by 80 rows, which looks like a rendering bug. var payload = TerminalFrame.CreateResizePayload(new TerminalSize(80, 24)); payload[0].ShouldBe((byte)0); payload[1].ShouldBe((byte)80); payload[2].ShouldBe((byte)0); payload[3].ShouldBe((byte)24); } }