158 lines
5.0 KiB
C#
158 lines
5.0 KiB
C#
namespace HSchool.Protocol.Tests;
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// <c>src/HSchool.Client/src/net/protocol.ts</c> honest.
|
|
/// </summary>
|
|
public class ProtocolCodecTests
|
|
{
|
|
[Fact]
|
|
public void Hello_RoundTrips()
|
|
{
|
|
var message = new ClientHelloMessage(ProtocolConstants.Version, "ada");
|
|
Span<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<EntitySnapshot> 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<byte> 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<byte> 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<ProtocolException>(() => ProtocolCodec.ReadWelcome(frame));
|
|
}
|
|
|
|
[Fact]
|
|
public void WrongMessageId_Throws()
|
|
{
|
|
Span<byte> buffer = stackalloc byte[16];
|
|
var length = ProtocolCodec.WritePing(buffer, new ClientPingMessage(1));
|
|
var frame = buffer[..length].ToArray();
|
|
|
|
Assert.Throws<ProtocolException>(() => 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<ProtocolException>(() => 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<ProtocolException>(() => ProtocolCodec.WriteWelcome(buffer, message));
|
|
}
|
|
}
|