159 lines
5.7 KiB
C#
159 lines
5.7 KiB
C#
using System.Text.Json;
|
|
using FinalFactory.Rendezvous.Contracts;
|
|
using FinalFactory.Rendezvous.Server.State;
|
|
|
|
namespace FinalFactory.Rendezvous.Server.Browser;
|
|
|
|
internal sealed record BrowserServiceResult<T>(RendezvousErrorCode Error, T? Value = default)
|
|
{
|
|
public bool Succeeded => Error == RendezvousErrorCode.None;
|
|
}
|
|
|
|
internal sealed class SessionBrowserService(
|
|
IEphemeralRendezvousStore store,
|
|
SessionBrowserCursorCodec cursors,
|
|
IWallClock clock)
|
|
{
|
|
public BrowserServiceResult<BrowseSessionsResponse> Browse(
|
|
BrowseSessionsRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
RendezvousErrorCode validation = Validate(request);
|
|
if (validation != RendezvousErrorCode.None)
|
|
{
|
|
return new(validation);
|
|
}
|
|
|
|
TenantScope scope = new(request.GameId, request.EnvironmentId);
|
|
if (!cursors.TryDecode(
|
|
request.Cursor,
|
|
scope,
|
|
request.ProtocolVersion,
|
|
request.RegionId,
|
|
request.ExcludeFull,
|
|
clock.UtcNow,
|
|
out SessionListingId? after))
|
|
{
|
|
return new(RendezvousErrorCode.InvalidRequest);
|
|
}
|
|
|
|
VisibleListingQuery query = new(
|
|
scope,
|
|
request.ProtocolVersion,
|
|
request.RegionId,
|
|
request.PageSize + 1,
|
|
after,
|
|
request.ExcludeFull);
|
|
StoreResult<IReadOnlyList<StoredListing>> found = store.BrowseVisibleListings(
|
|
query,
|
|
cancellationToken);
|
|
if (!found.Succeeded || found.Value is null)
|
|
{
|
|
return new(found.Code == StoreResultCode.ServiceUnavailable
|
|
? RendezvousErrorCode.ServiceUnavailable
|
|
: RendezvousErrorCode.InternalError);
|
|
}
|
|
|
|
List<SessionListing> items = found.Value
|
|
.Take(request.PageSize)
|
|
.Select(ToContract)
|
|
.ToList();
|
|
bool hasMore = found.Value.Count > request.PageSize;
|
|
while (items.Count > 0)
|
|
{
|
|
string? nextCursor = hasMore
|
|
? cursors.Encode(query, items[^1].ListingId, clock.UtcNow)
|
|
: null;
|
|
BrowseSessionsResponse response = new() { Items = items, NextCursor = nextCursor };
|
|
if (JsonSerializer.SerializeToUtf8Bytes(response, ContractJson.Options).Length
|
|
<= ContractLimits.BrowserResponseMaxBytes)
|
|
{
|
|
return new(RendezvousErrorCode.None, response);
|
|
}
|
|
|
|
items.RemoveAt(items.Count - 1);
|
|
hasMore = true;
|
|
}
|
|
|
|
return new(RendezvousErrorCode.None, new BrowseSessionsResponse());
|
|
}
|
|
|
|
public BrowserServiceResult<GetSessionResponse> Get(
|
|
SessionListingId listingId,
|
|
GameId gameId,
|
|
EnvironmentId environmentId,
|
|
uint protocolVersion,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (listingId.Value == Guid.Empty
|
|
|| string.IsNullOrEmpty(gameId.Value)
|
|
|| string.IsNullOrEmpty(environmentId.Value)
|
|
|| protocolVersion == 0)
|
|
{
|
|
return new(RendezvousErrorCode.InvalidRequest);
|
|
}
|
|
|
|
StoreResult<StoredListing> found = store.GetListing(listingId, true, cancellationToken);
|
|
if (!found.Succeeded || found.Value is null)
|
|
{
|
|
return new(found.Code == StoreResultCode.ServiceUnavailable
|
|
? RendezvousErrorCode.ServiceUnavailable
|
|
: RendezvousErrorCode.NotFound);
|
|
}
|
|
|
|
StoredListing listing = found.Value;
|
|
if (listing.Definition.Scope != new TenantScope(gameId, environmentId)
|
|
|| listing.Definition.ProtocolVersion != protocolVersion)
|
|
{
|
|
return new(RendezvousErrorCode.NotFound);
|
|
}
|
|
|
|
return new(RendezvousErrorCode.None, new GetSessionResponse
|
|
{
|
|
Session = ToContract(listing),
|
|
});
|
|
}
|
|
|
|
private static RendezvousErrorCode Validate(BrowseSessionsRequest request)
|
|
{
|
|
RendezvousErrorCode version = ContractValidation.ValidateContractVersion(request.ContractVersion);
|
|
if (version != RendezvousErrorCode.None)
|
|
{
|
|
return version;
|
|
}
|
|
|
|
return string.IsNullOrEmpty(request.GameId.Value)
|
|
|| string.IsNullOrEmpty(request.EnvironmentId.Value)
|
|
|| request.ProtocolVersion == 0
|
|
|| (request.RegionId.HasValue && string.IsNullOrEmpty(request.RegionId.Value.Value))
|
|
|| !ContractValidation.IsPageSizeValid(request.PageSize)
|
|
|| !ContractValidation.IsCursorValid(request.Cursor)
|
|
? RendezvousErrorCode.InvalidRequest
|
|
: RendezvousErrorCode.None;
|
|
}
|
|
|
|
private static SessionListing ToContract(StoredListing stored) => new()
|
|
{
|
|
ListingId = stored.Definition.ListingId,
|
|
GameId = stored.Definition.Scope.GameId,
|
|
EnvironmentId = stored.Definition.Scope.EnvironmentId,
|
|
RegionId = stored.Definition.RegionId,
|
|
ProtocolVersion = stored.Definition.ProtocolVersion,
|
|
BuildVersion = stored.Definition.BuildVersion,
|
|
DisplayName = stored.Definition.DisplayName,
|
|
Visibility = stored.Definition.Visibility,
|
|
PublisherTrustMode = stored.Definition.TrustMode,
|
|
Capacity = new()
|
|
{
|
|
CurrentPlayers = stored.Definition.CurrentPlayers,
|
|
MaximumPlayers = stored.Definition.MaximumPlayers,
|
|
},
|
|
Metadata = stored.Definition.Metadata.ToDictionary(
|
|
static item => item.Key,
|
|
static item => item.Value,
|
|
StringComparer.Ordinal),
|
|
DedicatedFallback = StoredListing.CopyEndpoint(stored.Definition.DedicatedFallback),
|
|
};
|
|
}
|