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);
}
}