Build / Desktop, server and tools (push) Successful in 1m8s
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
66 lines
1.7 KiB
C#
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;
|
|
}
|
|
}
|