using System.Globalization; using System.Text.Json; namespace XFEEliteDangerous.NativeFormat.Replication; /// /// A bounded reader for the confirmed IP4 form of a native server/list record. /// No authentication, packet cipher, handshake or gameplay success is implied. /// public sealed class EdServerDescriptor { public const int MaximumEncryptionKeyBytes = 4096; public ulong RunId { get; } public ulong Id { get; } public EdServerIp4Endpoint Endpoint { get; } public ReadOnlyMemory EncryptionKey { get; } private EdServerDescriptor(ulong runId, ulong id, EdServerIp4Endpoint endpoint, byte[] encryptionKey) => (RunId, Id, Endpoint, EncryptionKey) = (runId, id, endpoint, encryptionKey); public static EdServerDescriptor Parse(JsonElement record) { if (record.ValueKind != JsonValueKind.Object) throw new FormatException("The server descriptor must be an object."); var runIdText = RequiredString(record, "runId"); if (!TryUnsigned(runIdText, out var runId)) throw new FormatException("The server descriptor runId must be an unsigned decimal string."); var endpoint = EdServerIp4Endpoint.Parse(RequiredString(record, "connectionDetails")); var key = DecodeEncryptionKey(RequiredString(record, "encryptionKey")); ulong id = 0; if (record.TryGetProperty("id", out var idElement)) { if (idElement.ValueKind == JsonValueKind.String) { if (!TryUnsigned(idElement.GetString()!, out id)) throw new FormatException("The server descriptor id must be an unsigned integer."); } else if (idElement.ValueKind != JsonValueKind.Number || !idElement.TryGetUInt64(out id)) throw new FormatException("The server descriptor id must be an unsigned integer."); } return new(runId, id, endpoint, key); } /// /// Native helper RVA 0x545120 decodes two hexadecimal characters per byte. /// The cipher's key-size requirement is not yet established. Odd or invalid /// text is rejected here rather than imitating the native zero substitution. /// public static byte[] DecodeEncryptionKey(string hex) { ArgumentNullException.ThrowIfNull(hex); if ((hex.Length & 1) != 0 || hex.Length > MaximumEncryptionKeyBytes * 2) throw new FormatException("The encryption key must be bounded, even-length hexadecimal text."); try { return Convert.FromHexString(hex); } catch (FormatException) { throw new FormatException("The encryption key contains a non-hexadecimal character."); } } public override string ToString() => $"EdServerDescriptor {{ Endpoint = {Endpoint}, EncryptionKey = [redacted] }}"; private static string RequiredString(JsonElement record, string property) => record.TryGetProperty(property, out var value) && value.ValueKind == JsonValueKind.String ? value.GetString()! : throw new FormatException($"The server descriptor requires string field '{property}'."); private static bool TryUnsigned(string text, out ulong value) => ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out value); }