using System.Net; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Fleet.Core; using Fleet.Link; using MoShiro.Measurement; using MoShiro.Protocol; namespace MoShiro.Agent.Tests; public sealed class TaskLinkTests { [Fact] public async Task QuicConnectionLoss_IsRedialedFromANewPort_WhileTheControlLayerStaysUp() { using ECDsa key = ECDsa.Create(ECCurve.NamedCurves.nistP256); var request = new CertificateRequest("CN=localhost", key, HashAlgorithmName.SHA256); using X509Certificate2 certificate = request.CreateSelfSigned(DateTimeOffset.UnixEpoch, DateTimeOffset.UnixEpoch.AddYears(100)); var session = new DataSession(1, new byte[32]); var clock = new MonoClock(TimeProvider.System); await using var server = new QuicDatagramTransport(new SingleSession(session), null, clock, new DataPlaneCounters(), NullNodeLog.Instance, IPAddress.Loopback, [0], certificate); server.Bind(); await using var client = new QuicDatagramTransport(session, clock, new DataPlaneCounters(), NullNodeLog.Instance, new IPEndPoint(IPAddress.Loopback, server.BoundPorts[0]), key.ExportSubjectPublicKeyInfo()); var status = new LinkStatus("agt_1", "ipv4", TimeProvider.System, NullNodeLog.Instance); status.SetControl(ControlState.Up); using var link = new TaskLink(client, status, TimeProvider.System); using var stop = new CancellationTokenSource(TimeSpan.FromSeconds(30)); Task run = link.RunAsync(stop.Token); await WaitUntilAsync(() => status.Quic == QuicState.Ready, stop.Token); int firstPort = client.BoundPorts[0]; await client.CloseClientAsync(); bool controlStayedUp = true; await WaitUntilAsync(() => { controlStayedUp &= status.Control == ControlState.Up; return status.Quic == QuicState.Ready && client.BoundPorts[0] != firstPort; }, stop.Token); Assert.True(controlStayedUp); LinkHealth health = status.Snapshot(new Outbox(1 << 10), 0, null); Assert.Equal(2, health.Task.Quic.HandshakeSuccesses); Assert.Equal(ControlState.Up, health.Control.State); await stop.CancelAsync(); await run.WaitAsync(TimeSpan.FromSeconds(5)); } private static async Task WaitUntilAsync(Func condition, CancellationToken ct) { while (!condition()) { await Task.Delay(20, ct); } } }