Beispiel #1
0
        private Task EnsureControlStreamAcceptedAsync()
        {
            if (_inboundControlStream != null)
            {
                return(Task.CompletedTask);
            }

            return(EnsureControlStreamAcceptedInternalAsync());

            async Task EnsureControlStreamAcceptedInternalAsync()
            {
                Http3LoopbackStream controlStream;

                while (true)
                {
                    QuicStream quicStream = await _connection.AcceptInboundStreamAsync().ConfigureAwait(false);

                    if (!quicStream.CanWrite)
                    {
                        // control stream accepted
                        controlStream = new Http3LoopbackStream(quicStream);
                        break;
                    }

                    // control streams are unidirectional, so this must be a request stream
                    // keep it for later and wait for another stream
                    _delayedStreams.Enqueue(quicStream);
                }

                long?streamType = await controlStream.ReadIntegerAsync();

                Assert.Equal(Http3LoopbackStream.ControlStream, streamType);

                List <(long settingId, long settingValue)> settings = await controlStream.ReadSettingsAsync();

                (long settingId, long settingValue) = Assert.Single(settings);

                Assert.Equal(Http3LoopbackStream.MaxHeaderListSize, settingId);
                MaxHeaderListSize = settingValue;

                _inboundControlStream = controlStream;
            }
        }
Beispiel #2
0
    public override async ValueTask <ConnectionContext?> AcceptAsync(CancellationToken cancellationToken = default)
    {
        try
        {
            var stream = await _connection.AcceptInboundStreamAsync(cancellationToken);

            QuicStreamContext?context = null;

            // Only use pool for bidirectional streams. Just a handful of unidirecitonal
            // streams are created for a connection and they live for the lifetime of the connection.
            if (stream.CanRead && stream.CanWrite)
            {
                lock (_poolLock)
                {
                    StreamPool.TryPop(out context);
                }
            }

            if (context == null)
            {
                context = new QuicStreamContext(this, _context);
                context.Initialize(stream);
            }
            else
            {
                context.ResetFeatureCollection();
                context.ResetItems();
                context.Initialize(stream);

                QuicLog.StreamReused(_log, context);
            }

            context.Start();

            QuicLog.AcceptedStream(_log, context);

            return(context);
        }
        catch (QuicException ex) when(ex.QuicError == QuicError.ConnectionAborted)
        {
            // Shutdown initiated by peer, abortive.
            _error = ex.ApplicationErrorCode;
            QuicLog.ConnectionAborted(_log, this, ex.ApplicationErrorCode.GetValueOrDefault(), ex);

            ThreadPool.UnsafeQueueUserWorkItem(state =>
            {
                state.CancelConnectionClosedToken();
            },
                                               this,
                                               preferLocal: false);

            // Throw error so consumer sees the connection is aborted by peer.
            throw new ConnectionResetException(ex.Message, ex);
        }
        catch (QuicException ex) when(ex.QuicError == QuicError.OperationAborted)
        {
            lock (_shutdownLock)
            {
                // This error should only happen when shutdown has been initiated by the server.
                // If there is no abort reason and we have this error then the connection is in an
                // unexpected state. Abort connection and throw reason error.
                if (_abortReason == null)
                {
                    Abort(new ConnectionAbortedException("Unexpected error when accepting stream.", ex));
                }

                _abortReason !.Throw();
            }
        }
        catch (OperationCanceledException)
        {
            Debug.Assert(cancellationToken.IsCancellationRequested, "Error requires cancellation is requested.");

            lock (_shutdownLock)
            {
                // Connection has been aborted. Throw reason exception.
                _abortReason?.Throw();
            }
        }
        catch (Exception ex)
        {
            Debug.Fail($"Unexpected exception in {nameof(QuicConnectionContext)}.{nameof(AcceptAsync)}: {ex}");
            throw;
        }

        // Return null for graceful closure or cancellation.
        return(null);
    }
        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;
            }
        }