using FinalFactory.Rendezvous.Contracts; namespace FinalFactory.Rendezvous.Tests.Contracts; public sealed class UdpCodecTests { private static readonly Guid Handle = new("00112233-4455-6677-8899-aabbccddeeff"); [Fact] public void HostPresenceMatchesTheV1GoldenVector() { byte[] encoded = RendezvousUdpCodec.Encode(CreateDatagram()); Assert.Equal(ContractTestFiles.Read("udp-host-ipv4.hex"), Convert.ToHexString(encoded).ToLowerInvariant()); Assert.True(RendezvousUdpCodec.TryDecode(encoded, out PresenceDatagram? decoded, out UdpDecodeError error)); Assert.Equal(UdpDecodeError.None, error); Assert.NotNull(decoded); Assert.Equal(Handle, decoded.MediationHandle.Value); Assert.Equal("192.0.2.10", decoded.LocalAddress); Assert.Equal(9050, decoded.LocalPort); Assert.Equal("Abc_123-xYz", decoded.Capability); } [Fact] public void Ipv6RoundtripsWithoutLosingItsAddressFamily() { PresenceDatagram original = CreateDatagram(); original.MessageType = UdpPresenceMessageType.ClientPresence; original.AddressFamily = AddressFamilyKind.Ipv6; original.LocalAddress = "2001:db8::10"; byte[] encoded = RendezvousUdpCodec.Encode(original); Assert.True(RendezvousUdpCodec.TryDecode(encoded, out PresenceDatagram? decoded, out _)); Assert.NotNull(decoded); Assert.Equal(AddressFamilyKind.Ipv6, decoded.AddressFamily); Assert.Equal("2001:db8::10", decoded.LocalAddress); } [Fact] public void EncoderRejectsAnAddressFamilyMismatch() { PresenceDatagram datagram = CreateDatagram(); datagram.AddressFamily = AddressFamilyKind.Ipv4; datagram.LocalAddress = "2001:db8::10"; Assert.Throws(() => RendezvousUdpCodec.Encode(datagram)); } [Theory] [InlineData(2, 2, UdpDecodeError.UnsupportedVersion)] [InlineData(3, 3, UdpDecodeError.UnknownMessageType)] [InlineData(4, 1, UdpDecodeError.InvalidFlags)] [InlineData(21, 5, UdpDecodeError.InvalidAddressFamily)] public void DecoderReturnsStableErrorsForUnknownHeaderValues( int offset, byte replacement, UdpDecodeError expected) { byte[] encoded = RendezvousUdpCodec.Encode(CreateDatagram()); encoded[offset] = replacement; Assert.False(RendezvousUdpCodec.TryDecode(encoded, out _, out UdpDecodeError actual)); Assert.Equal(expected, actual); } [Fact] public void DecoderRejectsTruncationTrailingDataAndOversizedPackets() { byte[] encoded = RendezvousUdpCodec.Encode(CreateDatagram()); byte[] trailing = [.. encoded, 0]; byte[] oversized = new byte[ContractLimits.UdpDatagramMaxBytes + 1]; Assert.False(RendezvousUdpCodec.TryDecode(encoded.AsSpan(0, encoded.Length - 1), out _, out UdpDecodeError truncated)); Assert.Equal(UdpDecodeError.Truncated, truncated); Assert.False(RendezvousUdpCodec.TryDecode(trailing, out _, out UdpDecodeError trailingError)); Assert.Equal(UdpDecodeError.TrailingData, trailingError); Assert.False(RendezvousUdpCodec.TryDecode(oversized, out _, out UdpDecodeError oversizedError)); Assert.Equal(UdpDecodeError.DatagramTooLarge, oversizedError); } [Fact] public void DecoderRejectsNonBase64UrlCapabilities() { byte[] encoded = RendezvousUdpCodec.Encode(CreateDatagram()); encoded[^1] = (byte)'+'; Assert.False(RendezvousUdpCodec.TryDecode(encoded, out _, out UdpDecodeError error)); Assert.Equal(UdpDecodeError.InvalidCapability, error); } private static PresenceDatagram CreateDatagram() => new() { MessageType = UdpPresenceMessageType.HostPresence, MediationHandle = new MediationHandle(Handle), AddressFamily = AddressFamilyKind.Ipv4, LocalAddress = "192.0.2.10", LocalPort = 9050, Capability = "Abc_123-xYz", }; }