Closes #4
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace FinalFactory.Rendezvous.Contracts;
|
||||
|
||||
public static class ContractValidation
|
||||
{
|
||||
private const string CapabilityAlphabet =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
|
||||
public static RendezvousErrorCode ValidateContractVersion(int contractVersion) =>
|
||||
contractVersion == ContractLimits.ContractVersion
|
||||
? RendezvousErrorCode.None
|
||||
: RendezvousErrorCode.UnsupportedContractVersion;
|
||||
|
||||
public static bool AreProtocolsCompatible(uint requested, uint offered) => requested == offered;
|
||||
|
||||
public static bool IsHttpRequestSizeValid(int byteCount) =>
|
||||
byteCount is >= 0 and <= ContractLimits.HttpRequestMaxBytes;
|
||||
|
||||
public static bool IsBrowserResponseSizeValid(int byteCount) =>
|
||||
byteCount is >= 0 and <= ContractLimits.BrowserResponseMaxBytes;
|
||||
|
||||
public static bool IsUtf8LengthWithin(string? value, int maximumBytes)
|
||||
{
|
||||
if (maximumBytes < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(maximumBytes));
|
||||
}
|
||||
|
||||
return value is not null && Encoding.UTF8.GetByteCount(value) <= maximumBytes;
|
||||
}
|
||||
|
||||
public static bool IsPageSizeValid(int pageSize) =>
|
||||
pageSize is >= 1 and <= ContractLimits.BrowserPageMaxItems;
|
||||
|
||||
public static bool IsIdempotencyKeyValid(string? value) =>
|
||||
IsVisibleAsciiWithin(value, ContractLimits.IdempotencyKeyMaxCharacters);
|
||||
|
||||
public static bool IsCursorValid(string? value) =>
|
||||
value is null || IsVisibleAsciiWithin(value, ContractLimits.CursorMaxCharacters);
|
||||
|
||||
public static bool IsDiagnosticCodeValid(string? value) =>
|
||||
value is null || IsVisibleAsciiWithin(value, ContractLimits.DiagnosticCodeMaxCharacters);
|
||||
|
||||
public static bool IsBuildVersionValid(string? value) =>
|
||||
IsUtf8LengthWithin(value, ContractLimits.BuildVersionMaxBytes);
|
||||
|
||||
public static bool IsDisplayNameValid(string? value) =>
|
||||
IsUtf8LengthWithin(value, ContractLimits.DisplayNameMaxBytes);
|
||||
|
||||
public static bool IsOpaqueHttpCredentialValid(string? value) =>
|
||||
value is not null
|
||||
&& value.Length is > 0 and <= ContractLimits.OpaqueHttpCredentialMaxCharacters;
|
||||
|
||||
public static bool IsCapacityValid(SessionCapacity? capacity) =>
|
||||
capacity is not null
|
||||
&& capacity.MaximumPlayers is >= 1 and <= ContractLimits.SessionCapacityMaxPlayers
|
||||
&& capacity.CurrentPlayers >= 0
|
||||
&& capacity.CurrentPlayers <= capacity.MaximumPlayers;
|
||||
|
||||
public static bool IsCapabilityValid(string? capability) =>
|
||||
IsBase64UrlValueValid(capability, ContractLimits.UdpCapabilityMaxCharacters);
|
||||
|
||||
public static bool IsConnectionTicketValid(string? ticket) =>
|
||||
IsBase64UrlValueValid(ticket, ContractLimits.ConnectionTicketMaxCharacters);
|
||||
|
||||
public static bool IsNetworkEndpointValid(NetworkEndpoint? endpoint)
|
||||
{
|
||||
if (endpoint is null
|
||||
|| endpoint.Port is < 1 or > ushort.MaxValue
|
||||
|| !IPAddress.TryParse(endpoint.Address, out IPAddress? address))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return endpoint.AddressFamily switch
|
||||
{
|
||||
AddressFamilyKind.Ipv4 => address.AddressFamily == AddressFamily.InterNetwork,
|
||||
AddressFamilyKind.Ipv6 => address.AddressFamily == AddressFamily.InterNetworkV6,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
public static bool IsMetadataValid(IReadOnlyDictionary<string, string>? metadata)
|
||||
{
|
||||
if (metadata is null || metadata.Count > ContractLimits.MetadataMaxKeys)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, string> item in metadata)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(item.Key)
|
||||
|| !IsUtf8LengthWithin(item.Key, ContractLimits.MetadataKeyMaxBytes)
|
||||
|| !IsUtf8LengthWithin(item.Value, ContractLimits.MetadataValueMaxBytes))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] encoded = JsonSerializer.SerializeToUtf8Bytes(metadata, ContractJson.Options);
|
||||
return encoded.Length <= ContractLimits.MetadataMaxBytes;
|
||||
}
|
||||
|
||||
internal static bool IsSlug(string? value, int maximumCharacters)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)
|
||||
|| value.Length > maximumCharacters
|
||||
|| value[0] is < 'a' or > 'z')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return value.All(static character =>
|
||||
character is >= 'a' and <= 'z'
|
||||
or >= '0' and <= '9'
|
||||
or '-');
|
||||
}
|
||||
|
||||
private static bool IsBase64UrlValueValid(string? value, int maximumCharacters) =>
|
||||
value is not null
|
||||
&& value.Length is > 0
|
||||
&& value.Length <= maximumCharacters
|
||||
&& value.All(static character => CapabilityAlphabet.Contains(character));
|
||||
|
||||
private static bool IsVisibleAsciiWithin(string? value, int maximumCharacters) =>
|
||||
value is not null
|
||||
&& value.Length is > 0
|
||||
&& value.Length <= maximumCharacters
|
||||
&& value.All(static character => character is >= '!' and <= '~');
|
||||
}
|
||||
Reference in New Issue
Block a user