diff --git a/Projects/Server.Tests/Tests/Network/NetworkCompressionTests.cs b/Projects/Server.Tests/Tests/Network/NetworkCompressionTests.cs new file mode 100644 index 000000000..826d44deb --- /dev/null +++ b/Projects/Server.Tests/Tests/Network/NetworkCompressionTests.cs @@ -0,0 +1,68 @@ +using System; +using Server.Network; +using Xunit; + +namespace Server.Tests.Network; + +/// +/// Known-answer test for outgoing huffman compression. +/// The table is a contract with the client's hard-coded decompression tree, so a single wrong +/// entry -- or a subtle bit-packing error in a future rewrite of -- +/// silently corrupts every affected packet and desyncs the client's framing with no error. +/// A round-trip test cannot catch that, since encoder and decoder would share the same mistake. +/// The expected bytes below were generated from the canonical table, not from the implementation. +/// +public class NetworkCompressionTests +{ + // Every symbol 0x00-0xFF exactly once. Any single wrong table entry changes these bytes, so this + // pins all 256 entries plus the terminal code, and exercises the encoder end to end. + private static ReadOnlySpan AllSymbolsCompressed => + [ + 0x3F, 0x13, 0x4E, 0xB4, 0x76, 0xCB, 0x81, 0x8A, 0xB3, 0xCE, 0x76, 0x5E, + 0xAA, 0xEE, 0x2B, 0x7C, 0xCA, 0x74, 0xB3, 0xD9, 0x9F, 0x72, 0x3C, 0xE7, + 0xE9, 0x05, 0x97, 0xB9, 0x1C, 0x6B, 0x70, 0xCD, 0xE9, 0x37, 0x31, 0x69, + 0xAE, 0x70, 0x29, 0x14, 0xA4, 0x13, 0x3F, 0x71, 0x90, 0x39, 0xDF, 0x3A, + 0x5C, 0x03, 0x20, 0x9D, 0x86, 0x4B, 0x33, 0xCA, 0xDC, 0xF9, 0x48, 0x6F, + 0x4B, 0x6F, 0xD8, 0x8B, 0x42, 0xAA, 0x5A, 0x73, 0x87, 0x9C, 0x47, 0xE3, + 0xD2, 0x28, 0x76, 0x35, 0xDD, 0x12, 0xD6, 0x46, 0x7B, 0xBD, 0x68, 0x4B, + 0x25, 0xD7, 0x9A, 0xBC, 0x88, 0x95, 0xDC, 0xA1, 0x48, 0xD9, 0xD4, 0x71, + 0xE1, 0x78, 0x30, 0xEE, 0xE3, 0xBF, 0xD3, 0x3F, 0x1B, 0xC9, 0xE5, 0xD4, + 0xE1, 0x40, 0x83, 0x0B, 0x58, 0x35, 0x60, 0xF1, 0xF1, 0x63, 0x4C, 0x86, + 0xED, 0x9A, 0x6A, 0x2A, 0xFC, 0x96, 0x75, 0x26, 0xC0, 0xDD, 0x33, 0xDC, + 0xFB, 0x97, 0x29, 0x84, 0x93, 0x30, 0x6C, 0x90, 0xFA, 0x1C, 0x14, 0x75, + 0x63, 0xE1, 0x7D, 0xB5, 0xF5, 0x06, 0xE5, 0xE4, 0xE5, 0xD3, 0x19, 0xE6, + 0x5C, 0xF4, 0xAF, 0xFD, 0xF7, 0x77, 0x2D, 0xBB, 0x70, 0xB4, 0x1E, 0xE8, + 0xF7, 0x92, 0x57, 0xA1, 0xD2, 0xE6, 0xB6, 0xB6, 0x58, 0xCA, 0xE1, 0x75, + 0xCF, 0x19, 0xF1, 0x40, 0xB4, 0xD4, 0x91, 0x66, 0xC7, 0x6F, 0x16, 0x51, + 0xBA, 0x65, 0x2F, 0x53, 0xA0, 0x72, 0x31, 0xE3, 0x92, 0x7A, 0xB1, 0x9D, + 0x98, 0xE4, 0x1B, 0xB7, 0x42, 0xF1, 0x21, 0x18, 0x0E, 0x55, 0x18, 0xBE, + 0x0A, 0x6D, 0xEB, 0xC0, 0x4E, 0xB6, 0x13, 0x94, 0xF2, 0xC8, 0xF6, 0x3F, + 0xCB, 0xD1, 0xCE, 0x13, 0x1C, 0x5F, 0x30, 0xAB, 0x82, 0x76, 0x94, 0x1F, + 0x1F, 0x2C, 0xC7, 0xC0, 0x90, 0x34, 0x61, 0x32, 0xC2, 0x51, 0xF3, 0xAC, + 0x72, 0x6F, 0x85, 0xDE, 0x7A, 0xD8, 0xEB, 0xE5, 0x7C, 0x90, 0x3B, 0xC7, + 0x9D, 0xDE, 0xA6, 0x02, 0xC5, 0xED, 0x24, 0x8D, 0xA4, 0xC9, 0x9E, 0xBA, + 0x2D, 0x7F, 0x3D, 0x27, 0xB7, 0x39, 0x25, 0xB9, 0x59, 0xE1, 0x08, 0xE9, + 0xB8, 0x75, 0xEC, 0x75, 0xD9, 0x8B, 0x40 + ]; + + [Fact] + public void Compress_EncodesEverySymbol_MatchingCanonicalTable() + { + Span input = stackalloc byte[256]; + for (var i = 0; i < input.Length; i++) + { + input[i] = (byte)i; + } + + Span output = stackalloc byte[512]; + var length = NetworkCompression.Compress(input, output); + + Assert.Equal(AllSymbolsCompressed.Length, length); + + var expected = AllSymbolsCompressed; + for (var i = 0; i < expected.Length; i++) + { + Assert.True(expected[i] == output[i], $"Byte {i}: expected 0x{expected[i]:X2}, got 0x{output[i]:X2}"); + } + } +} diff --git a/Projects/Server/Network/NetworkCompression.cs b/Projects/Server/Network/NetworkCompression.cs index eaef6fa7f..1a0154d40 100644 --- a/Projects/Server/Network/NetworkCompression.cs +++ b/Projects/Server/Network/NetworkCompression.cs @@ -31,7 +31,7 @@ public static class NetworkCompression 0x2, 0x000, 0x5, 0x01F, 0x6, 0x022, 0x7, 0x034, 0x7, 0x075, 0x6, 0x028, 0x6, 0x03B, 0x7, 0x032, 0x8, 0x0E0, 0x8, 0x062, 0x7, 0x056, 0x8, 0x079, 0x9, 0x19D, 0x8, 0x097, 0x6, 0x02A, 0x7, 0x057, 0x8, 0x071, 0x8, 0x05B, 0x9, 0x1CC, 0x8, 0x0A7, 0x7, 0x025, 0x7, 0x04F, 0x8, 0x066, 0x8, 0x07D, - 0x9, 0x191, 0x9, 0x12E, 0x7, 0x03F, 0x9, 0x090, 0x8, 0x059, 0x8, 0x07B, 0x8, 0x091, 0x8, 0x0C6, + 0x9, 0x191, 0x9, 0x1CE, 0x7, 0x03F, 0x9, 0x090, 0x8, 0x059, 0x8, 0x07B, 0x8, 0x091, 0x8, 0x0C6, 0x6, 0x02D, 0x9, 0x186, 0x8, 0x06F, 0x9, 0x093, 0xA, 0x1CC, 0x8, 0x05A, 0xA, 0x1AE, 0xA, 0x1C0, 0x9, 0x148, 0x9, 0x14A, 0x9, 0x082, 0xA, 0x19F, 0x9, 0x171, 0x9, 0x120, 0x9, 0x0E7, 0xA, 0x1F3, 0x9, 0x14B, 0x9, 0x100, 0x9, 0x190, 0x6, 0x013, 0x9, 0x161, 0x9, 0x125, 0x9, 0x133, 0x9, 0x195,