public override async ValueTask <ConnectionContext> ConnectAsync(IFeatureCollection?features = null, CancellationToken cancellationToken = default)
    {
        QuicStream quicStream;

        var streamDirectionFeature = features?.Get <IStreamDirectionFeature>();

        if (streamDirectionFeature != null)
        {
            if (streamDirectionFeature.CanRead)
            {
                quicStream = await _connection.OpenBidirectionalStreamAsync(cancellationToken);
            }
            else
            {
                quicStream = await _connection.OpenUnidirectionalStreamAsync(cancellationToken);
            }
        }
        else
        {
            quicStream = await _connection.OpenBidirectionalStreamAsync(cancellationToken);
        }

        // Only a handful of control streams are created by the server and they last for the
        // lifetime of the connection. No value in pooling them.
        QuicStreamContext?context = new QuicStreamContext(this, _context);

        context.Initialize(quicStream);
        context.Start();

        QuicLog.ConnectedStream(_log, context);

        return(context);
    }
    public async Task ClientCertificate_Required_Sent_Populated()
    {
        // Arrange
        await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory, clientCertificateRequired : true);

        var options  = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint);
        var testCert = TestResources.GetTestCertificate();

        options.ClientAuthenticationOptions.ClientCertificates = new X509CertificateCollection {
            testCert
        };

        // Act
        using var quicConnection = new QuicConnection(options);
        await quicConnection.ConnectAsync().DefaultTimeout();

        var serverConnection = await connectionListener.AcceptAndAddFeatureAsync().DefaultTimeout();

        // Server waits for stream from client
        var serverStreamTask = serverConnection.AcceptAsync().DefaultTimeout();

        // Client creates stream
        using var clientStream = await quicConnection.OpenBidirectionalStreamAsync();

        await clientStream.WriteAsync(TestData).DefaultTimeout();

        // Server finishes accepting
        var serverStream = await serverStreamTask.DefaultTimeout();

        // Assert
        AssertTlsConnectionFeature(serverConnection.Features, testCert);
        AssertTlsConnectionFeature(serverStream.Features, testCert);
Example #3
0
        private static async Task <QuicStream> OpenAndUseStreamAsync(QuicConnection c)
        {
            QuicStream s = await c.OpenBidirectionalStreamAsync();

            // This will pend
            await s.ReadAsync(new byte[1]);

            return(s);
        }
Example #4
0
    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.AcceptAndAddFeatureAsync().DefaultTimeout();

        // Act
        var acceptTask = serverConnection.AcceptAsync();

        await using var clientStream = await quicConnection.OpenBidirectionalStreamAsync();

        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 = await clientStream.ReadAtLeastLengthAsync(TestData.Length).DefaultTimeout();

        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 serverStream.Transport.Output.CompleteAsync();

        await closedTcs.Task.DefaultTimeout();
    }
Example #5
0
        protected override async Task <StreamPair> CreateConnectedStreamsAsync()
        {
            var listener = await QuicListener.ListenAsync(new QuicListenerOptions()
            {
                ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0),
                ServerAuthenticationOptions = GetSslServerAuthenticationOptions()
            });

            byte[] buffer = new byte[1] {
                42
            };
            QuicConnection connection1 = null, connection2 = null;
            QuicStream     stream1 = null, stream2 = null;

            await WhenAllOrAnyFailed(
                Task.Run(async() =>
            {
                connection1 = await listener.AcceptConnectionAsync();
                stream1     = await connection1.AcceptStreamAsync();
                Assert.Equal(1, await stream1.ReadAsync(buffer));
            }),
                Task.Run(async() =>
            {
                try
                {
                    connection2 = await QuicConnection.ConnectAsync(new QuicClientConnectionOptions()
                    {
                        RemoteEndPoint = listener.ListenEndPoint,
                        ClientAuthenticationOptions = GetSslClientAuthenticationOptions()
                    });
                    await connection2.ConnectAsync();
                    stream2 = await connection2.OpenBidirectionalStreamAsync();
                    // 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
            listener.Dispose();

            var result = new StreamPairWithOtherDisposables(stream1, stream2);

            result.Disposables.Add(connection1);
            result.Disposables.Add(connection2);

            return(result);
        }
Example #6
0
    public async Task StreamPool_StreamAbortedOnClient_NotPooled()
    {
        // Arrange
        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();

        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.OpenBidirectionalStreamAsync();

        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.AbortWrite((long)Http3ErrorCode.InternalError);

        // Receive abort form client.
        var ex = await Assert.ThrowsAsync <ConnectionResetException>(() => serverStream.Transport.Input.ReadAsync().AsTask()).DefaultTimeout();

        Assert.Equal("Stream aborted by peer (258).", ex.Message);
        Assert.Equal((long)Http3ErrorCode.InternalError, ((QuicStreamAbortedException)ex.InnerException).ErrorCode);

        // Complete reading and then abort.
        await serverStream.Transport.Input.CompleteAsync();

        await serverStream.Transport.Output.CompleteAsync();

        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 StreamAbortFeature_AbortWrite_ClientReceivesAbort()
    {
        // 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();

        await using var serverConnection = await connectionListener.AcceptAndAddFeatureAsync().DefaultTimeout();

        // Act
        await using var clientStream = await quicConnection.OpenBidirectionalStreamAsync();

        await clientStream.WriteAsync(TestData).DefaultTimeout();

        await using var serverStream = await serverConnection.AcceptAsync().DefaultTimeout();

        var readResult = await serverStream.Transport.Input.ReadAtLeastAsync(TestData.Length).DefaultTimeout();

        serverStream.Transport.Input.AdvanceTo(readResult.Buffer.End);

        var serverReadTask = serverStream.Transport.Input.ReadAtLeastAsync(TestData.Length).AsTask();

        var streamAbortFeature = serverStream.Features.Get <IStreamAbortFeature>();

        streamAbortFeature.AbortRead((long)Http3ErrorCode.InternalError, new ConnectionAbortedException("Test reason"));

        // Assert

        // Server writes data
        await serverStream.Transport.Output.WriteAsync(TestData).DefaultTimeout();

        // Server completes its output.
        await serverStream.Transport.Output.CompleteAsync().DefaultTimeout();

        // Client successfully reads data to end
        var data = await clientStream.ReadUntilEndAsync().DefaultTimeout();

        Assert.Equal(TestData, data);

        // Client errors when writing
        var clientEx = await Assert.ThrowsAsync <QuicStreamAbortedException>(() => clientStream.WriteAsync(data).AsTask()).DefaultTimeout();

        Assert.Equal((long)Http3ErrorCode.InternalError, clientEx.ErrorCode);

        // Server errors when reading
        var serverEx = await Assert.ThrowsAsync <ConnectionAbortedException>(() => serverReadTask).DefaultTimeout();

        Assert.Equal("Test reason", serverEx.Message);
    }
Example #8
0
    public async Task StreamPool_StreamAbortedOnServerAfterComplete_NotPooled()
    {
        // Arrange
        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();

        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.OpenBidirectionalStreamAsync();

        await clientStream.WriteAsync(TestData, endStream : true).DefaultTimeout();

        var serverStream = await serverConnection.AcceptAsync().DefaultTimeout();

        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();

        Assert.True(readResult.IsCompleted);

        // Complete reading and writing.
        await serverStream.Transport.Input.CompleteAsync();

        await serverStream.Transport.Output.CompleteAsync();

        var quicStreamContext = Assert.IsType <QuicStreamContext>(serverStream);

        // Both send and receive loops have exited.
        await quicStreamContext._processingTask.DefaultTimeout();

        serverStream.Abort(new ConnectionAbortedException("Test message"));

        await quicStreamContext.DisposeAsync();

        Assert.Equal(0, quicConnectionContext.StreamPool.Count);
    }
    public async Task BidirectionalStream_ReadAborted_NotPooled()
    {
        // Arrange
        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
        var clientStream = await clientConnection.OpenBidirectionalStreamAsync();

        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);

        await clientStream.WriteAsync(TestData).DefaultTimeout();

        // Complete writing.
        await serverStream.Transport.Output.CompleteAsync();

        // Abort read-side of the stream and then complete pipe.
        // This simulates what Kestrel does when a request finishes without
        // reading the request body to the end.
        serverStream.Features.Get <IStreamAbortFeature>().AbortRead((long)Http3ErrorCode.NoError, new ConnectionAbortedException("Test message."));
        await serverStream.Transport.Input.CompleteAsync();

        var quicStreamContext = Assert.IsType <QuicStreamContext>(serverStream);

        // Both send and receive loops have exited.
        await quicStreamContext._processingTask.DefaultTimeout();

        Assert.True(quicStreamContext.CanWrite);
        Assert.True(quicStreamContext.CanRead);

        await quicStreamContext.DisposeAsync();

        quicStreamContext.Dispose();

        var quicConnectionContext = Assert.IsType <QuicConnectionContext>(serverConnection);

        // Assert
        Assert.Equal(0, quicConnectionContext.StreamPool.Count);
    }
    public async Task BidirectionalStream_ClientAbortedAfterDisposeCalled_NotPooled()
    {
        // Arrange
        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
        var clientStream = await clientConnection.OpenBidirectionalStreamAsync();

        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);

        // Server sends a large response that will make it wait to complete sends.
        await serverStream.Transport.Output.WriteAsync(new byte[1024 * 1024 * 32]).DefaultTimeout();

        // Complete reading and writing.
        await serverStream.Transport.Input.CompleteAsync();

        await serverStream.Transport.Output.CompleteAsync();

        var quicStreamContext = Assert.IsType <QuicStreamContext>(serverStream);

        // Server starts disposing
        var disposeTask = quicStreamContext.DisposeAsync();

        // Client aborts while server is draining
        clientStream.AbortRead((long)Http3ErrorCode.RequestCancelled);
        clientStream.AbortWrite((long)Http3ErrorCode.RequestCancelled);

        // Server finishes disposing
        await disposeTask.DefaultTimeout();

        quicStreamContext.Dispose();

        var quicConnectionContext = Assert.IsType <QuicConnectionContext>(serverConnection);

        // Assert
        Assert.Equal(0, quicConnectionContext.StreamPool.Count);
    }
    public async Task BidirectionalStream_ClientAbortWrite_ServerReceivesAbort()
    {
        // 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();

        await using var serverConnection = await connectionListener.AcceptAndAddFeatureAsync().DefaultTimeout();

        // Act
        await using var clientStream = await quicConnection.OpenBidirectionalStreamAsync();

        await clientStream.WriteAsync(TestData).DefaultTimeout();

        await using var serverStream = await serverConnection.AcceptAsync().DefaultTimeout();

        var readResult = await serverStream.Transport.Input.ReadAtLeastAsync(TestData.Length).DefaultTimeout();

        serverStream.Transport.Input.AdvanceTo(readResult.Buffer.End);

        var closedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);

        serverStream.ConnectionClosed.Register(() => closedTcs.SetResult());

        clientStream.AbortWrite((long)Http3ErrorCode.InternalError);

        // Receive abort from client.
        var ex = await Assert.ThrowsAsync <ConnectionResetException>(() => serverStream.Transport.Input.ReadAsync().AsTask()).DefaultTimeout();

        // Server completes its output.
        await serverStream.Transport.Output.CompleteAsync();

        // Assert
        Assert.Equal((long)Http3ErrorCode.InternalError, ((QuicStreamAbortedException)ex.InnerException).ErrorCode);

        var quicStreamContext = Assert.IsType <QuicStreamContext>(serverStream);

        Assert.Equal((long)Http3ErrorCode.InternalError, quicStreamContext.Error);

        // Both send and receive loops have exited.
        await quicStreamContext._processingTask.DefaultTimeout();

        await closedTcs.Task.DefaultTimeout();
    }
Example #12
0
    public async Task AcceptAsync_CancellationThenAccept_AcceptStreamAfterCancellation()
    {
        // Arrange
        var connectionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);

        await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory);

        // Act
        var acceptTask = connectionListener.AcceptAndAddFeatureAsync().DefaultTimeout();

        var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint);

        using var clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options);
        await clientConnection.ConnectAsync().DefaultTimeout();

        await using var serverConnection = await acceptTask.DefaultTimeout();

        // Wait for stream and then cancel
        var cts = new CancellationTokenSource();
        var acceptStreamTask = serverConnection.AcceptAsync(cts.Token);

        cts.Cancel();

        var serverStream = await acceptStreamTask.DefaultTimeout();

        Assert.Null(serverStream);

        // Wait for stream after cancellation
        acceptStreamTask = serverConnection.AcceptAsync();

        await using var clientStream = await clientConnection.OpenBidirectionalStreamAsync();

        await clientStream.WriteAsync(TestData);

        // Assert
        serverStream = await acceptStreamTask.DefaultTimeout();

        Assert.NotNull(serverStream);

        var read = await serverStream.Transport.Input.ReadAtLeastAsync(TestData.Length).DefaultTimeout();

        Assert.Equal(TestData, read.Buffer.ToArray());
        serverStream.Transport.Input.AdvanceTo(read.Buffer.End);
    }
Example #13
0
    public static async Task <QuicStreamContext> CreateAndCompleteBidirectionalStreamGracefully(QuicConnection clientConnection, MultiplexedConnectionContext serverConnection)
    {
        var clientStream = await clientConnection.OpenBidirectionalStreamAsync();

        await clientStream.WriteAsync(TestData, endStream : true).DefaultTimeout();

        var serverStream = await serverConnection.AcceptAsync().DefaultTimeout();

        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();

        Assert.True(readResult.IsCompleted);

        // Complete reading and writing.
        await serverStream.Transport.Input.CompleteAsync();

        await serverStream.Transport.Output.CompleteAsync();

        var quicStreamContext = Assert.IsType <QuicStreamContext>(serverStream);

        // Both send and receive loops have exited.
        await quicStreamContext._processingTask.DefaultTimeout();

        Assert.True(quicStreamContext.CanWrite);
        Assert.True(quicStreamContext.CanRead);

        await quicStreamContext.DisposeAsync();

        quicStreamContext.Dispose();

        return(quicStreamContext);
    }
Example #14
0
 public async ValueTask <Http3LoopbackStream> OpenBidirectionalStreamAsync()
 {
     return(new Http3LoopbackStream(await _connection.OpenBidirectionalStreamAsync()));
 }
    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);
    }