4 using System.Collections.Generic;
5 using System.Collections.Immutable;
25 internal sealed
class RateLimiter
27 private sealed record RateLimit(DateTimeOffset Expiry)
29 public int RequestAmount;
32 private readonly Dictionary<Client, RateLimit> rateLimits =
new();
33 private readonly HashSet<Client> expiredRateLimits =
new();
34 private readonly Dictionary<Client, DateTimeOffset> recentlyAnnouncedOffenders =
new();
36 private readonly
int maxRequests, expiryInSeconds;
38 private readonly ImmutableDictionary<RateLimitAction, RateLimitPunishment> punishments;
42 this.maxRequests = maxRequests;
43 this.expiryInSeconds = expiryInSeconds;
45 punishments = punishmentRules.ToImmutableDictionary(
46 static pair => pair.Action,
47 static pair => pair.Punishment);
50 public bool IsLimitReached(
Client client)
53 if (IsExempt(client)) {
return false; }
55 expiredRateLimits.Clear();
57 foreach (var (c, limit) in rateLimits)
59 if (limit.Expiry < DateTimeOffset.Now)
61 expiredRateLimits.Add(c);
65 foreach (
Client c
in expiredRateLimits)
70 if (!rateLimits.TryGetValue(client, out RateLimit? rateLimit))
72 rateLimit =
new RateLimit(DateTimeOffset.Now.AddSeconds(expiryInSeconds));
73 rateLimits.Add(client, rateLimit);
76 rateLimit.RequestAmount++;
78 if (rateLimit.RequestAmount > maxRequests)
80 ProcessPunishment(client, rateLimit.RequestAmount);
87 private void ProcessPunishment(
Client client,
int requests)
91 foreach (var (action, punishment) in punishments)
104 AnnounceOffender(client);
107 GameMain.Server?.BanClient(client, TextManager.Get(
"SpamFilterKicked").Value);
110 GameMain.Server?.KickClient(client, TextManager.Get(
"SpamFilterKicked").Value);
118 private void AnnounceOffender(
Client client)
120 if (recentlyAnnouncedOffenders.TryGetValue(client, out DateTimeOffset expiry))
122 if (expiry > DateTimeOffset.Now) {
return; }
124 recentlyAnnouncedOffenders.Remove(client);
128 recentlyAnnouncedOffenders.Add(client, DateTimeOffset.Now.AddSeconds(expiryInSeconds));
131 public static bool IsExempt(
Client client) =>
132 (GameMain.Server.OwnerConnection !=
null && client.
Connection == GameMain.Server.OwnerConnection)
bool HasPermission(ClientPermissions permission)
NetworkConnection Connection
static void Log(string line, ServerLog.MessageType messageType)