using FinalFactory.Rendezvous.Contracts; namespace FinalFactory.Rendezvous.Server.State; internal sealed class InMemoryEphemeralRendezvousStore : IEphemeralRendezvousStore { private static readonly TimeSpan UdpMaintenanceInterval = TimeSpan.FromSeconds(1); private readonly object _gate = new(); private readonly EphemeralStoreOptions _options; private readonly IMonotonicClock _monotonicClock; private readonly DateTimeOffset _wallOrigin; private readonly TimeSpan _monotonicOrigin; private readonly Dictionary _listings = []; private readonly Dictionary _leases = []; private readonly Dictionary _presenceHandles = []; private readonly Dictionary _presence = []; private readonly Dictionary _attempts = []; private readonly Dictionary _outcomeReports = []; private readonly Dictionary _attemptHandles = []; private readonly Dictionary _idempotency = new(StringComparer.Ordinal); private readonly Dictionary _replay = new(StringComparer.Ordinal); private readonly Dictionary _revocations = new(StringComparer.Ordinal); private TimeSpan? _drainDeadline; private TimeSpan _nextUdpMaintenance; private long _maintenanceSweepCount; private bool _available = true; public InMemoryEphemeralRendezvousStore( EphemeralStoreOptions options, IWallClock wallClock, IMonotonicClock monotonicClock) { ArgumentNullException.ThrowIfNull(options); ArgumentNullException.ThrowIfNull(wallClock); ArgumentNullException.ThrowIfNull(monotonicClock); options.Validate(); _options = options; _monotonicClock = monotonicClock; _wallOrigin = wallClock.UtcNow; _monotonicOrigin = monotonicClock.Elapsed; InstanceId = Guid.NewGuid(); } public Guid InstanceId { get; } internal long MaintenanceSweepCount => Interlocked.Read(ref _maintenanceSweepCount); public bool IsAvailable { get { lock (_gate) { return _available; } } } public bool IsDraining { get { lock (_gate) { return _drainDeadline.HasValue; } } } public StoreResult CreateListing( CreateListingCommand command, CancellationToken cancellationToken = default) => Atomic(now => { ArgumentNullException.ThrowIfNull(command); ValidateListing(command.Listing); if (command.OwnerListingLimit <= 0) { throw new ArgumentOutOfRangeException(nameof(command), "Owner listing limit must be positive."); } ValidateIdempotency(command.IdempotencyKey, command.RequestFingerprint); StoreResult? admission = CheckNewWorkAdmission(command.Listing.OwnerSubject); if (admission is not null) { return admission; } string idempotencyKey = $"listing:{command.Listing.Scope.GameId}:{command.Listing.Scope.EnvironmentId}:{command.Listing.OwnerSubject}:{command.IdempotencyKey}"; if (_idempotency.TryGetValue(idempotencyKey, out IdempotencyEntry? previous)) { if (!string.Equals(previous.RequestFingerprint, command.RequestFingerprint, StringComparison.Ordinal)) { return new(StoreResultCode.Conflict); } if (previous.ResourceId is SessionListingId listingId && _listings.TryGetValue(listingId, out ListingEntry? existing)) { return new(StoreResultCode.Success, Snapshot(existing), true); } return new(StoreResultCode.Expired); } if (_listings.Count >= _options.MaxListings || _idempotency.Count >= _options.MaxIdempotencyEntries || _listings.Values.Count(entry => string.Equals( entry.Definition.OwnerSubject, command.Listing.OwnerSubject, StringComparison.Ordinal)) >= command.OwnerListingLimit) { return new(StoreResultCode.CapacityExceeded); } ListingDefinition frozen = StoredListing.Freeze(command.Listing); if (_listings.ContainsKey(frozen.ListingId) || _leases.ContainsKey(frozen.LeaseId) || HandleExists(frozen.HostPresenceHandle)) { return new(StoreResultCode.Conflict); } ListingEntry entry = new( frozen, now + _options.LeaseLifetime, WallDeadline(now, _options.LeaseLifetime), version: 1); _listings.Add(frozen.ListingId, entry); _leases.Add(frozen.LeaseId, frozen.ListingId); _presenceHandles.Add(frozen.HostPresenceHandle, frozen.ListingId); _idempotency.Add(idempotencyKey, new( command.RequestFingerprint, frozen.ListingId, now + _options.IdempotencyLifetime)); return new(StoreResultCode.Success, Snapshot(entry)); }, cancellationToken); public StoreResult RenewLease( RenewLeaseCommand command, CancellationToken cancellationToken = default) => Atomic(now => { ArgumentNullException.ThrowIfNull(command); if (!_available) { return new(StoreResultCode.ServiceUnavailable); } if (_drainDeadline.HasValue) { return new(StoreResultCode.Draining); } if (!_listings.TryGetValue(command.ListingId, out ListingEntry? entry)) { return new(StoreResultCode.NotFound); } if (entry.Definition.LeaseId != command.LeaseId || entry.Definition.LeaseFingerprint != command.LeaseFingerprint || !string.Equals(entry.Definition.OwnerSubject, command.OwnerSubject, StringComparison.Ordinal)) { return new(StoreResultCode.NotFound); } if (entry.Version != command.ExpectedVersion) { return new(StoreResultCode.Conflict, Snapshot(entry)); } entry.LeaseDeadline = now + _options.LeaseLifetime; entry.WallExpiresAt = WallDeadline(now, _options.LeaseLifetime); entry.Version++; return new(StoreResultCode.Success, Snapshot(entry)); }, cancellationToken); public StoreResult UpdateListing( UpdateListingCommand command, CancellationToken cancellationToken = default) => Atomic(_ => { ArgumentNullException.ThrowIfNull(command); ValidateSubject(command.OwnerSubject, nameof(command.OwnerSubject)); if (!ContractValidation.IsBuildVersionValid(command.BuildVersion) || !ContractValidation.IsDisplayNameValid(command.DisplayName) || command.MaximumPlayers is <= 0 or > ContractLimits.SessionCapacityMaxPlayers || command.CurrentPlayers < 0 || command.CurrentPlayers > command.MaximumPlayers || !ContractValidation.IsMetadataValid(command.Metadata) || command.DedicatedFallback is not null && !ContractValidation.IsNetworkEndpointValid(command.DedicatedFallback)) { throw new ArgumentException("Listing update invariants are invalid.", nameof(command)); } if (!_available) { return new(StoreResultCode.ServiceUnavailable); } if (_drainDeadline.HasValue) { return new(StoreResultCode.Draining); } if (!_listings.TryGetValue(command.ListingId, out ListingEntry? entry) || entry.Definition.LeaseId != command.LeaseId || entry.Definition.LeaseFingerprint != command.LeaseFingerprint || !string.Equals(entry.Definition.OwnerSubject, command.OwnerSubject, StringComparison.Ordinal)) { return new(StoreResultCode.NotFound); } entry.Definition = StoredListing.Freeze(entry.Definition with { BuildVersion = command.BuildVersion, DisplayName = command.DisplayName, CurrentPlayers = command.CurrentPlayers, MaximumPlayers = command.MaximumPlayers, Metadata = command.Metadata, DedicatedFallback = command.DedicatedFallback, }); entry.Version++; return new(StoreResultCode.Success, Snapshot(entry)); }, cancellationToken); public StoreResult DeleteListing( DeleteListingCommand command, CancellationToken cancellationToken = default) => Atomic(_ => { ArgumentNullException.ThrowIfNull(command); if (!_listings.TryGetValue(command.ListingId, out ListingEntry? entry) || entry.Definition.LeaseId != command.LeaseId || entry.Definition.LeaseFingerprint != command.LeaseFingerprint || !string.Equals(entry.Definition.OwnerSubject, command.OwnerSubject, StringComparison.Ordinal)) { return new(StoreResultCode.NotFound); } RemoveListing(command.ListingId); return new(StoreResultCode.Success, true); }, cancellationToken); public StoreResult GetListing( SessionListingId listingId, bool requireFreshPresence, CancellationToken cancellationToken = default) => Atomic(_ => { if (!_available) { return new(StoreResultCode.ServiceUnavailable); } if (!_listings.TryGetValue(listingId, out ListingEntry? entry)) { return new(StoreResultCode.NotFound); } bool fresh = _presence.ContainsKey(entry.Definition.HostPresenceHandle); return requireFreshPresence && !fresh ? new(StoreResultCode.NotFound) : new(StoreResultCode.Success, Snapshot(entry)); }, cancellationToken); public StoreResult BindHostPresence( BindHostPresenceCommand command, CancellationToken cancellationToken = default) => Atomic(now => { ArgumentNullException.ThrowIfNull(command); if (command.Handle.Value == Guid.Empty || !command.CapabilityFingerprint.IsValid) { throw new ArgumentException("Host presence binding is invalid.", nameof(command)); } ValidateEndpoint(command.PublicEndpoint, command.LocalEndpoint); if (!_available) { return new(StoreResultCode.ServiceUnavailable); } if (!_presenceHandles.TryGetValue(command.Handle, out SessionListingId listingId) || !_listings.TryGetValue(listingId, out ListingEntry? entry) || entry.LeaseDeadline <= now || entry.Definition.HostPresenceFingerprint != command.CapabilityFingerprint) { return new(StoreResultCode.NotFound); } if (!_presence.ContainsKey(command.Handle) && _presence.Count >= _options.MaxPresenceBindings) { return new(StoreResultCode.CapacityExceeded); } _presence[command.Handle] = new( command.PublicEndpoint, command.LocalEndpoint, now + _options.PresenceLifetime); return new(StoreResultCode.Success, Snapshot(entry)); }, cancellationToken, eagerCleanup: false); public StoreResult> BrowseVisibleListings( VisibleListingQuery query, CancellationToken cancellationToken = default) => Atomic>(_ => { ArgumentNullException.ThrowIfNull(query); if (!_available) { return new(StoreResultCode.ServiceUnavailable); } if (!IsScopeValid(query.Scope) || query.ProtocolVersion == 0 || (query.RegionId.HasValue && string.IsNullOrEmpty(query.RegionId.Value.Value)) || query.MaximumResults <= 0 || query.MaximumResults > ContractLimits.BrowserPageMaxItems + 1) { throw new ArgumentOutOfRangeException(nameof(query)); } IReadOnlyList visible = _listings.Values .Where(entry => entry.Definition.Scope == query.Scope && entry.Definition.ProtocolVersion == query.ProtocolVersion && entry.Definition.Visibility == ListingVisibility.Public && (!query.RegionId.HasValue || entry.Definition.RegionId == query.RegionId.Value) && (!query.AfterListingId.HasValue || entry.Definition.ListingId.Value.CompareTo(query.AfterListingId.Value.Value) > 0) && (!query.ExcludeFull || entry.Definition.CurrentPlayers < entry.Definition.MaximumPlayers) && _presence.ContainsKey(entry.Definition.HostPresenceHandle)) .OrderBy(static entry => entry.Definition.ListingId.Value) .Take(query.MaximumResults) .Select(Snapshot) .ToArray(); return new(StoreResultCode.Success, visible); }, cancellationToken); public StoreResult CreateJoinAttempt( CreateJoinAttemptCommand command, CancellationToken cancellationToken = default) => Atomic(now => { ArgumentNullException.ThrowIfNull(command); ValidateAttempt(command); ValidateIdempotency(command.IdempotencyKey, command.RequestFingerprint); ValidateSubject(command.IdempotencyOwner, nameof(command.IdempotencyOwner)); ValidateSubject(command.ClientSubject, nameof(command.ClientSubject)); StoreResult? admission = CheckNewWorkAdmission(command.ClientSubject); if (admission is not null) { return admission; } string idempotencyKey = $"attempt:{command.Scope.GameId}:{command.Scope.EnvironmentId}:{command.IdempotencyOwner}:{command.IdempotencyKey}"; if (_idempotency.TryGetValue(idempotencyKey, out IdempotencyEntry? previous)) { if (!string.Equals(previous.RequestFingerprint, command.RequestFingerprint, StringComparison.Ordinal)) { return new(StoreResultCode.Conflict); } if (previous.ResourceId is JoinAttemptId attemptId && _attempts.TryGetValue(attemptId, out AttemptEntry? priorAttempt)) { return new(StoreResultCode.Success, Snapshot(priorAttempt), true); } return new(StoreResultCode.Expired); } if (!_listings.TryGetValue(command.ListingId, out ListingEntry? listing) || listing.Definition.Scope != command.Scope) { return new(StoreResultCode.NotFound); } if (listing.Definition.ProtocolVersion != command.ProtocolVersion) { return new(StoreResultCode.IncompatibleProtocol); } if (!_presence.ContainsKey(listing.Definition.HostPresenceHandle)) { return new(StoreResultCode.StaleHost); } command = command with { DedicatedFallback = StoredListing.CopyEndpoint(listing.Definition.DedicatedFallback), }; if (_attempts.Count >= _options.MaxJoinAttempts || _outcomeReports.Count >= _options.MaxOutcomeReports || _idempotency.Count >= _options.MaxIdempotencyEntries || _attempts.Values.Count(entry => entry.Command.Scope == command.Scope) >= command.ScopeAttemptLimit) { return new(StoreResultCode.CapacityExceeded); } if (_attempts.ContainsKey(command.AttemptId) || HandleExists(command.MediationHandle)) { return new(StoreResultCode.Conflict); } AttemptEntry attempt = new( command, now + _options.JoinAttemptLifetime, WallDeadline(now, _options.JoinAttemptLifetime)); _attempts.Add(command.AttemptId, attempt); _outcomeReports.Add(command.AttemptId, new( command.ListingId, command.ClientSubject, command.ClientCapabilityFingerprint, now + _options.JoinAttemptLifetime + _options.IdempotencyLifetime)); _attemptHandles.Add(command.MediationHandle, command.AttemptId); _idempotency.Add(idempotencyKey, new( command.RequestFingerprint, command.AttemptId, now + _options.IdempotencyLifetime)); return new(StoreResultCode.Success, Snapshot(attempt)); }, cancellationToken); public StoreResult> BrowseHostJoinAttempts( HostJoinAttemptQuery query, CancellationToken cancellationToken = default) => Atomic>(_ => { ArgumentNullException.ThrowIfNull(query); if (query.ListingId.Value == Guid.Empty || !query.LeaseFingerprint.IsValid || query.MaximumResults is < 1 or > ContractLimits.BrowserPageMaxItems + 1) { throw new ArgumentException("Host attempt query invariants are invalid.", nameof(query)); } if (!_available) { return new(StoreResultCode.ServiceUnavailable); } if (!_listings.TryGetValue(query.ListingId, out ListingEntry? listing) || listing.Definition.LeaseFingerprint != query.LeaseFingerprint) { return new(StoreResultCode.NotFound); } IReadOnlyList attempts = _attempts.Values .Where(entry => entry.Command.ListingId == query.ListingId && (!entry.IntroductionConsumed || entry.IsCancelled) && (!query.AfterAttemptId.HasValue || entry.Command.AttemptId.Value.CompareTo(query.AfterAttemptId.Value.Value) > 0)) .OrderBy(static entry => entry.Command.AttemptId.Value) .Take(query.MaximumResults) .Select(Snapshot) .ToArray(); return new(StoreResultCode.Success, attempts); }, cancellationToken); public StoreResult CancelJoinAttempt( CancelJoinAttemptCommand command, CancellationToken cancellationToken = default) => Atomic(_ => { ArgumentNullException.ThrowIfNull(command); if (command.AttemptId.Value == Guid.Empty || !command.ClientCapabilityFingerprint.IsValid) { throw new ArgumentException("Join cancellation invariants are invalid.", nameof(command)); } if (!_available) { return new(StoreResultCode.ServiceUnavailable); } if (!_attempts.TryGetValue(command.AttemptId, out AttemptEntry? attempt) || attempt.ClientCapabilityFingerprint != command.ClientCapabilityFingerprint) { return new(StoreResultCode.NotFound); } if (attempt.IsCancelled) { return new(StoreResultCode.Success, true, true); } attempt.IsCancelled = true; return new(StoreResultCode.Success, true); }, cancellationToken); public StoreResult ReportConnectionOutcome( ReportConnectionOutcomeCommand command, CancellationToken cancellationToken = default) => Atomic(_ => { ArgumentNullException.ThrowIfNull(command); if (command.AttemptId.Value == Guid.Empty || !command.ClientCapabilityFingerprint.IsValid || !ContractValidation.IsReportableConnectionOutcome(command.Outcome) || !Enum.IsDefined(command.ElapsedBucket)) { throw new ArgumentException("Connection outcome invariants are invalid.", nameof(command)); } if (!_available) { return new(StoreResultCode.ServiceUnavailable); } if (!_outcomeReports.TryGetValue(command.AttemptId, out OutcomeReportEntry? entry) || entry.ClientCapabilityFingerprint != command.ClientCapabilityFingerprint) { return new(StoreResultCode.NotFound); } StoredConnectionOutcome reported = new(command.Outcome, command.ElapsedBucket); if (entry.Outcome is not null) { return entry.Outcome == reported ? new(StoreResultCode.Success, entry.Outcome, true) : new(StoreResultCode.ReplayRejected); } entry.Outcome = reported; return new(StoreResultCode.Success, reported); }, cancellationToken); public StoreResult BindAttemptEndpoint( BindAttemptEndpointCommand command, CancellationToken cancellationToken = default) => Atomic(now => { ArgumentNullException.ThrowIfNull(command); if (command.Handle.Value == Guid.Empty || command.Role is not (AttemptPeerRole.Host or AttemptPeerRole.Client) || !command.CapabilityFingerprint.IsValid) { throw new ArgumentException("Attempt endpoint binding is invalid.", nameof(command)); } ValidateEndpoint(command.PublicEndpoint, command.LocalEndpoint); if (!_available) { return new(StoreResultCode.ServiceUnavailable); } if (!_attemptHandles.TryGetValue(command.Handle, out JoinAttemptId attemptId) || !_attempts.TryGetValue(attemptId, out AttemptEntry? attempt) || attempt.Deadline <= now || attempt.IsCancelled) { return new(StoreResultCode.NotFound); } if (!HasFreshHostPresence(attempt, now)) { return new(StoreResultCode.NotFound); } SecretFingerprint expected = command.Role == AttemptPeerRole.Host ? attempt.HostCapabilityFingerprint : attempt.ClientCapabilityFingerprint; if (expected != command.CapabilityFingerprint) { return new(StoreResultCode.NotFound); } AttemptEndpointBinding binding = new(command.PublicEndpoint, command.LocalEndpoint); AttemptEndpointBinding? current = command.Role == AttemptPeerRole.Host ? attempt.HostEndpoint : attempt.ClientEndpoint; if (current is not null) { return current == binding ? new(StoreResultCode.Success, Snapshot(attempt), true) : new(StoreResultCode.ReplayRejected); } if (command.Role == AttemptPeerRole.Host) { attempt.HostEndpoint = binding; } else { attempt.ClientEndpoint = binding; } return new(StoreResultCode.Success, Snapshot(attempt)); }, cancellationToken, eagerCleanup: false); public StoreResult ConsumeIntroduction( MediationHandle handle, CancellationToken cancellationToken = default) => Atomic(now => { if (!_available) { return new(StoreResultCode.ServiceUnavailable); } if (!_attemptHandles.TryGetValue(handle, out JoinAttemptId attemptId) || !_attempts.TryGetValue(attemptId, out AttemptEntry? attempt) || attempt.Deadline <= now || attempt.IsCancelled) { return new(StoreResultCode.NotFound); } if (!HasFreshHostPresence(attempt, now)) { return new(StoreResultCode.NotFound); } if (attempt.IntroductionConsumed) { return new(StoreResultCode.ReplayRejected); } if (attempt.HostEndpoint is null || attempt.ClientEndpoint is null) { return new(StoreResultCode.Conflict); } attempt.IntroductionConsumed = true; TimeSpan ticketLifetime = TimeSpan.FromTicks(Math.Min( _options.ConnectionTicketLifetime.Ticks, (attempt.Deadline - now).Ticks)); attempt.TicketDeadline = now + ticketLifetime; attempt.TicketWallExpiresAt = WallDeadline(now, ticketLifetime); return new(StoreResultCode.Success, new( Snapshot(attempt), attempt.HostEndpoint, attempt.ClientEndpoint)); }, cancellationToken, eagerCleanup: false); public StoreResult ConsumeConnectionTicket( ConsumeConnectionTicketCommand command, CancellationToken cancellationToken = default) => Atomic(now => { ArgumentNullException.ThrowIfNull(command); if (command.AttemptId.Value == Guid.Empty || !command.ConnectionTicketFingerprint.IsValid) { throw new ArgumentException("Connection ticket invariants are invalid.", nameof(command)); } if (!_available) { return new(StoreResultCode.ServiceUnavailable); } if (!_attempts.TryGetValue(command.AttemptId, out AttemptEntry? attempt) || attempt.ConnectionTicketFingerprint != command.ConnectionTicketFingerprint) { return new(StoreResultCode.NotFound); } if (!attempt.IntroductionConsumed) { return new(StoreResultCode.Conflict); } if (attempt.IsCancelled) { return new(StoreResultCode.Conflict); } if (!attempt.TicketDeadline.HasValue || attempt.TicketDeadline.Value <= now) { return new(StoreResultCode.Expired); } if (attempt.ConnectionTicketConsumed) { return new(StoreResultCode.ReplayRejected); } attempt.ConnectionTicketConsumed = true; return new(StoreResultCode.Success, true); }, cancellationToken); public StoreResult ConsumeReplay( ReplayConsumption consumption, CancellationToken cancellationToken = default) => Atomic(now => { ArgumentNullException.ThrowIfNull(consumption); if (!_available) { return new(StoreResultCode.ServiceUnavailable); } ValidateReplay(consumption); string key = $"{consumption.Namespace}:{consumption.Key}"; if (_replay.ContainsKey(key)) { return new(StoreResultCode.ReplayRejected); } if (_replay.Count >= _options.MaxReplayEntries) { return new(StoreResultCode.CapacityExceeded); } TimeSpan lifetime = consumption.Lifetime ?? _options.ReplayLifetime; if (lifetime <= TimeSpan.Zero || lifetime > _options.ReplayLifetime) { throw new ArgumentOutOfRangeException(nameof(consumption), "Replay lifetime exceeds the configured ceiling."); } _replay.Add(key, now + lifetime); return new(StoreResultCode.Success, true); }, cancellationToken); public StoreResult RevokeListing( SessionListingId listingId, CancellationToken cancellationToken = default) => Atomic(_ => { if (!_listings.ContainsKey(listingId)) { return new(StoreResultCode.NotFound); } RemoveListing(listingId); return new(StoreResultCode.Success, true); }, cancellationToken); public StoreResult RevokePrincipal( string subject, TimeSpan lifetime, CancellationToken cancellationToken = default) => Atomic(now => { ValidateSubject(subject, nameof(subject)); if (lifetime <= TimeSpan.Zero || lifetime > TimeSpan.FromMinutes(10)) { throw new ArgumentOutOfRangeException(nameof(lifetime)); } if (!_revocations.ContainsKey(subject) && _revocations.Count >= _options.MaxRevocations) { return new(StoreResultCode.CapacityExceeded); } _revocations[subject] = now + lifetime; SessionListingId[] listings = _listings .Where(item => string.Equals(item.Value.Definition.OwnerSubject, subject, StringComparison.Ordinal)) .Select(static item => item.Key) .ToArray(); JoinAttemptId[] attempts = _attempts .Where(item => string.Equals(item.Value.Command.ClientSubject, subject, StringComparison.Ordinal)) .Select(static item => item.Key) .ToArray(); JoinAttemptId[] outcomeReports = _outcomeReports .Where(item => string.Equals(item.Value.ClientSubject, subject, StringComparison.Ordinal)) .Select(static item => item.Key) .ToArray(); foreach (SessionListingId listingId in listings) { RemoveListing(listingId); } foreach (JoinAttemptId attemptId in attempts) { RemoveAttempt(attemptId); } foreach (JoinAttemptId attemptId in outcomeReports) { _outcomeReports.Remove(attemptId); } return new(StoreResultCode.Success, listings.Length + attempts.Length); }, cancellationToken); public void BeginDrain(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); lock (_gate) { cancellationToken.ThrowIfCancellationRequested(); if (!_drainDeadline.HasValue) { _drainDeadline = _monotonicClock.Elapsed + _options.GracefulDrainLifetime; } } } internal void MarkUnavailable() { lock (_gate) { _available = false; ClearActiveState(); } } private StoreResult Atomic( Func> operation, CancellationToken cancellationToken, bool eagerCleanup = true) { cancellationToken.ThrowIfCancellationRequested(); lock (_gate) { cancellationToken.ThrowIfCancellationRequested(); TimeSpan now = _monotonicClock.Elapsed; // Authenticated UDP duplicates need O(1) store work. Their operations // check exact resource deadlines and amortize physical expiry removal. bool drainExpired = _drainDeadline is TimeSpan drainDeadline && now >= drainDeadline; if (drainExpired || eagerCleanup || now >= _nextUdpMaintenance) { Cleanup(now); _nextUdpMaintenance = now + UdpMaintenanceInterval; } return operation(now); } } private bool HasFreshHostPresence(AttemptEntry attempt, TimeSpan now) => _listings.TryGetValue(attempt.Command.ListingId, out ListingEntry? listing) && listing.LeaseDeadline > now && _presence.TryGetValue( listing.Definition.HostPresenceHandle, out PresenceEntry? presence) && presence.Deadline > now; private StoreResult? CheckNewWorkAdmission(string subject) { if (!_available) { return new(StoreResultCode.ServiceUnavailable); } if (_drainDeadline.HasValue) { return new(StoreResultCode.Draining); } return _revocations.ContainsKey(subject) ? new(StoreResultCode.Revoked) : null; } private void Cleanup(TimeSpan now) { _maintenanceSweepCount++; if (_drainDeadline is TimeSpan drainDeadline && now >= drainDeadline) { ClearActiveState(); } RemoveExpired(_revocations, now); RemoveExpired(_replay, now); foreach (string key in _idempotency .Where(item => item.Value.Deadline <= now) .Select(static item => item.Key) .ToArray()) { _idempotency.Remove(key); } foreach (MediationHandle handle in _presence .Where(item => item.Value.Deadline <= now) .Select(static item => item.Key) .ToArray()) { _presence.Remove(handle); } foreach (JoinAttemptId attemptId in _attempts .Where(item => item.Value.Deadline <= now) .Select(static item => item.Key) .ToArray()) { RemoveAttempt(attemptId); } foreach (JoinAttemptId attemptId in _outcomeReports .Where(item => item.Value.Deadline <= now) .Select(static item => item.Key) .ToArray()) { _outcomeReports.Remove(attemptId); } foreach (SessionListingId listingId in _listings .Where(item => item.Value.LeaseDeadline <= now) .Select(static item => item.Key) .ToArray()) { RemoveListing(listingId); } } private void ClearActiveState() { _listings.Clear(); _leases.Clear(); _presenceHandles.Clear(); _presence.Clear(); _attempts.Clear(); _outcomeReports.Clear(); _attemptHandles.Clear(); _idempotency.Clear(); _replay.Clear(); } private void RemoveListing(SessionListingId listingId) { if (!_listings.Remove(listingId, out ListingEntry? listing)) { return; } _leases.Remove(listing.Definition.LeaseId); _presenceHandles.Remove(listing.Definition.HostPresenceHandle); _presence.Remove(listing.Definition.HostPresenceHandle); foreach (JoinAttemptId attemptId in _attempts .Where(item => item.Value.Command.ListingId == listingId) .Select(static item => item.Key) .ToArray()) { RemoveAttempt(attemptId); } foreach (JoinAttemptId attemptId in _outcomeReports .Where(item => item.Value.ListingId == listingId) .Select(static item => item.Key) .ToArray()) { _outcomeReports.Remove(attemptId); } } private void RemoveAttempt(JoinAttemptId attemptId) { if (_attempts.Remove(attemptId, out AttemptEntry? attempt)) { _attemptHandles.Remove(attempt.Command.MediationHandle); } } private bool HandleExists(MediationHandle handle) => _presenceHandles.ContainsKey(handle) || _attemptHandles.ContainsKey(handle); private DateTimeOffset WallDeadline(TimeSpan now, TimeSpan lifetime) => _wallOrigin + (now - _monotonicOrigin) + lifetime; private StoredListing Snapshot(ListingEntry entry) => new() { Definition = entry.Definition, LeaseExpiresAt = entry.WallExpiresAt, Version = entry.Version, HasFreshPresence = _presence.ContainsKey(entry.Definition.HostPresenceHandle), }; private static StoredJoinAttempt Snapshot(AttemptEntry entry) => new() { AttemptId = entry.Command.AttemptId, MediationHandle = entry.Command.MediationHandle, Scope = entry.Command.Scope, ListingId = entry.Command.ListingId, ClientSubject = entry.Command.ClientSubject, ProtocolVersion = entry.Command.ProtocolVersion, IdempotencyKey = entry.Command.IdempotencyKey, RequestFingerprint = entry.Command.RequestFingerprint, CapabilityDerivationSalt = entry.Command.CapabilityDerivationSalt, HostCapabilityFingerprint = entry.Command.HostCapabilityFingerprint, ClientCapabilityFingerprint = entry.Command.ClientCapabilityFingerprint, ConnectionTicketFingerprint = entry.Command.ConnectionTicketFingerprint, DedicatedFallback = StoredListing.CopyEndpoint(entry.Command.DedicatedFallback), ExpiresAt = entry.WallExpiresAt, ConnectionTicketExpiresAt = entry.TicketWallExpiresAt ?? default, HostEndpoint = entry.HostEndpoint, ClientEndpoint = entry.ClientEndpoint, IntroductionConsumed = entry.IntroductionConsumed, ConnectionTicketConsumed = entry.ConnectionTicketConsumed, IsCancelled = entry.IsCancelled, }; private static void RemoveExpired(Dictionary entries, TimeSpan now) { foreach (string key in entries .Where(item => item.Value <= now) .Select(static item => item.Key) .ToArray()) { entries.Remove(key); } } private static void ValidateListing(ListingDefinition listing) { ArgumentNullException.ThrowIfNull(listing); ValidateSubject(listing.OwnerSubject, nameof(listing.OwnerSubject)); ArgumentNullException.ThrowIfNull(listing.Metadata); if (listing.ListingId.Value == Guid.Empty || listing.LeaseId.Value == Guid.Empty || listing.HostPresenceHandle.Value == Guid.Empty || !IsScopeValid(listing.Scope) || string.IsNullOrEmpty(listing.RegionId.Value) || listing.ProtocolVersion == 0 || !ContractValidation.IsBuildVersionValid(listing.BuildVersion) || !ContractValidation.IsDisplayNameValid(listing.DisplayName) || !Enum.IsDefined(listing.Visibility) || !Enum.IsDefined(listing.TrustMode) || listing.MaximumPlayers is <= 0 or > ContractLimits.SessionCapacityMaxPlayers || listing.CurrentPlayers < 0 || listing.CurrentPlayers > listing.MaximumPlayers || !ContractValidation.IsMetadataValid(listing.Metadata) || listing.DedicatedFallback is not null && !ContractValidation.IsNetworkEndpointValid(listing.DedicatedFallback) || !listing.LeaseFingerprint.IsValid || !listing.HostPresenceFingerprint.IsValid || !IsDerivationSaltValid(listing.CapabilityDerivationSalt)) { throw new ArgumentException("Listing invariants are invalid.", nameof(listing)); } } private static void ValidateIdempotency(string key, string requestFingerprint) { if (string.IsNullOrWhiteSpace(key) || key.Length > 128) { throw new ArgumentException("Idempotency keys must contain 1-128 characters.", nameof(key)); } if (string.IsNullOrWhiteSpace(requestFingerprint) || requestFingerprint.Length > 128) { throw new ArgumentException("Request fingerprints must contain 1-128 characters.", nameof(requestFingerprint)); } } private static void ValidateReplay(ReplayConsumption consumption) { if (string.IsNullOrWhiteSpace(consumption.Namespace) || consumption.Namespace.Length > 64 || string.IsNullOrWhiteSpace(consumption.Key) || consumption.Key.Length > 128) { throw new ArgumentException("Replay namespace/key is invalid.", nameof(consumption)); } } private static void ValidateAttempt(CreateJoinAttemptCommand command) { if (command.AttemptId.Value == Guid.Empty || command.MediationHandle.Value == Guid.Empty || command.ListingId.Value == Guid.Empty || !IsScopeValid(command.Scope) || command.ProtocolVersion == 0 || !command.HostCapabilityFingerprint.IsValid || !command.ClientCapabilityFingerprint.IsValid || !command.ConnectionTicketFingerprint.IsValid || command.DedicatedFallback is not null && !ContractValidation.IsNetworkEndpointValid(command.DedicatedFallback) || !IsDerivationSaltValid(command.CapabilityDerivationSalt) || command.ScopeAttemptLimit <= 0) { throw new ArgumentException("Join attempt invariants are invalid.", nameof(command)); } } private static void ValidateEndpoint(ObservedEndpoint publicEndpoint, ObservedEndpoint? localEndpoint) { if (!publicEndpoint.IsValid || (localEndpoint.HasValue && !localEndpoint.Value.IsValid)) { throw new ArgumentException("Observed endpoints must be valid immutable endpoint values.", nameof(publicEndpoint)); } } private static bool IsScopeValid(TenantScope scope) => !string.IsNullOrEmpty(scope.GameId.Value) && !string.IsNullOrEmpty(scope.EnvironmentId.Value); private static bool IsDerivationSaltValid(string? value) => value is not null && value.Length == 43 && value.All(static character => character is >= 'A' and <= 'Z' or >= 'a' and <= 'z' or >= '0' and <= '9' or '-' or '_'); private static void ValidateSubject(string subject, string parameterName) { if (string.IsNullOrWhiteSpace(subject) || subject.Length > 256) { throw new ArgumentException("Subjects must contain 1-256 characters.", parameterName); } } private sealed class ListingEntry( ListingDefinition definition, TimeSpan leaseDeadline, DateTimeOffset wallExpiresAt, long version) { public ListingDefinition Definition { get; set; } = definition; public TimeSpan LeaseDeadline { get; set; } = leaseDeadline; public DateTimeOffset WallExpiresAt { get; set; } = wallExpiresAt; public long Version { get; set; } = version; } private sealed class PresenceEntry( ObservedEndpoint publicEndpoint, ObservedEndpoint? localEndpoint, TimeSpan deadline) { public ObservedEndpoint PublicEndpoint { get; } = publicEndpoint; public ObservedEndpoint? LocalEndpoint { get; } = localEndpoint; public TimeSpan Deadline { get; } = deadline; } private sealed class AttemptEntry( CreateJoinAttemptCommand command, TimeSpan deadline, DateTimeOffset wallExpiresAt) { public CreateJoinAttemptCommand Command { get; } = command; public SecretFingerprint HostCapabilityFingerprint { get; } = command.HostCapabilityFingerprint; public SecretFingerprint ClientCapabilityFingerprint { get; } = command.ClientCapabilityFingerprint; public SecretFingerprint ConnectionTicketFingerprint { get; } = command.ConnectionTicketFingerprint; public TimeSpan Deadline { get; } = deadline; public DateTimeOffset WallExpiresAt { get; } = wallExpiresAt; public TimeSpan? TicketDeadline { get; set; } public DateTimeOffset? TicketWallExpiresAt { get; set; } public AttemptEndpointBinding? HostEndpoint { get; set; } public AttemptEndpointBinding? ClientEndpoint { get; set; } public bool IntroductionConsumed { get; set; } public bool ConnectionTicketConsumed { get; set; } public bool IsCancelled { get; set; } } private sealed class OutcomeReportEntry( SessionListingId listingId, string clientSubject, SecretFingerprint clientCapabilityFingerprint, TimeSpan deadline) { public SessionListingId ListingId { get; } = listingId; public string ClientSubject { get; } = clientSubject; public SecretFingerprint ClientCapabilityFingerprint { get; } = clientCapabilityFingerprint; public TimeSpan Deadline { get; } = deadline; public StoredConnectionOutcome? Outcome { get; set; } } private sealed record IdempotencyEntry( string RequestFingerprint, object ResourceId, TimeSpan Deadline); }