using System.Diagnostics; using System.Buffers.Binary; using System.IO.MemoryMappedFiles; using System.Net; using System.Net.Quic; using System.Net.Security; using System.Net.Sockets; using System.Security.Authentication; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using GameCult.Caching; using GameCult.Mesh; using GameCult.Mesh.Quic; using GameCult.Networking; var payloadBytes = ReadPayloadBytes(args); var payload = new byte[payloadBytes]; new Random(0xAE7E).NextBytes(payload); var expectedHash = Convert.ToHexString(SHA256.HashData(payload)); Console.WriteLine($"payloadBytes={payloadBytes} quicSupported={QuicListener.IsSupported}"); if (!args.Contains("--state-only", StringComparer.Ordinal)) { Print(MeasureMappedFile(payload)); var tcp = await MeasureTcpAsync(payload, expectedHash); Print(tcp); Print(await MeasureCultMeshTcpAsync(payload)); if (QuicListener.IsSupported) Print(await MeasureQuicAsync(payload, expectedHash)); else Console.WriteLine("plane=quic status=unsupported"); Print(await MeasureCultMeshRudpAsync(payload)); } var statePayloadBytes = ReadPositiveOption(args, "--state-bytes", 16 * 1024); var stateFrameCount = ReadPositiveOption(args, "--state-frames", 1_000); Console.WriteLine($"statePayloadBytes={statePayloadBytes} stateFrames={stateFrameCount}"); if (QuicListener.IsSupported) PrintState(await MeasureCultMeshQuicStateAsync(statePayloadBytes, stateFrameCount)); else Console.WriteLine("plane=cultmesh-quic-latest status=unsupported"); PrintState(await MeasureCultNetRudpLatestStateAsync(statePayloadBytes, stateFrameCount)); static int ReadPayloadBytes(string[] args) { const int defaultBytes = 56_204_750; var index = Array.IndexOf(args, "--bytes"); if (index < 0) return defaultBytes; if (index + 1 >= args.Length || !int.TryParse(args[index + 1], out var bytes) || bytes <= 0) throw new ArgumentException("--bytes requires a positive 32-bit integer."); return bytes; } static int ReadPositiveOption(string[] args, string option, int defaultValue) { var index = Array.IndexOf(args, option); if (index < 0) return defaultValue; if (index + 1 >= args.Length || !int.TryParse(args[index + 1], out var value) || value <= 0) throw new ArgumentException($"{option} requires a positive 32-bit integer."); return value; } static void Print(Result result) => Console.WriteLine( $"plane={result.Plane} elapsedMs={result.Elapsed.TotalMilliseconds:F1} " + (result.GoodputMiBps.HasValue ? $"goodputMiBps={result.GoodputMiBps.Value:F1}" : "") + (result.WireBytes.HasValue ? $" wireBytes={result.WireBytes.Value}" : string.Empty)); static void PrintState(StateResult result) => Console.WriteLine( $"plane={result.Plane} sendElapsedMs={result.SendElapsed.TotalMilliseconds:F1} " + $"settledElapsedMs={result.SettledElapsed.TotalMilliseconds:F1} " + $"offeredMiBps={result.OfferedMiBps:F1} deliveredFrames={result.DeliveredFrames}/{result.SentFrames} " + $"highestSequence={result.HighestSequence}" + (result.WireBytes.HasValue ? $" wireBytes={result.WireBytes.Value}" : string.Empty)); static Result MeasureMappedFile(byte[] payload) { var source = TempPath("mapped-source"); File.WriteAllBytes(source, payload); var elapsed = Stopwatch.StartNew(); try { using var mapping = MemoryMappedFile.CreateFromFile( source, FileMode.Open, null, 0, MemoryMappedFileAccess.Read); using var view = mapping.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read); if (view.ReadByte(0) != payload[0] || view.ReadByte(payload.LongLength - 1) != payload[^1]) throw new InvalidDataException("Mapped body did not expose the provider file."); elapsed.Stop(); return Result.CreateOpen("mapped-file-open", elapsed.Elapsed); } finally { File.Delete(source); } } static async Task MeasureTcpAsync(byte[] payload, string expectedHash) { using var listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); var endpoint = (IPEndPoint)listener.LocalEndpoint; var server = Task.Run(async () => { using var peer = await listener.AcceptTcpClientAsync(); await peer.GetStream().WriteAsync(payload); peer.Client.Shutdown(SocketShutdown.Send); }); var output = TempPath("tcp"); var elapsed = Stopwatch.StartNew(); using (var client = new TcpClient()) { await client.ConnectAsync(endpoint.Address, endpoint.Port); await using var file = new FileStream(output, FileMode.CreateNew, FileAccess.Write, FileShare.None, 1024 * 1024, FileOptions.Asynchronous | FileOptions.SequentialScan); await client.GetStream().CopyToAsync(file); await file.FlushAsync(); } await server; VerifyAndDelete(output, expectedHash); elapsed.Stop(); return Result.Create("tcp", payload.LongLength, elapsed.Elapsed); } static async Task MeasureQuicAsync(byte[] payload, string expectedHash) { var protocol = new SslApplicationProtocol("cultmesh-parity"); using var certificate = CreateCertificate(); await using var listener = await QuicListener.ListenAsync(new QuicListenerOptions { ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), ApplicationProtocols = [protocol], ConnectionOptionsCallback = (_, _, _) => ValueTask.FromResult(new QuicServerConnectionOptions { DefaultCloseErrorCode = 0, DefaultStreamErrorCode = 0, ServerAuthenticationOptions = new SslServerAuthenticationOptions { ApplicationProtocols = [protocol], EnabledSslProtocols = SslProtocols.Tls13, ServerCertificate = certificate } }) }); var server = Task.Run(async () => { await using var connection = await listener.AcceptConnectionAsync(); await using var stream = await connection.OpenOutboundStreamAsync(QuicStreamType.Unidirectional); await stream.WriteAsync(payload); stream.CompleteWrites(); await stream.WritesClosed; }); var output = TempPath("quic"); var elapsed = Stopwatch.StartNew(); await using (var connection = await QuicConnection.ConnectAsync(new QuicClientConnectionOptions { RemoteEndPoint = listener.LocalEndPoint, DefaultCloseErrorCode = 0, DefaultStreamErrorCode = 0, MaxInboundUnidirectionalStreams = 1, ClientAuthenticationOptions = new SslClientAuthenticationOptions { ApplicationProtocols = [protocol], EnabledSslProtocols = SslProtocols.Tls13, RemoteCertificateValidationCallback = (_, _, _, _) => true } })) await using (var stream = await connection.AcceptInboundStreamAsync()) await using (var file = new FileStream(output, FileMode.CreateNew, FileAccess.Write, FileShare.None, 1024 * 1024, FileOptions.Asynchronous | FileOptions.SequentialScan)) { await stream.CopyToAsync(file); await file.FlushAsync(); } await server; VerifyAndDelete(output, expectedHash); elapsed.Stop(); return Result.Create("quic-msquic", payload.LongLength, elapsed.Elapsed); } static async Task MeasureCultMeshQuicStateAsync(int payloadBytes, int frameCount) { using var certificate = CreateCertificate(); await using var server = await CultMeshQuicRealtimeServer.ListenAsync( new CultMeshQuicRealtimeServerOptions { ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), ServerCertificate = certificate }); var endpoint = $"{CultMeshQuicRealtimeTransportConnector.Scheme}://127.0.0.1:{server.LocalEndPoint.Port}"; using var discovery = new CultMeshDiscoveryService([new RouteSource(endpoint)]); using var sessions = new CultMeshSessionManager( discovery, Array.Empty(), Array.Empty(), [new CultMeshQuicRealtimeTransportConnector(new CultMeshQuicRealtimeConnectorOptions { ValidateProviderCertificate = (_, _, _, _) => true })], LocalDevelopmentSessions()); var session = await sessions.ConnectRealtimeAsync( new CultMeshSessionTarget("parity", "service:parity.provider")); var deadline = DateTime.UtcNow.AddSeconds(5); while (server.ConnectionCount == 0) { if (DateTime.UtcNow >= deadline) throw new TimeoutException("QUIC state client did not connect."); await Task.Delay(1); } var receivedFrames = 0; var highestSequence = -1L; using var receiveDeadline = new CancellationTokenSource(TimeSpan.FromSeconds(30)); var receive = Task.Run(async () => { while (true) { var frame = await session.ReceiveAsync(receiveDeadline.Token); receivedFrames++; highestSequence = Math.Max(highestSequence, frame.Sequence); if (frame.ProducerEpoch == 1 && frame.Sequence == frameCount - 1) return; } }); var payload = new byte[payloadBytes]; var elapsed = Stopwatch.StartNew(); for (var sequence = 0; sequence < frameCount; sequence++) { await server.BroadcastAsync(new CultMeshRealtimeFrame { ChannelId = "parity.entities", SchemaId = "parity.entity_soa.v1", BodyId = "body:parity:entities", ProducerEpoch = 1, Sequence = sequence, Delivery = CultMeshRealtimeDelivery.LatestOnly, Payload = payload }); } var sendElapsed = elapsed.Elapsed; await receive; elapsed.Stop(); return StateResult.Create( "cultmesh-quic-latest", payloadBytes, frameCount, receivedFrames, highestSequence, sendElapsed, elapsed.Elapsed); } static async Task MeasureCultNetRudpLatestStateAsync(int payloadBytes, int frameCount) { using var serverSocket = BindUdpSocket(); using var clientSocket = BindUdpSocket(); const uint connectionId = 0x50415249; using var server = new CultNetRudpSocketTransportConnection(new CultNetRudpSocketTransportOptions { RuntimeId = "parity-rudp-server", Socket = serverSocket, Mode = CultNetRudpSocketMode.Server, ConnectionId = connectionId, InitialSequence = 100, MaxFragmentBytes = 1024 }); using var client = new CultNetRudpSocketTransportConnection(new CultNetRudpSocketTransportOptions { RuntimeId = "parity-rudp-client", Socket = clientSocket, Mode = CultNetRudpSocketMode.Client, RemoteEndPoint = serverSocket.LocalEndPoint, ConnectionId = connectionId, InitialSequence = 1, MaxFragmentBytes = 1024 }); client.Connect("parity"); var handshakeDeadline = DateTime.UtcNow.AddSeconds(5); while (!client.Connected || !server.Connected) { server.ReceiveOnce(); client.ReceiveOnce(); if (DateTime.UtcNow >= handshakeDeadline) throw new TimeoutException("RUDP state peers did not connect."); } var receivedFrames = 0; var highestSequence = -1L; using var receiveDeadline = new CancellationTokenSource(); var receive = Task.Run(() => { while (!receiveDeadline.IsCancellationRequested) { var frame = server.ReceiveOnce(); if (frame == null) continue; receivedFrames++; highestSequence = Math.Max(highestSequence, BinaryPrimitives.ReadInt64LittleEndian(frame.Payload)); } }); var payload = new byte[Math.Max(payloadBytes, sizeof(long))]; var elapsed = Stopwatch.StartNew(); for (var sequence = 0; sequence < frameCount; sequence++) { BinaryPrimitives.WriteInt64LittleEndian(payload, sequence); client.SendLatest(payload); } var sendElapsed = elapsed.Elapsed; await Task.Delay(250); receiveDeadline.Cancel(); await receive; elapsed.Stop(); return StateResult.Create( "cultnet-rudp-latest", payload.Length, frameCount, receivedFrames, highestSequence, sendElapsed, elapsed.Elapsed, client.Stats.BytesSent); } static async Task MeasureCultMeshRudpAsync(byte[] payload) { var artifact = CultMesh.PackCdnArtifact( "parity/body", payload, new CultMeshCdnPackOptions { ChunkSizeBytes = 1024 * 1024 }); using var providerCache = new CultCache(CultMesh.CreateCultCacheDocumentRegistry( typeof(CultMeshCdnArtifactManifest), typeof(CultMeshCdnArtifactChunk))); await CultMeshCdn.PublishAsync(providerCache, artifact); using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); socket.Bind(new IPEndPoint(IPAddress.Loopback, 0)); using var wireServer = new RudpCultNetSchemaServer(new RudpCultNetSchemaServerOptions { RuntimeId = "cultmesh-transport-parity", Socket = socket, MaxFragmentBytes = 1024, MaxPendingReliablePackets = 8192 }); using var contentServer = new CultMeshLegacyRudpContentServer(wireServer, providerCache); using var identityServer = new CultMeshSessionIdentityServer( wireServer, "service:parity.provider", ["parity"], [CultMeshProtocols.Content.Value], ["parity-route-1"]); using var pumpCancellation = new CancellationTokenSource(); var pump = Task.Run(async () => { while (!pumpCancellation.IsCancellationRequested) { var progress = await wireServer.PollAvailableAsync(256); wireServer.PollResends(); if (progress.TransportItemsConsumed == 0) await Task.Delay(1, pumpCancellation.Token); } }); var endpoint = $"rudp://127.0.0.1:{wireServer.LocalEndPoint.Port}"; using var discovery = new CultMeshDiscoveryService([new RouteSource(endpoint)]); using var sessions = new CultMeshSessionManager( discovery, Array.Empty(), [new CultMeshLegacyRudpContentTransportConnector( new CultMeshLegacyRudpContentTransportOptions { ResponseTimeout = TimeSpan.FromMinutes(2) })], LocalDevelopmentSessions()); var provider = new CultMeshSessionContentProvider( "parity.provider", sessions, new CultMeshSessionTarget("parity", "service:parity.provider")); var directory = Path.Combine(Path.GetTempPath(), "cultmesh-transport-parity", Guid.NewGuid().ToString("N")); using var transferState = new CultCache(CultMesh.CreateCultCacheDocumentRegistry( typeof(CultMeshContentTransferStateDocument))); var transfer = new CultMeshContentTransferService( transferState, [provider], new CultMeshContentTransferOptions(directory)); var elapsed = Stopwatch.StartNew(); try { var path = await transfer.FetchAsync(artifact.Manifest); elapsed.Stop(); if (!File.ReadAllBytes(path).AsSpan().SequenceEqual(payload)) throw new InvalidDataException("CultMesh RUDP payload did not match source."); return Result.Create("cultmesh-rudp", payload.LongLength, elapsed.Elapsed, wireServer.Stats.BytesSent); } finally { pumpCancellation.Cancel(); try { await pump; } catch (OperationCanceledException) { } if (Directory.Exists(directory)) Directory.Delete(directory, true); } } static async Task MeasureCultMeshTcpAsync(byte[] payload) { var artifact = CultMesh.PackCdnArtifact( "parity/body", payload, new CultMeshCdnPackOptions { ChunkSizeBytes = 1024 * 1024 }); using var providerCache = new CultCache(CultMesh.CreateCultCacheDocumentRegistry( typeof(CultMeshCdnArtifactManifest), typeof(CultMeshCdnArtifactChunk))); await CultMeshCdn.PublishAsync(providerCache, artifact); using var contentServer = new CultMeshTcpContentServer( new TcpListener(IPAddress.Loopback, 0), providerCache, new CultMeshTcpContentServerOptions { VerseId = "parity", AuthorityRuntimeId = "service:parity.provider", RouteGeneration = "parity-route-1" }); var endpoint = $"{CultMeshTcpContentTransportConnector.Scheme}://127.0.0.1:{contentServer.LocalEndPoint.Port}"; using var discovery = new CultMeshDiscoveryService([new RouteSource(endpoint)]); using var sessions = new CultMeshSessionManager( discovery, Array.Empty(), [new CultMeshTcpContentTransportConnector()], LocalDevelopmentSessions()); var provider = new CultMeshSessionContentProvider( "parity.provider", sessions, new CultMeshSessionTarget("parity", "service:parity.provider")); var directory = Path.Combine(Path.GetTempPath(), "cultmesh-transport-parity", Guid.NewGuid().ToString("N")); using var transferState = new CultCache(CultMesh.CreateCultCacheDocumentRegistry( typeof(CultMeshContentTransferStateDocument))); var transfer = new CultMeshContentTransferService( transferState, [provider], new CultMeshContentTransferOptions(directory)); var elapsed = Stopwatch.StartNew(); try { var path = await transfer.FetchAsync(artifact.Manifest); elapsed.Stop(); if (!File.ReadAllBytes(path).AsSpan().SequenceEqual(payload)) throw new InvalidDataException("CultMesh TCP payload did not match source."); return Result.Create("cultmesh-tcp-content", payload.LongLength, elapsed.Elapsed); } finally { if (Directory.Exists(directory)) Directory.Delete(directory, true); } } static X509Certificate2 CreateCertificate() { using var key = RSA.Create(2048); var request = new CertificateRequest("CN=localhost", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false)); request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false)); request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension( new OidCollection { new("1.3.6.1.5.5.7.3.1") }, false)); var names = new SubjectAlternativeNameBuilder(); names.AddDnsName("localhost"); names.AddIpAddress(IPAddress.Loopback); request.CertificateExtensions.Add(names.Build()); request.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(request.PublicKey, false)); using var certificate = request.CreateSelfSigned( DateTimeOffset.UtcNow.AddMinutes(-1), DateTimeOffset.UtcNow.AddHours(1)); return X509CertificateLoader.LoadPkcs12(certificate.Export(X509ContentType.Pfx), null); } static string TempPath(string plane) => Path.Combine(Path.GetTempPath(), $"cultmesh-parity-{plane}-{Guid.NewGuid():N}.body"); static CultMeshSessionManagerOptions LocalDevelopmentSessions() => new() { Trust = new CultMeshAuthorityTrustPolicy(CultMeshAuthorityTrustMode.LocalDevelopment) }; static Socket BindUdpSocket() { var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); socket.Bind(new IPEndPoint(IPAddress.Loopback, 0)); socket.ReceiveTimeout = 20; return socket; } static void VerifyAndDelete(string path, string expectedHash) { try { using var input = File.OpenRead(path); var actualHash = Convert.ToHexString(SHA256.HashData(input)); if (!actualHash.Equals(expectedHash, StringComparison.Ordinal)) throw new InvalidDataException($"Body hash mismatch: expected {expectedHash}, got {actualHash}."); } finally { File.Delete(path); } } internal sealed class RouteSource(string endpoint) : ICultMeshLookupSource { public string SourceId => "parity-route"; public Task> LookupAsync( CultMeshDiscoveryQuery query, CancellationToken cancellationToken = default) { var now = DateTimeOffset.UtcNow; IReadOnlyList observations = [ new( new CultMeshVerseDescriptor( "parity", "Transport parity", CultMeshVerseAuthorityModel.OperatorCluster, new CultMeshVerseCompatibility("cultmesh.v1", "parity"), authorityRoutes: [ new CultMeshAuthorityRoute( "service:parity.provider", endpoint, generation: "parity-route-1") ]), SourceId, now, now.AddMinutes(1), CultMeshDiscoveryTrust.Signed) ]; return Task.FromResult(observations); } } internal sealed record Result(string Plane, TimeSpan Elapsed, double? GoodputMiBps, long? WireBytes) { public static Result Create(string plane, long bytes, TimeSpan elapsed, long? wireBytes = null) => new(plane, elapsed, bytes / 1024d / 1024d / elapsed.TotalSeconds, wireBytes); public static Result CreateOpen(string plane, TimeSpan elapsed) => new(plane, elapsed, null, null); } internal sealed record StateResult( string Plane, int SentFrames, int DeliveredFrames, long HighestSequence, TimeSpan SendElapsed, TimeSpan SettledElapsed, double OfferedMiBps, long? WireBytes) { public static StateResult Create( string plane, int payloadBytes, int sentFrames, int deliveredFrames, long highestSequence, TimeSpan sendElapsed, TimeSpan settledElapsed, long? wireBytes = null) => new( plane, sentFrames, deliveredFrames, highestSequence, sendElapsed, settledElapsed, (long)payloadBytes * sentFrames / 1024d / 1024d / sendElapsed.TotalSeconds, wireBytes); }