using CryptoExchange.Net.Objects; using CryptoExchange.Net.RateLimiting.Interfaces; using CryptoExchange.Net.RateLimiting.Trackers; using System; using System.Collections.Generic; namespace CryptoExchange.Net.RateLimiting.Guards { /// public class RateLimitGuard : IRateLimitGuard { /// /// Apply guard per host /// public static Func PerHost { get; } = new Func((def, host, key) => host); /// /// Apply guard per endpoint /// public static Func PerEndpoint { get; } = new Func((def, host, key) => def.Path + def.Method); /// /// Apply guard per connection /// public static Func PerConnection { get; } = new Func((def, host, key) => def.ConnectionId.ToString()!); /// /// Apply guard per API key /// public static Func PerApiKey { get; } = new Func((def, host, key) => key!); /// /// Apply guard per API key per endpoint /// public static Func PerApiKeyPerEndpoint { get; } = new Func((def, host, key) => key! + def.Path + def.Method); private readonly IEnumerable _filters; private readonly Dictionary _trackers; private RateLimitWindowType _windowType; private double? _decayRate; private int? _connectionWeight; private readonly Func _keySelector; /// public string Name => "RateLimitGuard"; /// public string Description => _windowType == RateLimitWindowType.Decay ? $"Limit of {Limit} with a decay rate of {_decayRate}" : $"Limit of {Limit} per {TimeSpan}"; /// /// The limit per period /// public int Limit { get; } /// /// The time period for the limit /// public TimeSpan TimeSpan { get; } /// /// ctor /// /// The rate limit key selector /// Filter for rate limit items. Only when the rate limit item passes the filter the guard will apply /// Limit per period /// Timespan for the period /// Type of rate limit window /// The decay per timespan if windowType is DecayWindowTracker /// The weight of a new connection public RateLimitGuard(Func keySelector, IGuardFilter filter, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null) : this(keySelector, new[] { filter }, limit, timeSpan, windowType, decayPerTimeSpan, connectionWeight) { } /// /// ctor /// /// The rate limit key selector /// Filters for rate limit items. Only when the rate limit item passes all filters the guard will apply /// Limit per period /// Timespan for the period /// Type of rate limit window /// The decay per timespan if windowType is DecayWindowTracker /// The weight of a new connection public RateLimitGuard(Func keySelector, IEnumerable filters, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null) { _filters = filters; _trackers = new Dictionary(); _windowType = windowType; Limit = limit; TimeSpan = timeSpan; _keySelector = keySelector; _decayRate = decayPerTimeSpan; _connectionWeight = connectionWeight; } /// public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight) { foreach(var filter in _filters) { if (!filter.Passes(type, definition, host, apiKey)) return LimitCheck.NotApplicable; } if (type == RateLimitItemType.Connection) requestWeight = _connectionWeight ?? requestWeight; var key = _keySelector(definition, host, apiKey); if (!_trackers.TryGetValue(key, out var tracker)) { tracker = CreateTracker(); _trackers.Add(key, tracker); } var delay = tracker.GetWaitTime(requestWeight); if (delay == default) return LimitCheck.NotNeeded(Limit, TimeSpan, tracker.Current); return LimitCheck.Needed(delay, Limit, TimeSpan, tracker.Current); } /// public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight) { foreach (var filter in _filters) { if (!filter.Passes(type, definition, host, apiKey)) return RateLimitState.NotApplied; } if (type == RateLimitItemType.Connection) requestWeight = _connectionWeight ?? requestWeight; var key = _keySelector(definition, host, apiKey); var tracker = _trackers[key]; tracker.ApplyWeight(requestWeight); return RateLimitState.Applied(Limit, TimeSpan, tracker.Current); } /// /// Create a new WindowTracker /// /// protected IWindowTracker CreateTracker() { return _windowType == RateLimitWindowType.Sliding ? new SlidingWindowTracker(Limit, TimeSpan) : _windowType == RateLimitWindowType.Fixed ? new FixedWindowTracker(Limit, TimeSpan) : _windowType == RateLimitWindowType.FixedAfterFirst ? new FixedAfterStartWindowTracker(Limit, TimeSpan) : new DecayWindowTracker(Limit, TimeSpan, _decayRate ?? throw new InvalidOperationException("Decay rate not provided")); } } }