using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using NATS.Client.Core; namespace NATS.Server.Benchmark.Tests.Infrastructure; /// /// Starts both a Go and .NET NATS server with TLS enabled for transport overhead benchmarks. /// Shared across all tests in the "Benchmark-Tls" collection. /// public sealed class TlsServerFixture : IAsyncLifetime { private GoServerProcess? _goServer; private DotNetServerProcess? _dotNetServer; private string? _tempDir; public int GoPort => _goServer?.Port ?? throw new InvalidOperationException("Go server not started"); public int DotNetPort => _dotNetServer?.Port ?? throw new InvalidOperationException(".NET server not started"); public bool GoAvailable => _goServer is not null; public async Task InitializeAsync() { _tempDir = Path.Combine(Path.GetTempPath(), $"nats-bench-tls-{Guid.NewGuid():N}"); Directory.CreateDirectory(_tempDir); var caCertPath = Path.Combine(_tempDir, "ca.pem"); var serverCertPath = Path.Combine(_tempDir, "server-cert.pem"); var serverKeyPath = Path.Combine(_tempDir, "server-key.pem"); GenerateCertificates(caCertPath, serverCertPath, serverKeyPath); var config = $$""" tls { cert_file: "{{serverCertPath}}" key_file: "{{serverKeyPath}}" ca_file: "{{caCertPath}}" } """; _dotNetServer = new DotNetServerProcess(config); var dotNetTask = _dotNetServer.StartAsync(); if (GoServerProcess.IsAvailable()) { _goServer = new GoServerProcess(config); await Task.WhenAll(dotNetTask, _goServer.StartAsync()); } else { await dotNetTask; } } public async Task DisposeAsync() { if (_goServer is not null) await _goServer.DisposeAsync(); if (_dotNetServer is not null) await _dotNetServer.DisposeAsync(); if (_tempDir is not null && Directory.Exists(_tempDir)) { try { Directory.Delete(_tempDir, recursive: true); } catch { /* best-effort cleanup */ } } } public NatsConnection CreateGoTlsClient() => CreateTlsClient(GoPort); public NatsConnection CreateDotNetTlsClient() => CreateTlsClient(DotNetPort); private static NatsConnection CreateTlsClient(int port) { var opts = new NatsOpts { Url = $"nats://127.0.0.1:{port}", TlsOpts = new NatsTlsOpts { Mode = TlsMode.Require, InsecureSkipVerify = true, }, }; return new NatsConnection(opts); } private static void GenerateCertificates(string caCertPath, string serverCertPath, string serverKeyPath) { using var caKey = RSA.Create(2048); var caReq = new CertificateRequest( "CN=Benchmark Test CA", caKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); caReq.CertificateExtensions.Add( new X509BasicConstraintsExtension(certificateAuthority: true, hasPathLengthConstraint: false, pathLengthConstraint: 0, critical: true)); var now = DateTimeOffset.UtcNow; using var caCert = caReq.CreateSelfSigned(now.AddMinutes(-5), now.AddDays(1)); using var serverKey = RSA.Create(2048); var serverReq = new CertificateRequest( "CN=localhost", serverKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); var sanBuilder = new SubjectAlternativeNameBuilder(); sanBuilder.AddIpAddress(System.Net.IPAddress.Loopback); sanBuilder.AddDnsName("localhost"); serverReq.CertificateExtensions.Add(sanBuilder.Build()); serverReq.CertificateExtensions.Add( new X509BasicConstraintsExtension(certificateAuthority: false, hasPathLengthConstraint: false, pathLengthConstraint: 0, critical: false)); using var serverCert = serverReq.Create(caCert, now.AddMinutes(-5), now.AddDays(1), [1, 2, 3, 4]); File.WriteAllText(caCertPath, caCert.ExportCertificatePem()); File.WriteAllText(serverCertPath, serverCert.ExportCertificatePem()); File.WriteAllText(serverKeyPath, serverKey.ExportRSAPrivateKeyPem()); } }