132 lines
4.0 KiB
C#
132 lines
4.0 KiB
C#
namespace FinalFactory.Rendezvous.Contracts;
|
|
|
|
public enum NatPunchPeerRole
|
|
{
|
|
HostPresence = 1,
|
|
Host = 2,
|
|
Client = 3,
|
|
}
|
|
|
|
public sealed class NatPunchRequestToken
|
|
{
|
|
public NatPunchPeerRole Role { get; set; }
|
|
public MediationHandle MediationHandle { get; set; }
|
|
public string Capability { get; set; } = string.Empty;
|
|
|
|
public override string ToString() => "[NatPunchRequestToken: capability redacted]";
|
|
}
|
|
|
|
public static class NatPunchRequestTokenCodec
|
|
{
|
|
public const int EncodedLength = ContractLimits.NatPunchRequestTokenCharacters;
|
|
|
|
private const string VersionPrefix = "rv1:";
|
|
private const int HandleLength = 32;
|
|
private const int CapabilityLength = ContractLimits.DerivedCredentialCharacters;
|
|
private const char Separator = ':';
|
|
private const char Padding = '.';
|
|
|
|
public static string Encode(
|
|
NatPunchPeerRole role,
|
|
MediationHandle mediationHandle,
|
|
string capability)
|
|
{
|
|
if (!TryGetRoleCode(role, out char roleCode)
|
|
|| mediationHandle.Value == Guid.Empty
|
|
|| capability is null
|
|
|| capability.Length != CapabilityLength
|
|
|| !ContractValidation.IsCapabilityValid(capability))
|
|
{
|
|
throw new ArgumentException("The NAT punch request token fields are invalid.");
|
|
}
|
|
|
|
string payload = string.Concat(
|
|
VersionPrefix,
|
|
roleCode,
|
|
Separator,
|
|
mediationHandle.Value.ToString("N"),
|
|
Separator,
|
|
capability);
|
|
return payload.PadRight(EncodedLength, Padding);
|
|
}
|
|
|
|
public static bool TryDecode(string? encoded, out NatPunchRequestToken? token)
|
|
{
|
|
token = null;
|
|
if (encoded is null
|
|
|| encoded.Length != EncodedLength
|
|
|| !encoded.StartsWith(VersionPrefix, StringComparison.Ordinal)
|
|
|| !TryParseRole(encoded[VersionPrefix.Length], out NatPunchPeerRole role))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int roleSeparator = VersionPrefix.Length + 1;
|
|
int handleOffset = roleSeparator + 1;
|
|
int capabilitySeparator = handleOffset + HandleLength;
|
|
int capabilityOffset = capabilitySeparator + 1;
|
|
int paddingOffset = capabilityOffset + CapabilityLength;
|
|
string handleText = encoded.Substring(handleOffset, HandleLength);
|
|
if (encoded[roleSeparator] != Separator
|
|
|| encoded[capabilitySeparator] != Separator
|
|
|| !Guid.TryParseExact(handleText, "N", out Guid handle)
|
|
|| handle == Guid.Empty
|
|
|| !string.Equals(handleText, handle.ToString("N"), StringComparison.Ordinal)
|
|
|| !ContainsOnlyPadding(encoded, paddingOffset))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
string capability = encoded.Substring(capabilityOffset, CapabilityLength);
|
|
if (!ContractValidation.IsCapabilityValid(capability))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
token = new NatPunchRequestToken
|
|
{
|
|
Role = role,
|
|
MediationHandle = new MediationHandle(handle),
|
|
Capability = capability,
|
|
};
|
|
return true;
|
|
}
|
|
|
|
private static bool TryGetRoleCode(NatPunchPeerRole role, out char code)
|
|
{
|
|
code = role switch
|
|
{
|
|
NatPunchPeerRole.HostPresence => 'p',
|
|
NatPunchPeerRole.Host => 'h',
|
|
NatPunchPeerRole.Client => 'c',
|
|
_ => default,
|
|
};
|
|
return code != default;
|
|
}
|
|
|
|
private static bool TryParseRole(char code, out NatPunchPeerRole role)
|
|
{
|
|
role = code switch
|
|
{
|
|
'p' => NatPunchPeerRole.HostPresence,
|
|
'h' => NatPunchPeerRole.Host,
|
|
'c' => NatPunchPeerRole.Client,
|
|
_ => default,
|
|
};
|
|
return role != default;
|
|
}
|
|
|
|
private static bool ContainsOnlyPadding(string value, int offset)
|
|
{
|
|
for (int index = offset; index < value.Length; index++)
|
|
{
|
|
if (value[index] != Padding)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|