namespace HSchool.Protocol.Tests; /// /// The wire format is a contract with the browser client. Round-trips prove the C# side is /// self-consistent; the explicit byte-layout tests are what keeps /// src/HSchool.Client/src/net/protocol.ts honest. /// public class ProtocolCodecTests { [Fact] public void Hello_RoundTrips() { var message = new ClientHelloMessage(ProtocolConstants.Version, "ada"); Span buffer = stackalloc byte[64]; var length = ProtocolCodec.WriteHello(buffer, message); Assert.Equal(message, ProtocolCodec.ReadHello(buffer[..length])); } [Fact] public void Input_RoundTrips() { var message = new ClientInputMessage(0x01020304, InputButtons.Up | InputButtons.Right); Span buffer = stackalloc byte[16]; var length = ProtocolCodec.WriteInput(buffer, message); Assert.Equal(6, length); Assert.Equal(message, ProtocolCodec.ReadInput(buffer[..length])); } [Fact] public void Ping_RoundTrips() { var message = new ClientPingMessage(1_700_000_000_123); Span buffer = stackalloc byte[16]; var length = ProtocolCodec.WritePing(buffer, message); Assert.Equal(9, length); Assert.Equal(message, ProtocolCodec.ReadPing(buffer[..length])); } [Fact] public void Welcome_RoundTripsAndIsFifteenBytes() { var message = new ServerWelcomeMessage(ProtocolConstants.Version, 42, 20, 1600f, 900f); Span buffer = stackalloc byte[32]; var length = ProtocolCodec.WriteWelcome(buffer, message); Assert.Equal(15, length); Assert.Equal(message, ProtocolCodec.ReadWelcome(buffer[..length])); } [Fact] public void Pong_RoundTrips() { var message = new ServerPongMessage(5, 99); Span buffer = stackalloc byte[32]; var length = ProtocolCodec.WritePong(buffer, message); Assert.Equal(13, length); Assert.Equal(message, ProtocolCodec.ReadPong(buffer[..length])); } [Fact] public void Snapshot_RoundTripsEveryEntityField() { ReadOnlySpan entities = [ new EntitySnapshot(7, EntityKind.Player, 100f, 200f, 18f, 0x4CC9F0), new EntitySnapshot(8, EntityKind.Obstacle, 800f, 450f, 70f, 0x3A4553), ]; var buffer = new byte[ProtocolCodec.SnapshotSize(entities.Length)]; var length = ProtocolCodec.WriteSnapshot(buffer, 1234, entities); Assert.Equal(buffer.Length, length); var decoded = new EntitySnapshot[entities.Length]; var count = ProtocolCodec.ReadSnapshot(buffer, decoded, out var tick); Assert.Equal(entities.Length, count); Assert.Equal(1234u, tick); Assert.Equal(entities[0], decoded[0]); Assert.Equal(entities[1], decoded[1]); } [Fact] public void SnapshotSize_MatchesTheLayoutTheClientAssumes() { // 1 type + 4 tick + 2 count, then 21 bytes per entity. Assert.Equal(7, ProtocolCodec.SnapshotSize(0)); Assert.Equal(7 + 21, ProtocolCodec.SnapshotSize(1)); Assert.Equal(21, ProtocolConstants.EntitySnapshotSize); } [Fact] public void Numbers_AreLittleEndian() { Span buffer = stackalloc byte[16]; var length = ProtocolCodec.WriteInput(buffer, new ClientInputMessage(0x01020304, InputButtons.None)); Assert.Equal((byte)MessageType.ClientInput, buffer[0]); Assert.Equal(new byte[] { 0x04, 0x03, 0x02, 0x01 }, buffer[1..5].ToArray()); Assert.Equal(6, length); } [Fact] public void PeekMessageType_ReadsTheFirstByte() { Span buffer = stackalloc byte[16]; ProtocolCodec.WritePing(buffer, new ClientPingMessage(1)); Assert.Equal(MessageType.ClientPing, ProtocolCodec.PeekMessageType(buffer)); Assert.Equal(MessageType.None, ProtocolCodec.PeekMessageType([])); } [Fact] public void TruncatedFrame_Throws() { byte[] frame = [(byte)MessageType.ServerWelcome, ProtocolConstants.Version]; Assert.Throws(() => ProtocolCodec.ReadWelcome(frame)); } [Fact] public void WrongMessageId_Throws() { Span buffer = stackalloc byte[16]; var length = ProtocolCodec.WritePing(buffer, new ClientPingMessage(1)); var frame = buffer[..length].ToArray(); Assert.Throws(() => ProtocolCodec.ReadInput(frame)); } [Fact] public void OversizedName_Throws() { var message = new ClientHelloMessage(ProtocolConstants.Version, new string('x', 100)); var buffer = new byte[256]; Assert.Throws(() => ProtocolCodec.WriteHello(buffer, message)); } [Fact] public void UndersizedBuffer_Throws() { var message = new ServerWelcomeMessage(ProtocolConstants.Version, 1, 20, 1f, 1f); var buffer = new byte[4]; Assert.Throws(() => ProtocolCodec.WriteWelcome(buffer, message)); } }