< Summary

Information
Class: System.Net.Http.Http3Connection
Assembly: System.Net.Http
File(s): D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\Http3Connection.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 537
Coverable lines: 537
Total lines: 992
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 198
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\Http3Connection.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6using System.Globalization;
 7using System.IO;
 8using System.Net.Http.Headers;
 9using System.Net.Http.Metrics;
 10using System.Net.Quic;
 11using System.Runtime.CompilerServices;
 12using System.Runtime.Versioning;
 13using System.Threading;
 14using System.Threading.Tasks;
 15
 16namespace System.Net.Http
 17{
 18    [SupportedOSPlatform("linux")]
 19    [SupportedOSPlatform("macos")]
 20    [SupportedOSPlatform("windows")]
 21    internal sealed class Http3Connection : HttpConnectionBase
 22    {
 23        private readonly HttpAuthority _authority;
 24        private readonly byte[]? _altUsedEncodedHeader;
 25        private QuicConnection? _connection;
 26        private Task? _connectionClosedTask;
 27
 28        // Keep a collection of requests around so we can process GOAWAY.
 029        private readonly Dictionary<QuicStream, Http3RequestStream> _activeRequests = new Dictionary<QuicStream, Http3Re
 30
 31        // Set when GOAWAY is being processed, when aborting, or when disposing.
 032        private long _firstRejectedStreamId = -1;
 33
 34        // Our control stream.
 35        private QuicStream? _clientControl;
 36        private Task? _sendSettingsTask;
 37
 38        // Server-advertised SETTINGS_MAX_FIELD_SECTION_SIZE
 39        // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4.1-2.2.1
 040        private uint _maxHeaderListSize = uint.MaxValue; // Defaults to infinite
 41
 42        // Once the server's streams are received, these are set to true. Further receipt of these streams results in a 
 43        private bool _haveServerControlStream;
 44        private bool _haveServerQpackDecodeStream;
 45        private bool _haveServerQpackEncodeStream;
 46
 47        // A connection-level error will abort any future operations.
 48        private Exception? _abortException;
 49
 050        public HttpAuthority Authority => _authority;
 051        public HttpConnectionPool Pool => _pool;
 052        public uint MaxHeaderListSize => _maxHeaderListSize;
 053        public byte[]? AltUsedEncodedHeaderBytes => _altUsedEncodedHeader;
 054        public Exception? AbortException => Volatile.Read(ref _abortException);
 055        private object SyncObj => _activeRequests;
 56
 57        private int _availableRequestStreamsCount;
 58        private TaskCompletionSource<bool>? _availableStreamsWaiter;
 59
 60        /// <summary>
 61        /// If true, we've received GOAWAY, are aborting due to a connection-level error, or are disposing due to pool l
 62        /// </summary>
 63        private bool ShuttingDown
 64        {
 65            get
 066            {
 067                Debug.Assert(Monitor.IsEntered(SyncObj));
 068                return _firstRejectedStreamId != -1;
 069            }
 70        }
 71
 72        public Http3Connection(HttpConnectionPool pool, HttpAuthority authority, bool includeAltUsedHeader)
 073            : base(pool)
 074        {
 075            _authority = authority;
 76
 077            if (includeAltUsedHeader)
 078            {
 079                bool altUsedDefaultPort = pool.Kind == HttpConnectionKind.Http && authority.Port == HttpConnectionPool.D
 080                string altUsedValue = altUsedDefaultPort ? authority.IdnHost : string.Create(CultureInfo.InvariantCultur
 081                _altUsedEncodedHeader = QPack.QPackEncoder.EncodeLiteralHeaderFieldWithoutNameReferenceToArray(KnownHead
 082            }
 83
 084            uint maxHeaderListSize = _pool._lastSeenHttp3MaxHeaderListSize;
 085            if (maxHeaderListSize > 0)
 086            {
 87                // Previous connections to the same host advertised a limit.
 88                // Use this as an initial value before we receive the SETTINGS frame.
 089                _maxHeaderListSize = maxHeaderListSize;
 090            }
 091        }
 92
 93        public void InitQuicConnection(QuicConnection connection, Activity? connectionSetupActivity)
 094        {
 095            MarkConnectionAsEstablished(connectionSetupActivity: connectionSetupActivity, remoteEndPoint: connection.Rem
 96
 097            _connection = connection;
 98
 99            // Avoid capturing the initial request's ExecutionContext for the entire lifetime of the new connection.
 0100            using (ExecutionContext.SuppressFlow())
 0101            {
 102                // Errors are observed via Abort().
 0103                _sendSettingsTask = SendSettingsAsync();
 104
 105                // This process is cleaned up when _connection is disposed, and errors are observed via Abort().
 0106                _ = AcceptStreamsAsync();
 0107            }
 0108        }
 109
 110        /// <summary>
 111        /// Starts shutting down the <see cref="Http3Connection"/>. Final cleanup will happen when there are no more act
 112        /// </summary>
 113        public override void Dispose()
 0114        {
 0115            lock (SyncObj)
 0116            {
 0117                if (_firstRejectedStreamId == -1)
 0118                {
 0119                    _firstRejectedStreamId = long.MaxValue;
 0120                    CheckForShutdown();
 0121                }
 0122            }
 0123        }
 124
 125        /// <summary>
 126        /// Called when shutting down, this checks for when shutdown is complete (no more active requests) and does actu
 127        /// </summary>
 128        /// <remarks>Requires <see cref="SyncObj"/> to be locked.</remarks>
 129        private void CheckForShutdown()
 0130        {
 0131            Debug.Assert(Monitor.IsEntered(SyncObj));
 0132            Debug.Assert(ShuttingDown);
 133
 0134            if (_activeRequests.Count != 0)
 0135            {
 0136                return;
 137            }
 138
 0139            if (_connection != null)
 0140            {
 141                // Close the QuicConnection in the background.
 142
 0143                _availableStreamsWaiter?.SetResult(false);
 0144                _availableStreamsWaiter = null;
 145
 0146                _connectionClosedTask ??= _connection.CloseAsync((long)Http3ErrorCode.NoError).AsTask();
 147
 0148                QuicConnection connection = _connection;
 0149                _connection = null;
 150
 0151                _ = _connectionClosedTask.ContinueWith(async closeTask =>
 0152                {
 0153                    if (closeTask.IsFaulted && NetEventSource.Log.IsEnabled())
 0154                    {
 0155                        Trace($"{nameof(QuicConnection)} failed to close: {closeTask.Exception!.InnerException}");
 0156                    }
 0157
 0158                    try
 0159                    {
 0160                        await connection.DisposeAsync().ConfigureAwait(false);
 0161                    }
 0162                    catch (Exception ex)
 0163                    {
 0164                        Trace($"{nameof(QuicConnection)} failed to dispose: {ex}");
 0165                    }
 0166
 0167                    if (_clientControl != null)
 0168                    {
 0169                        await _sendSettingsTask!.ConfigureAwait(false);
 0170                        await _clientControl.DisposeAsync().ConfigureAwait(false);
 0171                        _clientControl = null;
 0172                    }
 0173
 0174                }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
 175
 0176                MarkConnectionAsClosed();
 0177            }
 0178        }
 179
 180        /// <summary>
 181        /// When EnableMultipleHttp3Connections is false: always reserve a stream, return a bool indicating if the strea
 182        /// When EnableMultipleHttp3Connections is true: reserve a stream only if it's available meaning that the return
 183        /// </summary>
 184        public bool TryReserveStream()
 0185        {
 0186            bool singleConnection = !_pool.Settings.EnableMultipleHttp3Connections;
 187
 0188            lock (SyncObj)
 0189            {
 190                // For the single connection case, we allow the counter to go below zero.
 0191                Debug.Assert(singleConnection || _availableRequestStreamsCount >= 0);
 192
 0193                if (NetEventSource.Log.IsEnabled()) Trace($"_availableRequestStreamsCount = {_availableRequestStreamsCou
 194
 0195                bool streamAvailable = _availableRequestStreamsCount > 0;
 196
 197                // Do not let the counter to go below zero when EnableMultipleHttp3Connections is true.
 198                // This equivalent to an immediate ReleaseStream() for the case no stream is immediately available.
 0199                if (singleConnection || _availableRequestStreamsCount > 0)
 0200                {
 0201                    --_availableRequestStreamsCount;
 0202                }
 203
 0204                return streamAvailable;
 205            }
 0206        }
 207
 208        public void ReleaseStream()
 0209        {
 0210            lock (SyncObj)
 0211            {
 0212                Debug.Assert(!_pool.Settings.EnableMultipleHttp3Connections || _availableRequestStreamsCount >= 0);
 213
 0214                if (NetEventSource.Log.IsEnabled()) Trace($"_availableRequestStreamsCount = {_availableRequestStreamsCou
 0215                ++_availableRequestStreamsCount;
 216
 0217                _availableStreamsWaiter?.SetResult(!ShuttingDown);
 0218                _availableStreamsWaiter = null;
 0219            }
 0220        }
 221
 222        public void StreamCapacityCallback(QuicConnection connection, QuicStreamCapacityChangedArgs args)
 0223        {
 0224            Debug.Assert(_connection is null || connection == _connection);
 225
 0226            lock (SyncObj)
 0227            {
 0228                Debug.Assert(_availableStreamsWaiter is null || _availableRequestStreamsCount >= 0);
 229
 0230                if (NetEventSource.Log.IsEnabled()) Trace($"_availableRequestStreamsCount = {_availableRequestStreamsCou
 231
 232                // Since _availableStreamsWaiter is only used in the multi-connection case, when _availableRequestStream
 233                // we don't need to check the value of _availableRequestStreamsCount here.
 0234                _availableRequestStreamsCount += args.BidirectionalIncrement;
 0235                _availableStreamsWaiter?.SetResult(!ShuttingDown);
 0236                _availableStreamsWaiter = null;
 0237            }
 0238        }
 239
 240        public Task<bool> WaitForAvailableStreamsAsync()
 0241        {
 242            // In the single connection case, _availableStreamsWaiter notifications do not guarantee that _availableRequ
 0243            Debug.Assert(_pool.Settings.EnableMultipleHttp3Connections, "Calling WaitForAvailableStreamsAsync() is inval
 244
 0245            lock (SyncObj)
 0246            {
 0247                Debug.Assert(_availableRequestStreamsCount >= 0);
 248
 0249                if (ShuttingDown)
 0250                {
 0251                    return Task.FromResult(false);
 252                }
 0253                if (_availableRequestStreamsCount > 0)
 0254                {
 0255                    return Task.FromResult(true);
 256                }
 257
 0258                Debug.Assert(_availableStreamsWaiter is null);
 0259                _availableStreamsWaiter = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronou
 0260                return _availableStreamsWaiter.Task;
 261            }
 0262        }
 263
 264        public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, WaitForHttp3ConnectionActivity wait
 0265        {
 266            // Allocate an active request
 0267            QuicStream? quicStream = null;
 0268            Http3RequestStream? requestStream = null;
 269
 270            try
 0271            {
 0272                Exception? exception = null;
 273                try
 0274                {
 0275                    QuicConnection? conn = _connection;
 0276                    if (conn != null)
 0277                    {
 278                        // We found a connection in the pool, but it did not have available streams, OpenOutboundStreamA
 0279                        if (!waitForConnectionActivity.Started && !streamAvailable)
 0280                        {
 0281                            waitForConnectionActivity.Start();
 0282                        }
 283
 0284                        quicStream = await conn.OpenOutboundStreamAsync(QuicStreamType.Bidirectional, cancellationToken)
 285
 0286                        requestStream = new Http3RequestStream(request, this, quicStream);
 0287                        lock (SyncObj)
 0288                        {
 0289                            if (_activeRequests.Count == 0)
 0290                            {
 0291                                MarkConnectionAsNotIdle();
 0292                            }
 0293                            _activeRequests.Add(quicStream, requestStream);
 0294                        }
 0295                    }
 0296                }
 297                // Swallow any exceptions caused by the connection being closed locally or even disposed due to a race.
 298                // Since quicStream will stay `null`, the code below will throw appropriate exception to retry the reque
 0299                catch (ObjectDisposedException e)
 0300                {
 0301                    exception = e;
 0302                }
 0303                catch (QuicException e) when (e.QuicError != QuicError.OperationAborted)
 0304                {
 0305                    exception = e;
 0306                }
 307                finally
 0308                {
 0309                    waitForConnectionActivity.Stop(request, Pool, exception);
 0310                }
 311
 0312                if (quicStream == null)
 0313                {
 0314                    throw new HttpRequestException(HttpRequestError.Unknown, SR.net_http_request_aborted, null, RequestR
 315                }
 316
 0317                requestStream!.StreamId = quicStream.Id;
 318
 319                bool goAway;
 0320                lock (SyncObj)
 0321                {
 0322                    goAway = _firstRejectedStreamId != -1 && requestStream.StreamId >= _firstRejectedStreamId;
 0323                }
 324
 0325                if (goAway)
 0326                {
 0327                    throw new HttpRequestException(HttpRequestError.Unknown, SR.net_http_request_aborted, null, RequestR
 328                }
 329
 0330                waitForConnectionActivity.AssertActivityNotRunning();
 0331                if (ConnectionSetupActivity is not null) ConnectionSetupDistributedTracing.AddConnectionLinkToRequestAct
 0332                if (NetEventSource.Log.IsEnabled()) Trace($"Sending request: {request}");
 333
 0334                Task<HttpResponseMessage> responseTask = requestStream.SendAsync(cancellationToken);
 335
 336                // null out requestStream to avoid disposing in finally block. It is now in charge of disposing itself.
 0337                requestStream = null;
 338
 0339                return await responseTask.ConfigureAwait(false);
 340            }
 0341            catch (QuicException ex) when (ex.QuicError == QuicError.OperationAborted)
 0342            {
 343                // This will happen if we aborted _connection somewhere and we have pending OpenOutboundStreamAsync call
 344                // note that _abortException may be null if we closed the connection in response to a GOAWAY frame
 0345                throw new HttpRequestException(HttpRequestError.Unknown, SR.net_http_client_execution_error, _abortExcep
 346            }
 347            finally
 0348            {
 0349                if (requestStream is not null)
 0350                {
 0351                    await requestStream.DisposeAsync().ConfigureAwait(false);
 0352                }
 0353            }
 0354        }
 355
 356        /// <summary>
 357        /// Aborts the connection with an error.
 358        /// </summary>
 359        /// <remarks>
 360        /// Used for e.g. I/O or connection-level frame parsing errors.
 361        /// </remarks>
 362        internal Exception Abort(Exception abortException)
 0363        {
 364            // Only observe the first exception we get.
 0365            Exception? firstException = Interlocked.CompareExchange(ref _abortException, abortException, null);
 366
 0367            if (firstException != null)
 0368            {
 0369                if (NetEventSource.Log.IsEnabled() && !ReferenceEquals(firstException, abortException))
 0370                {
 371                    // Lost the race to set the field to another exception, so just trace this one.
 0372                    Trace($"{nameof(abortException)}=={abortException}");
 0373                }
 374
 0375                return firstException;
 376            }
 377
 378            // Stop sending requests to this connection.
 379            // Do not dispose the connection when invalidating as the rest of this method does exactly that:
 380            //   set up _firstRejectedStreamId, close the connection with proper error code and CheckForShutdown.
 0381            _pool.InvalidateHttp3Connection(this, dispose: false);
 382
 0383            long connectionResetErrorCode = (abortException as HttpProtocolException)?.ErrorCode ?? (long)Http3ErrorCode
 384
 0385            lock (SyncObj)
 0386            {
 387                // Set _firstRejectedStreamId != -1 to make ShuttingDown = true.
 388                // It's possible GOAWAY is already being processed, in which case this would already be != -1.
 0389                if (_firstRejectedStreamId == -1)
 0390                {
 0391                    _firstRejectedStreamId = long.MaxValue;
 0392                }
 393
 394                // Abort the connection. This will cause all of our streams to abort on their next I/O.
 0395                if (_connection != null && _connectionClosedTask == null)
 0396                {
 0397                    _connectionClosedTask = _connection.CloseAsync((long)connectionResetErrorCode).AsTask();
 0398                }
 399
 0400                CheckForShutdown();
 0401            }
 402
 0403            return abortException;
 0404        }
 405
 406        private void OnServerGoAway(long firstRejectedStreamId)
 0407        {
 0408            if (NetEventSource.Log.IsEnabled())
 0409            {
 0410                Trace($"GOAWAY received. First rejected stream ID = {firstRejectedStreamId}");
 0411            }
 412
 413            // Stop sending requests to this connection.
 414            // Do not dispose the connection when invalidating as the rest of this method does exactly that:
 415            //   set up _firstRejectedStreamId to the stream id from GO_AWAY frame and CheckForShutdown.
 0416            _pool.InvalidateHttp3Connection(this, dispose: false);
 417
 0418            var streamsToGoAway = new List<Http3RequestStream>();
 419
 0420            lock (SyncObj)
 0421            {
 0422                if (_firstRejectedStreamId != -1 && firstRejectedStreamId > _firstRejectedStreamId)
 0423                {
 424                    // Server can send multiple GOAWAY frames.
 425                    // Spec says a server MUST NOT increase the stream ID in subsequent GOAWAYs,
 426                    // but doesn't specify what client should do if that is violated. Ignore for now.
 0427                    if (NetEventSource.Log.IsEnabled())
 0428                    {
 0429                        Trace("HTTP/3 server sent GOAWAY with increasing stream ID. Retried requests may have been doubl
 0430                    }
 0431                    return;
 432                }
 433
 0434                _firstRejectedStreamId = firstRejectedStreamId;
 435
 0436                foreach (KeyValuePair<QuicStream, Http3RequestStream> request in _activeRequests)
 0437                {
 0438                    if (request.Value.StreamId >= firstRejectedStreamId)
 0439                    {
 0440                        streamsToGoAway.Add(request.Value);
 0441                    }
 0442                }
 443
 0444                CheckForShutdown();
 0445            }
 446
 447            // GOAWAY each stream outside of the lock, so they can acquire the lock to remove themselves from _activeReq
 0448            foreach (Http3RequestStream stream in streamsToGoAway)
 0449            {
 0450                stream.GoAway();
 0451            }
 0452        }
 453
 454        public void RemoveStream(QuicStream stream)
 0455        {
 0456            lock (SyncObj)
 0457            {
 0458                if (_activeRequests.Remove(stream))
 0459                {
 0460                    if (ShuttingDown)
 0461                    {
 0462                        CheckForShutdown();
 0463                    }
 464
 0465                    if (_activeRequests.Count == 0)
 0466                    {
 0467                        MarkConnectionAsIdle();
 0468                    }
 0469                }
 0470            }
 0471        }
 472
 473        public override void Trace(string message, [CallerMemberName] string? memberName = null) =>
 0474            Trace(0, _connection is not null ? $"{_connection} {message}" : message, memberName);
 475
 476        internal void Trace(long streamId, string message, [CallerMemberName] string? memberName = null) =>
 0477            NetEventSource.Log.HandlerMessage(
 0478                _pool?.GetHashCode() ?? 0,    // pool ID
 0479                GetHashCode(),                // connection ID
 0480                (int)streamId,                // stream ID
 0481                memberName,                   // method name
 0482                message);                     // message
 483
 484        private async Task SendSettingsAsync()
 0485        {
 486            try
 0487            {
 0488                _clientControl = await _connection!.OpenOutboundStreamAsync(QuicStreamType.Unidirectional).ConfigureAwai
 489
 490                // Server MUST NOT abort our control stream, setup a continuation which will react accordingly
 0491                _ = _clientControl.WritesClosed.ContinueWith(t =>
 0492                {
 0493                    if (t.Exception?.InnerException is QuicException ex && ex.QuicError == QuicError.StreamAborted)
 0494                    {
 0495                        Abort(HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ClosedCriticalStream))
 0496                    }
 0497                }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Current);
 498
 0499                await _clientControl.WriteAsync(_pool.Settings.Http3SettingsFrame, CancellationToken.None).ConfigureAwai
 0500            }
 0501            catch (QuicException ex) when (ex.QuicError == QuicError.ConnectionAborted)
 0502            {
 0503                Debug.Assert(ex.ApplicationErrorCode.HasValue);
 0504                Http3ErrorCode code = (Http3ErrorCode)ex.ApplicationErrorCode.Value;
 505
 0506                Abort(HttpProtocolException.CreateHttp3ConnectionException(code, SR.net_http_http3_connection_close));
 0507            }
 0508            catch (Exception ex)
 0509            {
 0510                Abort(ex);
 0511            }
 0512        }
 513
 514        public static byte[] BuildSettingsFrame(HttpConnectionSettings settings)
 0515        {
 0516            Span<byte> buffer = stackalloc byte[4 + VariableLengthIntegerHelper.MaximumEncodedLength];
 517
 0518            int integerLength = VariableLengthIntegerHelper.WriteInteger(buffer.Slice(4), settings.MaxResponseHeadersByt
 0519            int payloadLength = 1 + integerLength; // includes the setting ID and the integer value.
 0520            Debug.Assert(payloadLength <= VariableLengthIntegerHelper.OneByteLimit);
 521
 0522            buffer[0] = (byte)Http3StreamType.Control;
 0523            buffer[1] = (byte)Http3FrameType.Settings;
 0524            buffer[2] = (byte)payloadLength;
 0525            buffer[3] = (byte)Http3SettingType.MaxHeaderListSize;
 526
 0527            return buffer.Slice(0, 4 + integerLength).ToArray();
 0528        }
 529
 530        /// <summary>
 531        /// Accepts unidirectional streams (control, QPack, ...) from the server.
 532        /// </summary>
 533        private async Task AcceptStreamsAsync()
 0534        {
 535            try
 0536            {
 0537                while (true)
 0538                {
 539                    ValueTask<QuicStream> streamTask;
 540
 0541                    lock (SyncObj)
 0542                    {
 0543                        if (ShuttingDown)
 0544                        {
 0545                            return;
 546                        }
 547
 548                        // No cancellation token is needed here; we expect the operation to cancel itself when _connecti
 0549                        streamTask = _connection!.AcceptInboundStreamAsync(CancellationToken.None);
 0550                    }
 551
 0552                    QuicStream stream = await streamTask.ConfigureAwait(false);
 553
 554                    // This process is cleaned up when _connection is disposed, and errors are observed via Abort().
 0555                    _ = ProcessServerStreamAsync(stream);
 0556                }
 557            }
 0558            catch (QuicException ex) when (ex.QuicError == QuicError.OperationAborted)
 0559            {
 560                // Shutdown initiated by us, no need to abort.
 0561            }
 0562            catch (QuicException ex) when (ex.QuicError == QuicError.ConnectionAborted)
 0563            {
 0564                Debug.Assert(ex.ApplicationErrorCode.HasValue);
 0565                Http3ErrorCode code = (Http3ErrorCode)ex.ApplicationErrorCode.Value;
 566
 0567                Abort(HttpProtocolException.CreateHttp3ConnectionException(code, SR.net_http_http3_connection_close));
 0568            }
 0569            catch (Exception ex)
 0570            {
 0571                Abort(ex);
 0572            }
 0573        }
 574
 575        /// <summary>
 576        /// Routes a stream to an appropriate stream-type-specific processor
 577        /// </summary>
 578        private async Task ProcessServerStreamAsync(QuicStream stream)
 0579        {
 0580            ArrayBuffer buffer = default;
 581
 582            try
 0583            {
 0584                await using (stream.ConfigureAwait(false))
 0585                {
 586                    // Check if this is a bidirectional stream (which we don't support from the server).
 0587                    if (stream.CanWrite)
 0588                    {
 589                        // Server initiated bidirectional streams are either push streams or extensions, and we support 
 0590                        throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.StreamCreationError);
 591                    }
 592
 0593                    buffer = new ArrayBuffer(initialSize: 32, usePool: true);
 594
 595                    // Read the stream type, which is a variable-length integer.
 596                    // This may require multiple reads if the integer is encoded in multiple bytes.
 597                    long streamType;
 0598                    while (true)
 0599                    {
 600                        int bytesRead;
 601                        try
 0602                        {
 0603                            bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).Configure
 0604                        }
 0605                        catch (QuicException ex) when (ex.QuicError == QuicError.StreamAborted)
 0606                        {
 607                            // Treat identical to receiving 0. See below comment.
 0608                            bytesRead = 0;
 0609                        }
 610
 0611                        if (bytesRead == 0)
 0612                        {
 613                            // https://www.rfc-editor.org/rfc/rfc9114.html#name-unidirectional-streams
 614                            // A sender can close or reset a unidirectional stream unless otherwise specified. A receive
 615                            // tolerate unidirectional streams being closed or reset prior to the reception of the unidi
 616                            // stream header.
 0617                            return;
 618                        }
 619
 0620                        buffer.Commit(bytesRead);
 621
 0622                        if (VariableLengthIntegerHelper.TryRead(buffer.ActiveSpan, out streamType, out int streamTypeLen
 0623                        {
 624                            // Successfully read the stream type.
 0625                            buffer.Discard(streamTypeLength);
 0626                            break;
 627                        }
 0628                    }
 629
 0630                    if (NetEventSource.Log.IsEnabled())
 0631                    {
 0632                        NetEventSource.Info(this, $"Received server-initiated unidirectional stream of type {streamType}
 0633                    }
 634
 635                    // Process the stream based on its type.
 0636                    switch ((Http3StreamType)streamType)
 637                    {
 638                        case Http3StreamType.Control:
 0639                            if (Interlocked.Exchange(ref _haveServerControlStream, true))
 0640                            {
 641                                // A second control stream has been received.
 0642                                throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.StreamCreation
 643                            }
 644
 645                            // Ownership of buffer is transferred to ProcessServerControlStreamAsync.
 0646                            ArrayBuffer bufferCopy = buffer;
 0647                            buffer = default;
 648
 0649                            await ProcessServerControlStreamAsync(stream, bufferCopy).ConfigureAwait(false);
 0650                            return;
 651                        case Http3StreamType.QPackDecoder:
 0652                            if (Interlocked.Exchange(ref _haveServerQpackDecodeStream, true))
 0653                            {
 654                                // A second QPack decode stream has been received.
 0655                                throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.StreamCreation
 656                            }
 657
 658                            // The stream must not be closed, but we aren't using QPACK right now -- ignore.
 0659                            buffer.Dispose();
 0660                            await stream.CopyToAsync(Stream.Null).ConfigureAwait(false);
 0661                            return;
 662                        case Http3StreamType.QPackEncoder:
 0663                            if (Interlocked.Exchange(ref _haveServerQpackEncodeStream, true))
 0664                            {
 665                                // A second QPack encode stream has been received.
 0666                                throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.StreamCreation
 667                            }
 668
 669                            // We haven't enabled QPack in our SETTINGS frame, so we shouldn't receive any meaningful da
 670                            // However, the standard says the stream must not be closed for the lifetime of the connecti
 0671                            buffer.Dispose();
 0672                            await stream.CopyToAsync(Stream.Null).ConfigureAwait(false);
 0673                            return;
 674                        case Http3StreamType.Push:
 675                            // We don't support push streams.
 676                            // Because no maximum push stream ID was negotiated via a MAX_PUSH_ID frame, server should n
 0677                            throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.IdError);
 678                        default:
 679                            // Unknown stream type. Per spec, these must be ignored and aborted but not be considered a 
 0680                            stream.Abort(QuicAbortDirection.Read, (long)Http3ErrorCode.StreamCreationError);
 0681                            return;
 682                    }
 683                }
 0684            }
 0685            catch (QuicException ex) when (ex.QuicError == QuicError.OperationAborted)
 0686            {
 687                // ignore the exception, we have already closed the connection
 0688            }
 0689            catch (QuicException ex) when (ex.QuicError == QuicError.ConnectionAborted)
 0690            {
 0691                Debug.Assert(ex.ApplicationErrorCode.HasValue);
 0692                Http3ErrorCode code = (Http3ErrorCode)ex.ApplicationErrorCode.Value;
 693
 0694                Abort(HttpProtocolException.CreateHttp3ConnectionException(code, SR.net_http_http3_connection_close));
 0695            }
 0696            catch (Exception ex)
 0697            {
 0698                Abort(ex);
 0699            }
 700            finally
 0701            {
 0702                buffer.Dispose();
 0703            }
 0704        }
 705
 706        /// <summary>
 707        /// Reads the server's control stream.
 708        /// </summary>
 709        private async Task ProcessServerControlStreamAsync(QuicStream stream, ArrayBuffer buffer)
 0710        {
 711            try
 0712            {
 0713                using (buffer)
 0714                {
 715                    // Read the first frame of the control stream. Per spec:
 716                    // A SETTINGS frame MUST be sent as the first frame of each control stream.
 717
 0718                    (Http3FrameType? frameType, long payloadLength) = await ReadFrameEnvelopeAsync().ConfigureAwait(fals
 719
 0720                    if (frameType == null)
 0721                    {
 722                        // Connection closed prematurely, expected SETTINGS frame.
 0723                        throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ClosedCriticalStream);
 724                    }
 725
 0726                    if (frameType != Http3FrameType.Settings)
 0727                    {
 0728                        throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.MissingSettings);
 729                    }
 730
 0731                    await ProcessSettingsFrameAsync(payloadLength).ConfigureAwait(false);
 732
 733                    // Read subsequent frames.
 734
 0735                    while (true)
 0736                    {
 0737                        (frameType, payloadLength) = await ReadFrameEnvelopeAsync().ConfigureAwait(false);
 738
 0739                        switch (frameType)
 740                        {
 741                            case Http3FrameType.GoAway:
 0742                                await ProcessGoAwayFrameAsync(payloadLength).ConfigureAwait(false);
 0743                                break;
 744                            case Http3FrameType.Settings:
 745                                // If an endpoint receives a second SETTINGS frame on the control stream, the endpoint M
 0746                                throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.UnexpectedFram
 747                            case Http3FrameType.Headers: // Servers should not send these frames to a control stream.
 748                            case Http3FrameType.Data:
 749                            case Http3FrameType.MaxPushId:
 750                            case Http3FrameType.ReservedHttp2Priority: // These frames are explicitly reserved and must 
 751                            case Http3FrameType.ReservedHttp2Ping:
 752                            case Http3FrameType.ReservedHttp2WindowUpdate:
 753                            case Http3FrameType.ReservedHttp2Continuation:
 0754                                if (NetEventSource.Log.IsEnabled())
 0755                                {
 0756                                    Trace($"Received reserved frame: {frameType}");
 0757                                }
 0758                                throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.UnexpectedFram
 759                            case Http3FrameType.PushPromise:
 760                            case Http3FrameType.CancelPush:
 761                                // Because we haven't sent any MAX_PUSH_ID frame, it is invalid to receive any push-rela
 0762                                throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.IdError);
 763                            case null:
 764                                // End of stream reached. If we're shutting down, stop looping. Otherwise, this is an er
 765                                bool shuttingDown;
 0766                                lock (SyncObj)
 0767                                {
 0768                                    shuttingDown = ShuttingDown;
 0769                                }
 0770                                if (!shuttingDown)
 0771                                {
 0772                                    if (NetEventSource.Log.IsEnabled())
 0773                                    {
 0774                                        Trace($"Control stream closed by the server.");
 0775                                    }
 0776                                    throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ClosedCrit
 777                                }
 0778                                return;
 779                            default:
 0780                                await SkipUnknownPayloadAsync(payloadLength).ConfigureAwait(false);
 0781                                break;
 782                        }
 0783                    }
 784                }
 785            }
 0786            catch (QuicException ex) when (ex.QuicError == QuicError.StreamAborted)
 0787            {
 788                // Peers MUST NOT close the control stream
 0789                throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ClosedCriticalStream);
 790            }
 791
 792            async ValueTask<(Http3FrameType? frameType, long payloadLength)> ReadFrameEnvelopeAsync()
 0793            {
 794                long frameType, payloadLength;
 795                int bytesRead;
 796
 0797                while (!Http3Frame.TryReadIntegerPair(buffer.ActiveSpan, out frameType, out payloadLength, out bytesRead
 0798                {
 0799                    buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength * 2);
 0800                    bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(fa
 801
 0802                    if (bytesRead != 0)
 0803                    {
 0804                        buffer.Commit(bytesRead);
 0805                    }
 0806                    else if (buffer.ActiveLength == 0)
 0807                    {
 808                        // End of stream.
 0809                        return (null, 0);
 810                    }
 811                    else
 0812                    {
 813                        // Our buffer has partial frame data in it but not enough to complete the read: bail out.
 0814                        throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.FrameError);
 815                    }
 0816                }
 817
 0818                buffer.Discard(bytesRead);
 819
 0820                return ((Http3FrameType)frameType, payloadLength);
 0821            }
 822
 823            async ValueTask ProcessSettingsFrameAsync(long settingsPayloadLength)
 0824            {
 0825                while (settingsPayloadLength != 0)
 0826                {
 827                    long settingId, settingValue;
 828                    int bytesRead;
 829
 0830                    while (!Http3Frame.TryReadIntegerPair(buffer.ActiveSpan, out settingId, out settingValue, out bytesR
 0831                    {
 0832                        buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength * 2);
 0833                        bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwai
 834
 0835                        if (bytesRead != 0)
 0836                        {
 0837                            buffer.Commit(bytesRead);
 0838                        }
 839                        else
 0840                        {
 841                            // Our buffer has partial frame data in it but not enough to complete the read: bail out.
 0842                            throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.FrameError);
 843                        }
 0844                    }
 845
 0846                    settingsPayloadLength -= bytesRead;
 847
 0848                    if (settingsPayloadLength < 0)
 0849                    {
 850                        // An integer was encoded past the payload length.
 851                        // A frame payload that contains additional bytes after the identified fields or a frame payload
 0852                        throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.FrameError);
 853                    }
 854
 0855                    buffer.Discard(bytesRead);
 856
 0857                    if (NetEventSource.Log.IsEnabled()) Trace($"Applying setting {(Http3SettingType)settingId}={settingV
 858
 0859                    switch ((Http3SettingType)settingId)
 860                    {
 861                        case Http3SettingType.MaxHeaderListSize:
 0862                            _maxHeaderListSize = (uint)Math.Min((ulong)settingValue, uint.MaxValue);
 0863                            _pool._lastSeenHttp3MaxHeaderListSize = _maxHeaderListSize;
 0864                            break;
 865                        case Http3SettingType.ReservedHttp2EnablePush:
 866                        case Http3SettingType.ReservedHttp2MaxConcurrentStreams:
 867                        case Http3SettingType.ReservedHttp2InitialWindowSize:
 868                        case Http3SettingType.ReservedHttp2MaxFrameSize:
 869                            // Per https://tools.ietf.org/html/draft-ietf-quic-http-31#section-7.2.4.1
 870                            // these settings IDs are reserved and must never be sent.
 0871                            throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.SettingsError);
 872                    }
 0873                }
 0874            }
 875
 876            async ValueTask ProcessGoAwayFrameAsync(long goawayPayloadLength)
 0877            {
 878                long firstRejectedStreamId;
 879                int bytesRead;
 880
 0881                while (!VariableLengthIntegerHelper.TryRead(buffer.ActiveSpan, out firstRejectedStreamId, out bytesRead)
 0882                {
 0883                    buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength);
 0884                    bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(fa
 885
 0886                    if (bytesRead != 0)
 0887                    {
 0888                        buffer.Commit(bytesRead);
 0889                    }
 890                    else
 0891                    {
 892                        // Our buffer has partial frame data in it but not enough to complete the read: bail out.
 0893                        throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.FrameError);
 894                    }
 0895                }
 896
 0897                buffer.Discard(bytesRead);
 0898                if (bytesRead != goawayPayloadLength)
 0899                {
 900                    // Frame contains unknown extra data after the integer.
 0901                    throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.FrameError);
 902                }
 903
 0904                OnServerGoAway(firstRejectedStreamId);
 0905            }
 906
 907            async ValueTask SkipUnknownPayloadAsync(long payloadLength)
 0908            {
 0909                while (payloadLength != 0)
 0910                {
 0911                    if (buffer.ActiveLength == 0)
 0912                    {
 0913                        int bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).Configure
 914
 0915                        if (bytesRead != 0)
 0916                        {
 0917                            buffer.Commit(bytesRead);
 0918                        }
 919                        else
 0920                        {
 921                            // Our buffer has partial frame data in it but not enough to complete the read: bail out.
 0922                            throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.FrameError);
 923                        }
 0924                    }
 925
 0926                    long readLength = Math.Min(payloadLength, buffer.ActiveLength);
 0927                    buffer.Discard((int)readLength);
 0928                    payloadLength -= readLength;
 0929                }
 0930            }
 0931        }
 932    }
 933
 934    /// <summary>
 935    /// Tracks telemetry signals associated with the time period an HTTP/3 request spends waiting for a usable HTTP/3 co
 936    /// the wait_for_connection Activity, the RequestLeftQueue EventSource event and the http.client.request.time_in_que
 937    /// </summary>
 938    internal struct WaitForHttp3ConnectionActivity
 939    {
 940        // The HttpConnectionSettings -> SocketsHttpHandlerMetrics indirection is needed for the trimmer.
 941        private HttpConnectionSettings _settings;
 942        private readonly HttpAuthority _authority;
 943        private Activity? _activity;
 944        private long _startTimestamp;
 945
 946        public WaitForHttp3ConnectionActivity(HttpConnectionSettings settings, HttpAuthority authority)
 947        {
 948            _settings = settings;
 949            _authority = authority;
 950        }
 951
 952        public bool Started { get; private set; }
 953
 954        public void Start()
 955        {
 956            Debug.Assert(!Started);
 957            _startTimestamp = HttpTelemetry.Log.IsEnabled() || (GlobalHttpSettings.MetricsHandler.IsGloballyEnabled && _
 958            _activity = ConnectionSetupDistributedTracing.StartWaitForConnectionActivity(_authority);
 959            Started = true;
 960        }
 961
 962        public void Stop(HttpRequestMessage request, HttpConnectionPool pool, Exception? exception)
 963        {
 964            if (exception is not null)
 965            {
 966                ConnectionSetupDistributedTracing.ReportError(_activity, exception);
 967            }
 968
 969            _activity?.Stop();
 970
 971            if (_startTimestamp != 0)
 972            {
 973                TimeSpan duration = Stopwatch.GetElapsedTime(_startTimestamp);
 974
 975                if (GlobalHttpSettings.MetricsHandler.IsGloballyEnabled)
 976                {
 977                    _settings._metrics!.RequestLeftQueue(request, pool, duration, versionMajor: 3);
 978                }
 979                if (HttpTelemetry.Log.IsEnabled())
 980                {
 981                    HttpTelemetry.Log.RequestLeftQueue(3, duration);
 982                }
 983            }
 984        }
 985
 986        [Conditional("DEBUG")]
 987        public void AssertActivityNotRunning()
 988        {
 989            Debug.Assert(_activity?.IsStopped != false);
 990        }
 991    }
 992}