Files
MeWeb/src/MeWeb.Server/SpeedService.cs
T
thepraandClaude Opus 5.5 d408edea98
Build / Desktop, server and tools (push) Successful in 1m8s
House rules: the build checks them, and every refusal from the server answers 400, 403 or 404
The server no longer answers 409, 413 or 429: a busy probe, a share or upload that is too large and a request over
the limit now answer 400 with the same reason as before, and still say when to try again. The app reads the reason,
so nothing changes for it; versions up to 0.15.4 report a limited speed test as unmeasured until they update.
The app's commands can now be cancelled all the way down, and the house rules are written into PATTERNS.md and
checked on every build, Android included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W4YtEJrgxG12Pc39Fai8Bd
2026-11-10 05:32:04 +01:00

66 lines
1.7 KiB
C#

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<string, List<DateTimeOffset>> 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<long> 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;
}
}