using System.Buffers.Binary; using System.Net; using System.Text; using FinalFactory.Rendezvous.Contracts; namespace FinalFactory.Rendezvous.Server.Transport; internal static class LiteNetNatRequestCodec { private const byte NatMessageProperty = 17; private const int TypeIdentifierLength = 8; private const int TokenLengthPrefix = NatPunchRequestTokenCodec.EncodedLength + 1; // LiteNetLib 2.1.4's private NatIntroduceRequest type ID. The native socket // integration test deliberately fails if a package upgrade changes this wire value. private static ReadOnlySpan RequestTypeIdentifier => [0x88, 0xbe, 0x10, 0x26, 0xbf, 0xb1, 0x66, 0x9c]; public static bool TryDecode( ReadOnlySpan datagram, out IPEndPoint? claimedLocalEndpoint, out string? token) { claimedLocalEndpoint = null; token = null; if (datagram.Length < 1 + TypeIdentifierLength + 1 + 4 + 2 + 2 + NatPunchRequestTokenCodec.EncodedLength || datagram[0] != NatMessageProperty || !datagram.Slice(1, TypeIdentifierLength).SequenceEqual(RequestTypeIdentifier)) { return false; } int offset = 1 + TypeIdentifierLength; int addressLength = datagram[offset++] switch { 0 => 4, 1 => 16, _ => 0, }; int expectedLength = offset + addressLength + 2 + 2 + NatPunchRequestTokenCodec.EncodedLength; if (addressLength == 0 || datagram.Length != expectedLength) { return false; } IPAddress localAddress = new(datagram.Slice(offset, addressLength)); offset += addressLength; int localPort = BinaryPrimitives.ReadUInt16LittleEndian(datagram.Slice(offset, 2)); offset += 2; int encodedTokenLength = BinaryPrimitives.ReadUInt16LittleEndian(datagram.Slice(offset, 2)); offset += 2; if (localPort == 0 || encodedTokenLength != TokenLengthPrefix) { return false; } ReadOnlySpan tokenBytes = datagram.Slice( offset, NatPunchRequestTokenCodec.EncodedLength); for (int index = 0; index < tokenBytes.Length; index++) { if (tokenBytes[index] > 0x7f) { return false; } } claimedLocalEndpoint = new(localAddress, localPort); token = Encoding.ASCII.GetString(tokenBytes); return true; } }