119 lines
5.2 KiB
C#
119 lines
5.2 KiB
C#
using FinalFactory.Rendezvous.Contracts;
|
|
|
|
namespace FinalFactory.Rendezvous.Server.Provisioning;
|
|
|
|
internal sealed class GamePolicyRegistry
|
|
{
|
|
private readonly Dictionary<(GameId Game, EnvironmentId Environment), GamePolicy> _policies;
|
|
|
|
private GamePolicyRegistry(
|
|
Dictionary<(GameId Game, EnvironmentId Environment), GamePolicy> policies) =>
|
|
_policies = policies;
|
|
|
|
public bool HasEnabledPolicies => _policies.Values.Any(static policy => policy.Enabled);
|
|
public IEnumerable<GamePolicy> EnabledPolicies =>
|
|
_policies.Values.Where(static policy => policy.Enabled);
|
|
|
|
public static GamePolicyRegistry Create(IEnumerable<GamePolicyOptions> options)
|
|
{
|
|
GamePolicyOptions[] configuredPolicies = options.ToArray();
|
|
if (configuredPolicies.Length > ProvisioningLimits.MaxGamePolicies)
|
|
{
|
|
throw new ProvisioningConfigurationException(
|
|
$"At most {ProvisioningLimits.MaxGamePolicies} game policies may be configured.");
|
|
}
|
|
|
|
Dictionary<(GameId Game, EnvironmentId Environment), GamePolicy> policies = [];
|
|
foreach (GamePolicyOptions policyOptions in configuredPolicies)
|
|
{
|
|
Validate(policyOptions);
|
|
GamePolicy policy = new(policyOptions);
|
|
if (!policies.TryAdd((policy.GameId, policy.EnvironmentId), policy))
|
|
{
|
|
throw new ProvisioningConfigurationException(
|
|
$"Duplicate game/environment policy: {policy.GameId}/{policy.EnvironmentId}.");
|
|
}
|
|
}
|
|
|
|
return new GamePolicyRegistry(policies);
|
|
}
|
|
|
|
public bool TryGet(
|
|
GameId gameId,
|
|
EnvironmentId environmentId,
|
|
out GamePolicy? policy)
|
|
{
|
|
if (_policies.TryGetValue((gameId, environmentId), out GamePolicy? candidate)
|
|
&& candidate.Enabled)
|
|
{
|
|
policy = candidate;
|
|
return true;
|
|
}
|
|
|
|
policy = null;
|
|
return false;
|
|
}
|
|
|
|
private static void Validate(GamePolicyOptions options)
|
|
{
|
|
if (!GameId.TryParse(options.GameId, out _)
|
|
|| !EnvironmentId.TryParse(options.EnvironmentId, out _))
|
|
{
|
|
throw new ProvisioningConfigurationException(
|
|
"Game policies require valid game and environment IDs.");
|
|
}
|
|
|
|
if (options.ProtocolVersions.Count is 0 or > ProvisioningLimits.MaxProtocolVersionsPerPolicy
|
|
|| options.ProtocolVersions.Contains(0)
|
|
|| options.ProtocolVersions.Count != options.ProtocolVersions.Distinct().Count())
|
|
{
|
|
throw new ProvisioningConfigurationException(
|
|
$"Policy {options.GameId}/{options.EnvironmentId} requires unique non-zero protocol versions.");
|
|
}
|
|
|
|
if (options.Regions.Count is 0 or > ProvisioningLimits.MaxRegionsPerPolicy
|
|
|| options.Regions.Any(static region => !RegionId.TryParse(region, out _))
|
|
|| options.Regions.Count != options.Regions.Distinct(StringComparer.Ordinal).Count())
|
|
{
|
|
throw new ProvisioningConfigurationException(
|
|
$"Policy {options.GameId}/{options.EnvironmentId} requires unique valid regions.");
|
|
}
|
|
|
|
if (options.VisibilityModes.Count == 0
|
|
|| options.VisibilityModes.Any(static mode => !Enum.IsDefined(mode))
|
|
|| options.VisibilityModes.Count != options.VisibilityModes.Distinct().Count()
|
|
|| options.PublisherTrustModes.Count == 0
|
|
|| options.PublisherTrustModes.Any(static mode => !Enum.IsDefined(mode))
|
|
|| options.PublisherTrustModes.Count != options.PublisherTrustModes.Distinct().Count())
|
|
{
|
|
throw new ProvisioningConfigurationException(
|
|
$"Policy {options.GameId}/{options.EnvironmentId} requires valid visibility and trust modes.");
|
|
}
|
|
|
|
if (options.MetadataMaxBytes is < 2 or > ContractLimits.MetadataMaxBytes
|
|
|| options.MetadataMaxKeys is < 0 or > ContractLimits.MetadataMaxKeys
|
|
|| options.MetadataValueMaxBytes.Count > options.MetadataMaxKeys
|
|
|| options.MetadataValueMaxBytes.Any(static item =>
|
|
string.IsNullOrWhiteSpace(item.Key)
|
|
|| !ContractValidation.IsUtf8LengthWithin(item.Key, ContractLimits.MetadataKeyMaxBytes)
|
|
|| item.Value is < 0 or > ContractLimits.MetadataValueMaxBytes)
|
|
|| options.RequiredMetadataKeys.Any(key =>
|
|
!options.MetadataValueMaxBytes.ContainsKey(key)))
|
|
{
|
|
throw new ProvisioningConfigurationException(
|
|
$"Policy {options.GameId}/{options.EnvironmentId} has an invalid metadata schema.");
|
|
}
|
|
|
|
if (options.MaxListingsPerPrincipal is < 1 or > ProvisioningLimits.MaxListingsPerPrincipal
|
|
|| options.MaxAnonymousListingsPerAddress < 0
|
|
|| options.MaxAnonymousListingsPerAddress > options.MaxListingsPerPrincipal
|
|
|| options.MaxActiveJoinAttempts is < 1
|
|
or > ProvisioningLimits.MaxActiveJoinAttemptsPerPolicy
|
|
|| !Enum.IsDefined(options.FallbackPolicy))
|
|
{
|
|
throw new ProvisioningConfigurationException(
|
|
$"Policy {options.GameId}/{options.EnvironmentId} has invalid quotas or fallback policy.");
|
|
}
|
|
}
|
|
}
|