using System.Collections.Concurrent; using System.Net; using System.Security.Cryptography; using MeWeb.Shared; namespace MeWeb.Server; public class SpeedService { const int PerHour = 12; const int AtOnce = 4; int active; static readonly byte[] Noise = RandomNumberGenerator.GetBytes(1024 * 1024); readonly ConcurrentDictionary> uses = new(); public bool Allow(IPAddress caller) { var now = DateTimeOffset.UtcNow; var times = uses.GetOrAdd(ProbeService.NetworkOf(caller), _ => []); lock (times) { times.RemoveAll(t => now - t > TimeSpan.FromHours(1)); if (times.Count >= PerHour) return false; times.Add(now); return true; } } public bool Enter() { if (Interlocked.Increment(ref active) <= AtOnce) return true; Interlocked.Decrement(ref active); return false; } public void Leave() => Interlocked.Decrement(ref active); public static async Task Stream(HttpResponse response, int bytes, CancellationToken ct) { response.ContentType = "application/octet-stream"; response.ContentLength = bytes; response.Headers.CacheControl = "no-store"; var left = bytes; while (left > 0) { var chunk = Math.Min(left, Noise.Length); await response.Body.WriteAsync(Noise.AsMemory(0, chunk), ct); left -= chunk; } } public static async Task Drain(Stream body, CancellationToken ct) { var buffer = new byte[64 * 1024]; long total = 0; var read = default(int); while ((read = await body.ReadAsync(buffer, ct)) > 0) { total += read; if (total > Endpoints.Speed.MaxUpBytes) return -1; } return total; } }