public async Task MultipleStreamsOnSingleConnection() { using QuicListener listener = CreateQuicListener(); Task listenTask = Task.Run(async() => { { using QuicConnection connection = await listener.AcceptConnectionAsync(); await using QuicStream stream = await connection.AcceptStreamAsync(); await using QuicStream stream2 = await connection.AcceptStreamAsync(); byte[] buffer = new byte[s_data.Length]; while (true) { int bytesRead = await stream.ReadAsync(buffer); if (bytesRead == 0) { break; } Assert.Equal(s_data.Length, bytesRead); Assert.True(s_data.Span.SequenceEqual(buffer)); } while (true) { int bytesRead = await stream2.ReadAsync(buffer); if (bytesRead == 0) { break; } Assert.True(s_data.Span.SequenceEqual(buffer)); } await stream.WriteAsync(s_data, endStream: true); await stream.ShutdownWriteCompleted(); await stream2.WriteAsync(s_data, endStream: true); await stream2.ShutdownWriteCompleted(); await connection.CloseAsync(errorCode: 0); } }); Task clientTask = Task.Run(async() => { using QuicConnection connection = CreateQuicConnection(listener.ListenEndPoint); await connection.ConnectAsync(); await using QuicStream stream = connection.OpenBidirectionalStream(); await using QuicStream stream2 = connection.OpenBidirectionalStream(); await stream.WriteAsync(s_data, endStream: true); await stream.ShutdownWriteCompleted(); await stream2.WriteAsync(s_data, endStream: true); await stream2.ShutdownWriteCompleted(); byte[] memory = new byte[12]; while (true) { int res = await stream.ReadAsync(memory); if (res == 0) { break; } Assert.True(s_data.Span.SequenceEqual(memory)); } while (true) { int res = await stream2.ReadAsync(memory); if (res == 0) { break; } Assert.True(s_data.Span.SequenceEqual(memory)); } await connection.CloseAsync(errorCode: 0); }); await(new[] { listenTask, clientTask }).WhenAllOrAnyFailed(millisecondsTimeout: 60000); }
public async Task AcceptAsync_ClientStartsAndStopsBidirectionStream_ServerAccepts() { // Arrange await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory); var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint); using var quicConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options); await quicConnection.ConnectAsync().DefaultTimeout(); var serverConnection = await connectionListener.AcceptAsync().DefaultTimeout(); // Act var acceptTask = serverConnection.AcceptAsync(); await using var clientStream = quicConnection.OpenBidirectionalStream(); await clientStream.WriteAsync(TestData); await using var serverStream = await acceptTask.DefaultTimeout(); await serverStream.Transport.Output.WriteAsync(TestData); // Assert Assert.NotNull(serverStream); Assert.False(serverStream.ConnectionClosed.IsCancellationRequested); var closedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); serverStream.ConnectionClosed.Register(() => closedTcs.SetResult()); // Read data from client. var read = await serverStream.Transport.Input.ReadAtLeastAsync(TestData.Length).DefaultTimeout(); Assert.Equal(TestData, read.Buffer.ToArray()); serverStream.Transport.Input.AdvanceTo(read.Buffer.End); // Read data from server. var data = new List <byte>(); var buffer = new byte[1024]; var readCount = 0; while ((readCount = await clientStream.ReadAsync(buffer).DefaultTimeout()) != -1) { data.AddRange(buffer.AsMemory(0, readCount).ToArray()); if (data.Count == TestData.Length) { break; } } Assert.Equal(TestData, data); // Shutdown from client. clientStream.Shutdown(); // Get shutdown from client. read = await serverStream.Transport.Input.ReadAsync().DefaultTimeout(); Assert.True(read.IsCompleted); await closedTcs.Task.DefaultTimeout(); }
public async Task ConnectWithCertificateCallback() { X509Certificate2 c1 = System.Net.Test.Common.Configuration.Certificates.GetServerCertificate(); X509Certificate2 c2 = System.Net.Test.Common.Configuration.Certificates.GetClientCertificate(); // This 'wrong' certificate but should be sufficient X509Certificate2 expectedCertificate = c1; using CancellationTokenSource cts = new CancellationTokenSource(); cts.CancelAfter(PassingTestTimeout); string? receivedHostName = null; X509Certificate?receivedCertificate = null; var listenerOptions = new QuicListenerOptions(); listenerOptions.ListenEndPoint = new IPEndPoint(Socket.OSSupportsIPv6 ? IPAddress.IPv6Loopback : IPAddress.Loopback, 0); listenerOptions.ServerAuthenticationOptions = GetSslServerAuthenticationOptions(); listenerOptions.ServerAuthenticationOptions.ServerCertificate = null; listenerOptions.ServerAuthenticationOptions.ServerCertificateSelectionCallback = (sender, hostName) => { receivedHostName = hostName; if (hostName == "foobar1") { return(c1); } else if (hostName == "foobar2") { return(c2); } return(null); }; using QuicListener listener = new QuicListener(QuicImplementationProviders.MsQuic, listenerOptions); QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(); clientOptions.ClientAuthenticationOptions.TargetHost = "foobar1"; clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => { receivedCertificate = cert; return(true); }; (QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(clientOptions, listener); Assert.Equal(clientOptions.ClientAuthenticationOptions.TargetHost, receivedHostName); Assert.Equal(c1, receivedCertificate); clientConnection.Dispose(); serverConnection.Dispose(); // This should fail when callback return null. clientOptions.ClientAuthenticationOptions.TargetHost = "foobar3"; clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, clientOptions); Task clientTask = clientConnection.ConnectAsync(cts.Token).AsTask(); await Assert.ThrowsAsync <QuicException>(() => clientTask); Assert.Equal(clientOptions.ClientAuthenticationOptions.TargetHost, receivedHostName); clientConnection.Dispose(); // Do this last to make sure Listener is still functional. clientOptions.ClientAuthenticationOptions.TargetHost = "foobar2"; expectedCertificate = c2; (clientConnection, serverConnection) = await CreateConnectedQuicConnection(clientOptions, listener); Assert.Equal(clientOptions.ClientAuthenticationOptions.TargetHost, receivedHostName); Assert.Equal(c2, receivedCertificate); clientConnection.Dispose(); serverConnection.Dispose(); }
public async Task BidirectionalStream_ServerWritesDataAndDisposes_ClientReadsData(int dataLength) { // Arrange var testData = new byte[dataLength]; for (int i = 0; i < dataLength; i++) { testData[i] = (byte)i; } await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory); var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint); using var clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options); await clientConnection.ConnectAsync().DefaultTimeout(); await using var serverConnection = await connectionListener.AcceptAndAddFeatureAsync().DefaultTimeout(); // Act Logger.LogInformation("Client starting stream."); var clientStream = await clientConnection.OpenBidirectionalStreamAsync(); await clientStream.WriteAsync(TestData, endStream : true).DefaultTimeout(); var serverStream = await serverConnection.AcceptAsync().DefaultTimeout(); Logger.LogInformation("Server accepted stream."); var readResult = await serverStream.Transport.Input.ReadAtLeastAsync(TestData.Length).DefaultTimeout(); serverStream.Transport.Input.AdvanceTo(readResult.Buffer.End); // Input should be completed. readResult = await serverStream.Transport.Input.ReadAsync().DefaultTimeout(); Assert.True(readResult.IsCompleted); Logger.LogInformation("Server sending data."); await serverStream.Transport.Output.WriteAsync(testData).DefaultTimeout(); Logger.LogInformation("Server completing pipes."); await serverStream.Transport.Input.CompleteAsync().DefaultTimeout(); await serverStream.Transport.Output.CompleteAsync().DefaultTimeout(); Logger.LogInformation("Client reading until end of stream."); var data = await clientStream.ReadUntilEndAsync().DefaultTimeout(); Assert.Equal(testData.Length, data.Length); Assert.Equal(testData, data); var quicStreamContext = Assert.IsType <QuicStreamContext>(serverStream); Logger.LogInformation("Server waiting for send and receiving loops to complete."); await quicStreamContext._processingTask.DefaultTimeout(); Assert.True(quicStreamContext.CanWrite); Assert.True(quicStreamContext.CanRead); Logger.LogInformation("Server disposing stream."); await quicStreamContext.DisposeAsync().DefaultTimeout(); quicStreamContext.Dispose(); var quicConnectionContext = Assert.IsType <QuicConnectionContext>(serverConnection); Assert.Equal(1, quicConnectionContext.StreamPool.Count); }
public async Task MultipleReadsAndWrites() { using QuicListener listener = CreateQuicListener(); for (int j = 0; j < 100; j++) { Task listenTask = Task.Run(async() => { // Connection isn't being accepted, interesting. using QuicConnection connection = await listener.AcceptConnectionAsync(); await using QuicStream stream = await connection.AcceptStreamAsync(); byte[] buffer = new byte[s_data.Length]; while (true) { int bytesRead = await stream.ReadAsync(buffer); if (bytesRead == 0) { break; } Assert.Equal(s_data.Length, bytesRead); Assert.True(s_data.Span.SequenceEqual(buffer)); } for (int i = 0; i < 5; i++) { await stream.WriteAsync(s_data); } await stream.WriteAsync(Memory <byte> .Empty, endStream: true); await stream.ShutdownWriteCompleted(); await connection.CloseAsync(errorCode: 0); }); Task clientTask = Task.Run(async() => { using QuicConnection connection = CreateQuicConnection(listener.ListenEndPoint); await connection.ConnectAsync(); await using QuicStream stream = connection.OpenBidirectionalStream(); for (int i = 0; i < 5; i++) { await stream.WriteAsync(s_data); } await stream.WriteAsync(Memory <byte> .Empty, endStream: true); byte[] memory = new byte[12]; while (true) { int res = await stream.ReadAsync(memory); if (res == 0) { break; } Assert.True(s_data.Span.SequenceEqual(memory)); } await stream.ShutdownWriteCompleted(); await connection.CloseAsync(errorCode: 0); }); await(new[] { listenTask, clientTask }).WhenAllOrAnyFailed(millisecondsTimeout: 1000000); } }
public async Task StreamPool_Heartbeat_ExpiredStreamRemoved() { // Arrange var now = new DateTimeOffset(2021, 7, 6, 12, 0, 0, TimeSpan.Zero); var testSystemClock = new TestSystemClock { UtcNow = now }; await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory, testSystemClock); var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint); using var clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options); await clientConnection.ConnectAsync().DefaultTimeout(); await using var serverConnection = await connectionListener.AcceptAndAddFeatureAsync().DefaultTimeout(); var testHeartbeatFeature = new TestHeartbeatFeature(); serverConnection.Features.Set <IConnectionHeartbeatFeature>(testHeartbeatFeature); // Act & Assert var quicConnectionContext = Assert.IsType <QuicConnectionContext>(serverConnection); Assert.Equal(0, quicConnectionContext.StreamPool.Count); var stream1 = await QuicTestHelpers.CreateAndCompleteBidirectionalStreamGracefully(clientConnection, serverConnection); Assert.Equal(1, quicConnectionContext.StreamPool.Count); QuicStreamContext pooledStream = quicConnectionContext.StreamPool._array[0]; Assert.Same(stream1, pooledStream); Assert.Equal(now.Ticks + QuicConnectionContext.StreamPoolExpiryTicks, pooledStream.PoolExpirationTicks); now = now.AddMilliseconds(100); testSystemClock.UtcNow = now; testHeartbeatFeature.RaiseHeartbeat(); // Not removed. Assert.Equal(1, quicConnectionContext.StreamPool.Count); var stream2 = await QuicTestHelpers.CreateAndCompleteBidirectionalStreamGracefully(clientConnection, serverConnection); Assert.Equal(1, quicConnectionContext.StreamPool.Count); pooledStream = quicConnectionContext.StreamPool._array[0]; Assert.Same(stream1, pooledStream); Assert.Equal(now.Ticks + QuicConnectionContext.StreamPoolExpiryTicks, pooledStream.PoolExpirationTicks); Assert.Same(stream1, stream2); now = now.AddTicks(QuicConnectionContext.StreamPoolExpiryTicks); testSystemClock.UtcNow = now; testHeartbeatFeature.RaiseHeartbeat(); // Not removed. Assert.Equal(1, quicConnectionContext.StreamPool.Count); now = now.AddTicks(1); testSystemClock.UtcNow = now; testHeartbeatFeature.RaiseHeartbeat(); // Removed. Assert.Equal(0, quicConnectionContext.StreamPool.Count); }
protected override async Task <StreamPair> CreateConnectedStreamsAsync() { var listener = await QuicListener.ListenAsync(new QuicListenerOptions() { ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), ApplicationProtocols = new List <SslApplicationProtocol>() { new SslApplicationProtocol("quictest") }, ConnectionOptionsCallback = (_, _, _) => ValueTask.FromResult(new QuicServerConnectionOptions() { DefaultStreamErrorCode = QuicTestBase.DefaultStreamErrorCodeServer, DefaultCloseErrorCode = QuicTestBase.DefaultCloseErrorCodeServer, ServerAuthenticationOptions = GetSslServerAuthenticationOptions() }) }); byte[] buffer = new byte[1] { 42 }; QuicConnection connection1 = null, connection2 = null; QuicStream stream1 = null, stream2 = null; try { await WhenAllOrAnyFailed( Task.Run(async() => { connection1 = await listener.AcceptConnectionAsync(); stream1 = await connection1.AcceptInboundStreamAsync(); Assert.Equal(1, await stream1.ReadAsync(buffer)); }), Task.Run(async() => { try { connection2 = await QuicConnection.ConnectAsync(new QuicClientConnectionOptions() { DefaultStreamErrorCode = QuicTestBase.DefaultStreamErrorCodeClient, DefaultCloseErrorCode = QuicTestBase.DefaultCloseErrorCodeClient, RemoteEndPoint = listener.LocalEndPoint, ClientAuthenticationOptions = GetSslClientAuthenticationOptions() }); stream2 = await connection2.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); // OpenBidirectionalStream only allocates ID. We will force stream opening // by Writing there and receiving data on the other side. await stream2.WriteAsync(buffer); } catch (Exception ex) { _output?.WriteLine($"Failed to {ex.Message}"); throw; } })); // No need to keep the listener once we have connected connection and streams await listener.DisposeAsync(); var result = new StreamPairWithOtherDisposables(stream1, stream2); result.Disposables.Add(connection1); result.Disposables.Add(connection2); return(result); } catch { if (stream1 is not null) { await stream1.DisposeAsync(); } if (stream2 is not null) { await stream2.DisposeAsync(); } if (connection1 is not null) { await connection1.DisposeAsync(); } if (connection2 is not null) { await connection2.DisposeAsync(); } throw; } }
public async Task StreamPool_StreamAbortedOnClientAndServer_NotPooled() { // Arrange await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory); var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint); await using var clientConnection = await QuicConnection.ConnectAsync(options); await using var serverConnection = await connectionListener.AcceptAndAddFeatureAsync().DefaultTimeout(); var testHeartbeatFeature = new TestHeartbeatFeature(); serverConnection.Features.Set <IConnectionHeartbeatFeature>(testHeartbeatFeature); // Act & Assert var quicConnectionContext = Assert.IsType <QuicConnectionContext>(serverConnection); Assert.Equal(0, quicConnectionContext.StreamPool.Count); var clientStream = await clientConnection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); await clientStream.WriteAsync(TestData).DefaultTimeout(); var serverStream = await serverConnection.AcceptAsync().DefaultTimeout(); var readResult = await serverStream.Transport.Input.ReadAtLeastAsync(TestData.Length).DefaultTimeout(); serverStream.Transport.Input.AdvanceTo(readResult.Buffer.End); clientStream.Abort(QuicAbortDirection.Write, (long)Http3ErrorCode.InternalError); // Receive abort form client. var serverEx = await Assert.ThrowsAsync <ConnectionResetException>(() => serverStream.Transport.Input.ReadAsync().AsTask()).DefaultTimeout(); Assert.Equal("Stream aborted by peer (258).", serverEx.Message); Assert.Equal((long)Http3ErrorCode.InternalError, ((QuicException)serverEx.InnerException).ApplicationErrorCode.Value); serverStream.Features.Get <IProtocolErrorCodeFeature>().Error = (long)Http3ErrorCode.RequestRejected; serverStream.Abort(new ConnectionAbortedException("Test message.")); // Complete server. await serverStream.Transport.Input.CompleteAsync(); await serverStream.Transport.Output.CompleteAsync(); var buffer = new byte[1024]; var clientEx = await Assert.ThrowsAsync <QuicException>(() => clientStream.ReadAsync(buffer).AsTask()).DefaultTimeout(); Assert.Equal(QuicError.StreamAborted, clientEx.QuicError); Assert.Equal((long)Http3ErrorCode.RequestRejected, clientEx.ApplicationErrorCode.Value); var quicStreamContext = Assert.IsType <QuicStreamContext>(serverStream); // Both send and receive loops have exited. await quicStreamContext._processingTask.DefaultTimeout(); await quicStreamContext.DisposeAsync(); Assert.Equal(0, quicConnectionContext.StreamPool.Count); }
public async Task BidirectionalStream_ClientAbortedAfterDisposeCalled_NotPooled() { // Arrange using var httpEventSource = new HttpEventSourceListener(LoggerFactory); await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory); var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint); await using var clientConnection = await QuicConnection.ConnectAsync(options); await using var serverConnection = await connectionListener.AcceptAndAddFeatureAsync().DefaultTimeout(); // Act Logger.LogInformation("Client starting stream."); var clientStream = await clientConnection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); await clientStream.WriteAsync(TestData).DefaultTimeout(); var readTask = clientStream.ReadUntilEndAsync(); Logger.LogInformation("Server accepted stream."); var serverStream = await serverConnection.AcceptAsync().DefaultTimeout(); var readResult = await serverStream.Transport.Input.ReadAtLeastAsync(TestData.Length).DefaultTimeout(); serverStream.Transport.Input.AdvanceTo(readResult.Buffer.End); // Server sends a large response that will make it wait to complete sends. Logger.LogInformation("Server writing a large response."); await serverStream.Transport.Output.WriteAsync(new byte[1024 * 1024 * 32]).DefaultTimeout(); // Complete reading and writing. Logger.LogInformation("Server complete reading and writing."); await serverStream.Transport.Input.CompleteAsync(); await serverStream.Transport.Output.CompleteAsync(); Logger.LogInformation("Client wait to finish reading."); await readTask.DefaultTimeout(); var quicStreamContext = Assert.IsType <QuicStreamContext>(serverStream); // Server starts disposing Logger.LogInformation("Server starts disposing."); var disposeTask = quicStreamContext.DisposeAsync(); // Client aborts while server is draining clientStream.Abort(QuicAbortDirection.Read, (long)Http3ErrorCode.RequestCancelled); clientStream.Abort(QuicAbortDirection.Write, (long)Http3ErrorCode.RequestCancelled); // Server finishes disposing Logger.LogInformation("Wait for server finish disposing."); await disposeTask.DefaultTimeout(); quicStreamContext.Dispose(); var quicConnectionContext = Assert.IsType <QuicConnectionContext>(serverConnection); // Assert Assert.Equal(0, quicConnectionContext.StreamPool.Count); }
public async Task LargeDataSentAndReceived() { byte[] data = Enumerable.Range(0, 64 * 1024).Select(x => (byte)x).ToArray(); const int NumberOfWrites = 256; // total sent = 16M using QuicListener listener = CreateQuicListener(); for (int j = 0; j < 100; j++) { Task listenTask = Task.Run(async() => { using QuicConnection connection = await listener.AcceptConnectionAsync(); await using QuicStream stream = await connection.AcceptStreamAsync(); byte[] buffer = new byte[data.Length]; for (int i = 0; i < NumberOfWrites; i++) { int totalBytesRead = 0; while (totalBytesRead < data.Length) { int bytesRead = await stream.ReadAsync(buffer.AsMemory(totalBytesRead)); Assert.NotEqual(0, bytesRead); totalBytesRead += bytesRead; } Assert.Equal(data.Length, totalBytesRead); Assert.True(data.AsSpan().SequenceEqual(buffer)); } for (int i = 0; i < NumberOfWrites; i++) { await stream.WriteAsync(data); } await stream.WriteAsync(Memory <byte> .Empty, endStream: true); await stream.ShutdownWriteCompleted(); await connection.CloseAsync(errorCode: 0); }); Task clientTask = Task.Run(async() => { using QuicConnection connection = CreateQuicConnection(listener.ListenEndPoint); await connection.ConnectAsync(); await using QuicStream stream = connection.OpenBidirectionalStream(); byte[] buffer = new byte[data.Length]; for (int i = 0; i < NumberOfWrites; i++) { await stream.WriteAsync(data); } await stream.WriteAsync(Memory <byte> .Empty, endStream: true); for (int i = 0; i < NumberOfWrites; i++) { int totalBytesRead = 0; while (totalBytesRead < data.Length) { int bytesRead = await stream.ReadAsync(buffer.AsMemory(totalBytesRead)); Assert.NotEqual(0, bytesRead); totalBytesRead += bytesRead; } Assert.Equal(data.Length, totalBytesRead); Assert.True(data.AsSpan().SequenceEqual(buffer)); } await stream.ShutdownWriteCompleted(); await connection.CloseAsync(errorCode: 0); }); await(new[] { listenTask, clientTask }).WhenAllOrAnyFailed(millisecondsTimeout: 1000000); } }