184 lines
6.4 KiB
C#
184 lines
6.4 KiB
C#
using FinalFactory.Rendezvous.Contracts;
|
|
using LiteNetLib;
|
|
|
|
namespace FinalFactory.Rendezvous.Client;
|
|
|
|
public enum RendezvousConnectionOutcomeSource
|
|
{
|
|
RendezvousService = 1,
|
|
LocalTraversal = 2,
|
|
RemoteHost = 3,
|
|
Caller = 4,
|
|
Lifecycle = 5,
|
|
}
|
|
|
|
public enum RendezvousConnectionFailureCategory
|
|
{
|
|
None = 0,
|
|
Directory = 1,
|
|
Compatibility = 2,
|
|
Authorization = 3,
|
|
Capacity = 4,
|
|
HostPresence = 5,
|
|
Service = 6,
|
|
Mediation = 7,
|
|
NatTraversal = 8,
|
|
DirectConnection = 9,
|
|
Lifecycle = 10,
|
|
}
|
|
|
|
public enum RendezvousConnectionPhase
|
|
{
|
|
Directory = 1,
|
|
Authorization = 2,
|
|
Mediation = 3,
|
|
NatTraversal = 4,
|
|
DirectConnection = 5,
|
|
Complete = 6,
|
|
}
|
|
|
|
public sealed class RendezvousConnectionOutcome
|
|
{
|
|
private readonly NetworkEndpoint? _dedicatedFallback;
|
|
|
|
private RendezvousConnectionOutcome(
|
|
ConnectionOutcomeKind kind,
|
|
RendezvousConnectionOutcomeSource source,
|
|
RendezvousConnectionFailureCategory category,
|
|
RendezvousConnectionPhase phase,
|
|
TimeSpan elapsed,
|
|
RendezvousErrorCode? serviceError,
|
|
NetworkEndpoint? dedicatedFallback,
|
|
NetPeer? peer)
|
|
{
|
|
if (elapsed < TimeSpan.Zero)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(elapsed));
|
|
}
|
|
|
|
if (dedicatedFallback is not null
|
|
&& !ContractValidation.IsNetworkEndpointValid(dedicatedFallback))
|
|
{
|
|
throw new ArgumentException("The dedicated fallback endpoint is invalid.", nameof(dedicatedFallback));
|
|
}
|
|
|
|
Kind = kind;
|
|
Source = source;
|
|
Category = category;
|
|
Phase = phase;
|
|
Elapsed = elapsed;
|
|
ServiceError = serviceError;
|
|
_dedicatedFallback = RendezvousEndpoint.Copy(dedicatedFallback);
|
|
Peer = peer;
|
|
}
|
|
|
|
public ConnectionOutcomeKind Kind { get; }
|
|
public RendezvousConnectionOutcomeSource Source { get; }
|
|
public RendezvousConnectionFailureCategory Category { get; }
|
|
public RendezvousConnectionPhase Phase { get; }
|
|
public TimeSpan Elapsed { get; }
|
|
public RendezvousErrorCode? ServiceError { get; }
|
|
public NetworkEndpoint? DedicatedFallback => RendezvousEndpoint.Copy(_dedicatedFallback);
|
|
public NetPeer? Peer { get; }
|
|
public bool IsSuccess => Kind == ConnectionOutcomeKind.Connected;
|
|
public bool HasDedicatedFallback => _dedicatedFallback is not null;
|
|
|
|
public static RendezvousConnectionOutcome FromServiceError(
|
|
RendezvousErrorCode error,
|
|
TimeSpan elapsed,
|
|
NetworkEndpoint? dedicatedFallback = null)
|
|
{
|
|
if (error == RendezvousErrorCode.None)
|
|
{
|
|
throw new ArgumentException("A service failure outcome requires an error.", nameof(error));
|
|
}
|
|
|
|
(ConnectionOutcomeKind kind, RendezvousConnectionFailureCategory category, RendezvousConnectionPhase phase) =
|
|
error switch
|
|
{
|
|
RendezvousErrorCode.NotFound => (
|
|
ConnectionOutcomeKind.DirectoryNotFound,
|
|
RendezvousConnectionFailureCategory.Directory,
|
|
RendezvousConnectionPhase.Directory),
|
|
RendezvousErrorCode.Expired => (
|
|
ConnectionOutcomeKind.AttemptExpired,
|
|
RendezvousConnectionFailureCategory.Authorization,
|
|
RendezvousConnectionPhase.Authorization),
|
|
RendezvousErrorCode.IncompatibleProtocol => (
|
|
ConnectionOutcomeKind.IncompatibleProtocol,
|
|
RendezvousConnectionFailureCategory.Compatibility,
|
|
RendezvousConnectionPhase.Directory),
|
|
RendezvousErrorCode.AuthenticationRequired
|
|
or RendezvousErrorCode.Forbidden
|
|
or RendezvousErrorCode.ReplayRejected => (
|
|
ConnectionOutcomeKind.Unauthorized,
|
|
RendezvousConnectionFailureCategory.Authorization,
|
|
RendezvousConnectionPhase.Authorization),
|
|
RendezvousErrorCode.RateLimited
|
|
or RendezvousErrorCode.CapacityExceeded => (
|
|
ConnectionOutcomeKind.RateLimited,
|
|
RendezvousConnectionFailureCategory.Capacity,
|
|
RendezvousConnectionPhase.Authorization),
|
|
RendezvousErrorCode.StaleHost => (
|
|
ConnectionOutcomeKind.NoHostPresence,
|
|
RendezvousConnectionFailureCategory.HostPresence,
|
|
RendezvousConnectionPhase.Mediation),
|
|
RendezvousErrorCode.ServiceUnavailable => (
|
|
ConnectionOutcomeKind.ServiceUnavailable,
|
|
RendezvousConnectionFailureCategory.Service,
|
|
RendezvousConnectionPhase.Authorization),
|
|
_ => (
|
|
ConnectionOutcomeKind.ServiceRejected,
|
|
RendezvousConnectionFailureCategory.Service,
|
|
RendezvousConnectionPhase.Authorization),
|
|
};
|
|
return new(
|
|
kind,
|
|
RendezvousConnectionOutcomeSource.RendezvousService,
|
|
category,
|
|
phase,
|
|
elapsed,
|
|
error,
|
|
dedicatedFallback,
|
|
null);
|
|
}
|
|
|
|
public static ConnectionElapsedBucket BucketElapsed(TimeSpan elapsed)
|
|
{
|
|
if (elapsed < TimeSpan.Zero)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(elapsed));
|
|
}
|
|
|
|
return elapsed.TotalSeconds switch
|
|
{
|
|
< 1 => ConnectionElapsedBucket.UnderOneSecond,
|
|
< 5 => ConnectionElapsedBucket.OneToFiveSeconds,
|
|
< 15 => ConnectionElapsedBucket.FiveToFifteenSeconds,
|
|
< 30 => ConnectionElapsedBucket.FifteenToThirtySeconds,
|
|
_ => ConnectionElapsedBucket.ThirtySecondsOrMore,
|
|
};
|
|
}
|
|
|
|
public override string ToString() =>
|
|
$"[RendezvousConnectionOutcome {Kind}; {Source}; credentials redacted]";
|
|
|
|
internal static RendezvousConnectionOutcome Create(
|
|
ConnectionOutcomeKind kind,
|
|
RendezvousConnectionOutcomeSource source,
|
|
RendezvousConnectionFailureCategory category,
|
|
RendezvousConnectionPhase phase,
|
|
TimeSpan elapsed,
|
|
NetworkEndpoint? dedicatedFallback = null,
|
|
NetPeer? peer = null) => new(
|
|
kind,
|
|
source,
|
|
category,
|
|
phase,
|
|
elapsed,
|
|
null,
|
|
dedicatedFallback,
|
|
peer);
|
|
|
|
}
|