2026-10-15 05:41:44 +02:00
|
|
|
using System.Diagnostics;
|
2026-10-15 11:11:24 +02:00
|
|
|
using System.Net;
|
|
|
|
|
using System.Net.Sockets;
|
2026-10-15 05:41:44 +02:00
|
|
|
|
|
|
|
|
using MeWeb.Shared;
|
|
|
|
|
|
|
|
|
|
namespace MeWeb.Core.Net;
|
|
|
|
|
|
|
|
|
|
public class SpeedReport
|
|
|
|
|
{
|
|
|
|
|
public double DownMbps { get; set; }
|
|
|
|
|
public double UpMbps { get; set; }
|
|
|
|
|
public int DownBytes { get; set; }
|
|
|
|
|
public int UpBytes { get; set; }
|
|
|
|
|
public bool Limited { get; set; }
|
2026-10-15 11:11:24 +02:00
|
|
|
public int RoundTripMs { get; set; }
|
2026-10-15 05:41:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static class SpeedCheck
|
|
|
|
|
{
|
|
|
|
|
public static async Task<SpeedReport> Run(HttpClient https, LinkKind kind, CancellationToken ct)
|
|
|
|
|
{
|
|
|
|
|
var mobile = kind == LinkKind.Mobile;
|
|
|
|
|
var report = new SpeedReport { DownBytes = mobile ? 2 * 1024 * 1024 : 12 * 1024 * 1024, UpBytes = mobile ? 1024 * 1024 : 4 * 1024 * 1024 };
|
2026-10-15 11:11:24 +02:00
|
|
|
report.RoundTripMs = await RoundTrip(ct);
|
2026-10-15 05:41:44 +02:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
|
|
|
|
timeout.CancelAfter(TimeSpan.FromSeconds(30));
|
|
|
|
|
var clock = Stopwatch.StartNew();
|
|
|
|
|
using (var response = await https.GetAsync(Endpoints.Base + Endpoints.Speed.GET_M_Down(report.DownBytes), HttpCompletionOption.ResponseHeadersRead, timeout.Token))
|
|
|
|
|
{
|
|
|
|
|
if ((int)response.StatusCode == 429)
|
|
|
|
|
{
|
|
|
|
|
report.Limited = true;
|
|
|
|
|
return report;
|
|
|
|
|
}
|
|
|
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
|
await using var body = await response.Content.ReadAsStreamAsync(timeout.Token);
|
|
|
|
|
var buffer = new byte[64 * 1024];
|
2026-10-15 09:10:37 +02:00
|
|
|
var warm = report.DownBytes / 4;
|
2026-10-15 05:41:44 +02:00
|
|
|
long total = 0;
|
2026-10-15 09:10:37 +02:00
|
|
|
long warmBytes = 0;
|
|
|
|
|
var warmAt = TimeSpan.Zero;
|
2026-10-15 05:41:44 +02:00
|
|
|
int read;
|
2026-10-15 09:10:37 +02:00
|
|
|
while ((read = await body.ReadAsync(buffer, timeout.Token)) > 0)
|
|
|
|
|
{
|
|
|
|
|
total += read;
|
|
|
|
|
if (warmBytes > 0 || total < warm) continue;
|
|
|
|
|
warmBytes = total;
|
|
|
|
|
warmAt = clock.Elapsed;
|
|
|
|
|
}
|
|
|
|
|
var steady = clock.Elapsed - warmAt;
|
|
|
|
|
report.DownMbps = warmBytes > 0 && total > warmBytes && steady > TimeSpan.FromMilliseconds(50)
|
|
|
|
|
? Mbps(total - warmBytes, steady)
|
|
|
|
|
: Mbps(total, clock.Elapsed);
|
2026-10-15 05:41:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var payload = new byte[report.UpBytes];
|
|
|
|
|
Random.Shared.NextBytes(payload);
|
|
|
|
|
clock.Restart();
|
|
|
|
|
using (var response = await https.PostAsync(Endpoints.Base + Endpoints.Speed.POST_Up, new ByteArrayContent(payload), timeout.Token))
|
|
|
|
|
{
|
|
|
|
|
if (response.IsSuccessStatusCode) report.UpMbps = Mbps(payload.Length, clock.Elapsed);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-15 09:10:37 +02:00
|
|
|
catch (Exception) when (!ct.IsCancellationRequested)
|
2026-10-15 05:41:44 +02:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
return report;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-15 11:11:24 +02:00
|
|
|
static async Task<int> RoundTrip(CancellationToken ct)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var addresses = await Dns.GetHostAddressesAsync(Endpoints.Host, ct);
|
|
|
|
|
if (addresses.Length == 0) return 0;
|
|
|
|
|
var samples = new List<double>();
|
|
|
|
|
for (var i = 0; i < 3; i++)
|
|
|
|
|
{
|
|
|
|
|
using var client = new TcpClient(addresses[0].AddressFamily);
|
|
|
|
|
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
|
|
|
|
timeout.CancelAfter(TimeSpan.FromSeconds(3));
|
|
|
|
|
var clock = Stopwatch.StartNew();
|
|
|
|
|
await client.ConnectAsync(addresses[0], 443, timeout.Token);
|
|
|
|
|
samples.Add(clock.Elapsed.TotalMilliseconds);
|
|
|
|
|
}
|
|
|
|
|
return (int)Math.Round(samples.Order().ElementAt(samples.Count / 2));
|
|
|
|
|
}
|
|
|
|
|
catch (Exception) when (!ct.IsCancellationRequested)
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-15 05:41:44 +02:00
|
|
|
static double Mbps(long bytes, TimeSpan elapsed) => elapsed.TotalSeconds <= 0 ? 0 : Math.Round(bytes * 8 / elapsed.TotalSeconds / 1_000_000, 1);
|
|
|
|
|
}
|