using System.Text; using FinalFactory.Rendezvous.Contracts; namespace FinalFactory.Rendezvous.Client; public sealed class DirectConnectionRequest { public JoinAttemptId AttemptId { get; set; } public string ConnectionTicket { get; set; } = string.Empty; public override string ToString() => $"[DirectConnectionRequest {AttemptId}; ticket redacted]"; } public static class DirectConnectionRequestCodec { public const int EncodedLength = 63; private const int MagicLength = 4; private const int AttemptIdLength = 16; private const int TicketLength = ContractLimits.DerivedCredentialCharacters; private static readonly byte[] Magic = [(byte)'R', (byte)'V', (byte)'D', (byte)'1']; public static bool IsRendezvousRequest(ReadOnlySpan encoded) => encoded.Length >= MagicLength && encoded[..MagicLength].SequenceEqual(Magic); public static byte[] Encode(JoinAttemptId attemptId, string connectionTicket) { if (attemptId.Value == Guid.Empty || connectionTicket is null || connectionTicket.Length != TicketLength || !ContractValidation.IsConnectionTicketValid(connectionTicket)) { throw new ArgumentException("The direct connection request fields are invalid."); } byte[] encoded = new byte[EncodedLength]; Magic.CopyTo(encoded, 0); if (!attemptId.Value.TryWriteBytes(encoded.AsSpan(MagicLength, AttemptIdLength))) { throw new InvalidOperationException("The join attempt identifier could not be encoded."); } Encoding.ASCII.GetBytes( connectionTicket, 0, connectionTicket.Length, encoded, MagicLength + AttemptIdLength); return encoded; } public static bool TryDecode( ReadOnlySpan encoded, out DirectConnectionRequest? request) { request = null; if (encoded.Length != EncodedLength || !encoded[..MagicLength].SequenceEqual(Magic)) { return false; } Guid attemptId = new(encoded.Slice(MagicLength, AttemptIdLength)); if (attemptId == Guid.Empty) { return false; } string ticket = Encoding.ASCII.GetString(encoded[(MagicLength + AttemptIdLength)..]); if (!ContractValidation.IsConnectionTicketValid(ticket)) { return false; } request = new DirectConnectionRequest { AttemptId = new JoinAttemptId(attemptId), ConnectionTicket = ticket, }; return true; } }