XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 0
UTF-8
using System.Security.Cryptography;
using System.Text;
namespace XFEEliteDangerous.NativeFormat.Replication;

public sealed class EdServerRouteSchemaException(string kind,uint nameBytes,int depth):NotSupportedException("Unsupported bounded route schema.")
{
    public string Kind{get;}=kind;
    public uint NameBytes{get;}=nameBytes;
    public int Depth{get;}=depth;
}

public sealed class EdServerRouteMetadata
{
    private readonly byte[] digest;
    internal EdServerRouteMetadata(ulong machine,bool request,int endpoints,IReadOnlyList<string> schemes,byte[] digest)
        =>(MachineRunId,IsRequest,EndpointCount,Schemes,this.digest)=(machine,request,endpoints,schemes,digest);
    public ulong MachineRunId{get;}
    //4C373D0 explicitly labels flag1 as "request" and flag0 as "update".
    public bool IsRequest{get;}
    public int EndpointCount{get;}
    public IReadOnlyList<string> Schemes{get;}
    public byte[] CopyIdentityDigest()=>digest.ToArray();
    public override string ToString()=>$"Route metadata: request={IsRequest}, endpoints={EndpointCount}";
}

// Pinned readers:4C372C0 U48+bool;4C18B10 ordinary narrow type name;
// IP4 factory4C10A50;IP4NAT4C10D00;Comp4C113E0;Relay4C11C10.
// This parses metadata
// only. No advertised address is used to open, redirect or authorize a socket.
public static class EdServerRouteMessageCodec
{
    public static byte[] EncodeIp4Update(ulong machine,EdServerIp4Endpoint endpoint,int typeBits=16)
    {
        if(machine is 0 or >EdServerDatagramHeader.MaximumRunId||endpoint.Address!=0x7F000001||endpoint.Port==0||endpoint.Mtu is <576 or >1472)
            throw new ArgumentException("A private logical IPv4 route is required.");
        var bytes=new byte[(typeBits+169+7)/8];var p=0;
        EdServerControlMessageCodec.WriteLetterType(bytes,ref p,(byte)EdServerBuiltInLetter.Route,typeBits);
        void Write(ulong value,int count){for(var bit=0;bit<count;bit++,p++)if(((value>>bit)&1)!=0)bytes[p/8]|=(byte)(1<<(p%8));}
        Write(machine,48);Write(0,1);Write(3,32);foreach(var value in Encoding.ASCII.GetBytes("IP4"))Write(value,8);
        Write(endpoint.Address,32);Write(endpoint.Port,16);Write(endpoint.Mtu,16);
        return bytes;
    }
    public static EdServerRouteMetadata ReadBody(ReadOnlySpan<byte> bytes,ref int bitOffset)
    {
        var start=bitOffset;var p=start;var machine=Read(bytes,ref p,48);var removed=Read(bytes,ref p,1)!=0;
        var schemes=new List<string>();var endpoints=0;
        if(!removed)ReadEndpoint(bytes,ref p,0,schemes,ref endpoints);
        if(p-start>1472*8)throw new InvalidDataException("Route metadata exceeds the local datagram bound.");
        var canonical=new byte[(p-start+7)/8];
        try
        {
            for(var index=0;index<p-start;index++)if((bytes[(start+index)/8]&(1<<((start+index)%8)))!=0)canonical[index/8]|=(byte)(1<<(index%8));
            var digest=SHA256.HashData(canonical);bitOffset=p;
            return new(machine,removed,endpoints,Array.AsReadOnly(schemes.ToArray()),digest);
        }
        finally{CryptographicOperations.ZeroMemory(canonical);}
    }
    private static void ReadEndpoint(ReadOnlySpan<byte> bytes,ref int p,int depth,List<string> schemes,ref int endpoints)
    {
        if(depth>4||schemes.Count>=16)throw new InvalidDataException("Route nesting exceeds the local bound.");
        var length=Read(bytes,ref p,32);if(length>8)throw new EdServerRouteSchemaException("name-length",(uint)length,depth);
        Span<byte> name=stackalloc byte[8];
        for(var index=0;index<(int)length;index++)name[index]=(byte)Read(bytes,ref p,8);
        var scheme=Encoding.ASCII.GetString(name[..(int)length]);schemes.Add(scheme);
        switch(scheme)
        {
            case "":return;
            case "IP4":Read(bytes,ref p,32);Read(bytes,ref p,16);Read(bytes,ref p,16);endpoints++;return;
            // Factory4C11C80: two U64 address halves, U16 port, U16 MTU.
            // Parsed metadata only, no IPv6 child socket or route authority.
            case "IP6":Read(bytes,ref p,64);Read(bytes,ref p,64);Read(bytes,ref p,16);Read(bytes,ref p,16);endpoints++;return;
            // Turn6 factory4C11ED0 reads one U32 and U16.
            case "Turn6":Read(bytes,ref p,32);Read(bytes,ref p,16);endpoints++;return;
            // Factory4C11C10 ignores the stream argument and constructs the
            // marker via4C31430. There is no body after the scheme name.
            case "Relay":endpoints++;return;
            case "IP4NAT":
                for(var index=0;index<4;index++){Read(bytes,ref p,32);Read(bytes,ref p,16);}
                Read(bytes,ref p,8);Read(bytes,ref p,1);Read(bytes,ref p,1);Read(bytes,ref p,32);endpoints++;return;
            case "Comp":
                var count=Read(bytes,ref p,16);if(count>4)throw new InvalidDataException("Route collection exceeds the local bound.");
                for(var index=0;index<(int)count;index++)ReadEndpoint(bytes,ref p,depth+1,schemes,ref endpoints);return;
            default:throw new EdServerRouteSchemaException("unknown-name",(uint)length,depth);
        }
    }
    private static ulong Read(ReadOnlySpan<byte> bytes,ref int p,int count)
    {
        if(p<0||(long)p+count>(long)bytes.Length*8)throw new InvalidDataException("Route metadata is truncated.");
        ulong value=0;for(var bit=0;bit<count;bit++,p++)value|=(ulong)((bytes[p/8]>>(p%8))&1)<<bit;return value;
    }
}