< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 2455
Coverable lines: 2455
Total lines: 4260
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 954
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
File 1: .cctor()100%110%
File 1: .ctor(...)0%440%
File 1: TimeSpanToMs(System.TimeSpan)0%220%
File 1: Finalize()100%110%
File 1: SetupAsync(...)0%880%
File 1: Shutdown()0%660%
File 1: TryReserveStream()0%440%
File 1: ReleaseStream()0%10100%
File 1: WaitForAvailableStreamsAsync()0%440%
File 1: SignalAvailableStreamsWaiter(...)0%440%
File 1: FlushOutgoingBytesAsync()0%440%
File 1: ReadFrameAsync(...)0%26260%
File 1: ThrowPrematureEOF(System.Int32)100%110%
File 1: ThrowMissingFrame()100%110%
File 1: ProcessIncomingFramesAsync()0%34340%
File 1: GetStream(...)0%660%
File 1: ProcessHeadersFrame(...)0%12120%
File 1: .cctor()100%110%
File 1: System.Net.Http.IHttpStreamHeadersHandler.OnHeader(...)100%110%
File 1: System.Net.Http.IHttpStreamHeadersHandler.OnHeadersComplete(...)100%110%
File 1: System.Net.Http.IHttpStreamHeadersHandler.OnStaticIndexedHeader(...)100%110%
File 1: System.Net.Http.IHttpStreamHeadersHandler.OnStaticIndexedHeader(...)100%110%
File 1: System.Net.Http.IHttpStreamHeadersHandler.OnDynamicIndexedHeader(...)100%110%
File 1: GetFrameData(...)0%10100%
File 1: ProcessAltSvcFrame(...)0%12120%
File 1: ProcessDataFrame(...)0%10100%
File 1: ProcessSettingsFrame(...)0%41410%
File 1: ChangeMaxConcurrentStreams(...)0%660%
File 1: ChangeInitialWindowSize(...)0%440%
File 1: ProcessPriorityFrame(...)0%440%
File 1: ProcessPingFrame(...)0%880%
File 1: ProcessWindowUpdateFrame(...)0%10100%
File 1: ProcessRstStreamFrame(...)0%880%
File 1: ProcessGoAwayFrame(...)0%660%
File 1: ReadGoAwayFrame(...)0%660%
File 1: FlushAsync(...)100%110%
File 1: .ctor(...)100%110%
File 1: TryDisableCancellation()100%110%
File 1: .ctor(...)100%110%
File 1: InvokeWriteAction(...)100%110%
File 1: PerformWriteAsync(...)0%660%
File 1: ProcessOutgoingFramesAsync()0%22220%
File 1: QueueSettingsAck()0%440%
File 1: QueuePing(...)0%660%
File 1: SendRstStreamAsync(...)0%220%
File 1: HeartBeat()0%880%
File 1: SplitBuffer(...)0%220%
File 1: WriteIndexedHeader(...)0%440%
File 1: WriteIndexedHeader(...)0%440%
File 1: WriteLiteralHeader(...)0%440%
File 1: WriteLiteralHeaderValues(...)0%660%
File 1: WriteLiteralHeaderValue(...)0%440%
File 1: WriteBytes(...)0%220%
File 1: WriteHeaderCollection(...)0%28280%
File 1: WriteHeaders(...)0%30300%
File 1: AddStream(...)0%10100%
File 1: SendHeadersAsync(...)0%22220%
File 1: SendStreamDataAsync(...)0%10100%
File 1: SendEndStreamAsync(...)0%220%
File 1: SendWindowUpdateAsync(...)0%220%
File 1: ExtendWindow(...)0%660%
File 1: ForceSendConnectionWindowUpdate()0%440%
File 1: TryIncrementQueuedFireAndForgetFrames()0%440%
File 1: DecrementQueuedFireAndForgetFrames()100%110%
File 1: Abort(...)0%10100%
File 1: FinalTeardown()0%220%
File 1: Dispose()100%110%
File 1: .ctor(...)100%110%
File 1: ReadFrom(...)100%110%
File 1: WriteTo(...)100%110%
File 1: ToString()100%110%
File 1: CreateSuccessfullyCompletedTcs()100%110%
File 1: SendAsync(...)0%36360%
File 1: RemoveStream(...)0%660%
File 1: RefreshPingTimestamp()100%110%
File 1: ProcessPingAck(...)0%660%
File 1: VerifyKeepAlive()0%12120%
File 1: ToString()100%110%
File 1: Trace(...)100%110%
File 1: Trace(...)0%220%
File 1: ThrowRetry(...)0%220%
File 1: GetRequestAbortedException(...)0%220%
File 1: ThrowRequestAborted(...)100%110%
File 1: ThrowProtocolError()100%110%
File 1: ThrowProtocolError(...)100%110%
File 2: .ctor(...)0%10100%
File 2: Initialize(...)0%220%
File 2: GetAndClearResponse()100%110%
File 2: SendRequestBodyAsync(...)0%40400%
File 2: WaitFor100ContinueAsync(...)0%14140%
File 2: SendReset()0%660%
File 2: Complete()0%440%
File 2: Cancel()0%12120%
File 2: CancelResponseBody()0%440%
File 2: OnWindowUpdate(...)0%660%
File 2: .cctor()100%110%
File 2: System.Net.Http.IHttpStreamHeadersHandler.OnStaticIndexedHeader(...)0%880%
File 2: System.Net.Http.IHttpStreamHeadersHandler.OnStaticIndexedHeader(...)0%880%
File 2: System.Net.Http.IHttpStreamHeadersHandler.OnDynamicIndexedHeader(...)100%110%
File 2: AdjustHeaderBudget(...)0%220%
File 2: OnStatus(...)0%30300%
File 2: OnHeader(...)0%24240%
File 2: OnHeader(...)0%880%
File 2: OnHeadersStart()0%880%
File 2: OnHeadersComplete(...)0%27270%
File 2: OnResponseData(...)0%16160%
File 2: OnReset(...)0%20200%
File 2: CheckResponseBodyState()0%660%
File 2: TryEnsureHeaders()0%440%
File 2: ReadResponseHeadersAsync(...)0%16160%
File 2: TryReadFromBuffer(...)0%660%
File 2: ReadData(...)0%660%
File 2: ReadDataAsync(...)0%660%
File 2: CopyTo(...)0%440%
File 2: CopyToAsync(...)0%440%
File 2: MoveTrailersToResponseMessage(...)0%220%
File 2: SendDataAsync(...)0%20200%
File 2: CloseResponseBody()0%14140%
File 2: RegisterRequestBodyCancellation(...)100%110%
File 2: System.Threading.Tasks.Sources.IValueTaskSource.GetStatus(...)100%110%
File 2: System.Threading.Tasks.Sources.IValueTaskSource.OnCompleted(...)100%110%
File 2: System.Threading.Tasks.Sources.IValueTaskSource.GetResult(...)100%110%
File 2: WaitForData()100%110%
File 2: WaitForDataAsync(...)0%220%
File 2: Trace(...)100%110%
File 2: .ctor(...)100%110%
File 2: Write(...)100%110%
File 2: WriteAsync(...)100%110%
File 2: .ctor(...)100%110%
File 2: Read(...)100%110%
File 2: ReadAsync(...)100%110%
File 2: CopyTo(...)100%110%
File 2: CopyToAsync(...)100%110%
File 2: WriteAsync(...)0%220%
File 2: .ctor(...)100%110%
File 2: Finalize()0%880%
File 2: Dispose(...)0%440%
File 2: Read(...)100%110%
File 2: ReadAsync(...)0%440%
File 2: CopyTo(...)0%220%
File 2: CopyToAsync(...)0%440%
File 2: WriteAsync(...)0%220%
File 2: FlushAsync(...)0%440%
File 3: .ctor(...)0%220%
File 3: Start()100%110%
File 3: AdjustWindow(...)0%440%
File 3: AjdustWindowStatic(...)0%220%
File 3: AdjustWindowDynamic(...)0%14140%
File 3: .cctor()100%110%
File 3: Create()0%220%
File 3: OnInitialSettingsSent()0%220%
File 3: OnInitialSettingsAckReceived(...)0%220%
File 3: OnDataOrHeadersReceived(...)0%16160%
File 3: OnPingAckReceived(...)0%12120%
File 3: OnGoAwayReceived()0%220%
File 3: RefreshRtt(...)0%440%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.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.Buffers.Binary;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Diagnostics.CodeAnalysis;
 8using System.IO;
 9using System.Net.Http.Headers;
 10using System.Net.Http.HPack;
 11using System.Runtime.CompilerServices;
 12using System.Runtime.ExceptionServices;
 13using System.Text;
 14using System.Threading;
 15using System.Threading.Channels;
 16using System.Threading.Tasks;
 17
 18namespace System.Net.Http
 19{
 20    internal sealed partial class Http2Connection : HttpConnectionBase
 21    {
 22        // Equivalent to the bytes returned from HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingNewNameToAllocatedA
 023        private static ReadOnlySpan<byte> ProtocolLiteralHeaderBytes => [0x0, 0x9, 0x3a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0
 24
 025        private static readonly TaskCompletionSourceWithCancellation<bool> s_settingsReceivedSingleton = CreateSuccessfu
 26
 27        private TaskCompletionSourceWithCancellation<bool>? _initialSettingsReceived;
 28
 29        private readonly Stream _stream;
 30
 31        // NOTE: These are mutable structs; do not make these readonly.
 32        // ProcessIncomingFramesAsync and ProcessOutgoingFramesAsync are responsible for disposing/returning their respe
 33        private ArrayBuffer _incomingBuffer;
 34        private ArrayBuffer _outgoingBuffer;
 35
 36        /// <summary>Reusable array used to get the values for each header being written to the wire.</summary>
 37        [ThreadStatic]
 38        private static string[]? t_headerValues;
 39
 40        private readonly HPackDecoder _hpackDecoder;
 41
 42        private readonly Dictionary<int, Http2Stream> _httpStreams;
 43
 44        private readonly CreditManager _connectionWindow;
 45        private RttEstimator _rttEstimator;
 46
 47        private int _nextStream;
 48        private bool _receivedSettingsAck;
 49        private int _initialServerStreamWindowSize;
 50        private int _pendingWindowUpdate;
 51
 52        private uint _maxConcurrentStreams;
 53        private uint _streamsInUse;
 54        private TaskCompletionSource<bool>? _availableStreamsWaiter;
 55
 56        private readonly Channel<WriteQueueEntry> _writeChannel;
 57        private bool _lastPendingWriterShouldFlush;
 58
 59        // Server-advertised SETTINGS_MAX_HEADER_LIST_SIZE
 60        // https://www.rfc-editor.org/rfc/rfc9113.html#section-6.5.2-2.12.1
 061        private uint _maxHeaderListSize = uint.MaxValue; // Defaults to infinite
 62
 63        // This flag indicates that the connection is shutting down and cannot accept new requests, because of one of th
 64        // (1) We received a GOAWAY frame from the server
 65        // (2) We have exhaustead StreamIds (i.e. _nextStream == MaxStreamId)
 66        // (3) A connection-level error occurred, in which case _abortException below is set.
 67        // (4) The connection is being disposed.
 68        // Requests currently in flight will continue to be processed.
 69        // When all requests have completed, the connection will be torn down.
 70        private bool _shutdown;
 71
 72        // If this is set, the connection is aborting due to an IO failure (IOException) or a protocol violation (Http2P
 73        // _shutdown above is true, and requests in flight have been (or are being) failed.
 74        private Exception? _abortException;
 75
 76        private Http2ProtocolErrorCode? _goAwayErrorCode;
 77
 78        // Cap number of untransmitted PING and SETTING ACKs and PING requests
 79        private const int MaxQueuedFireAndForgetFrames = 1000;
 80        private int _queuedFireAndForgetFrames;
 81
 82        private const int MaxStreamId = int.MaxValue;
 83
 084        private static ReadOnlySpan<byte> Http2ConnectionPreface => "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"u8;
 85
 86#if DEBUG
 87        // In debug builds, start with a very small buffer to induce buffer growing logic.
 88        private const int InitialConnectionBufferSize = FrameHeader.Size;
 89#else
 90        // Rent enough space to receive a full data frame in one read call.
 91        private const int InitialConnectionBufferSize = FrameHeader.Size + FrameHeader.MaxPayloadLength;
 92#endif
 93
 94        // The default initial window size for streams and connections according to the RFC:
 95        // https://datatracker.ietf.org/doc/html/rfc7540#section-5.2.1
 96        // Unlike HttpHandlerDefaults.DefaultInitialHttp2StreamWindowSize, this value should never be changed.
 97        internal const int DefaultInitialWindowSize = 65535;
 98
 99        // We don't really care about limiting control flow at the connection level.
 100        // We limit it per stream, and the user controls how many streams are created.
 101        // So set the connection window size to a large value.
 102        private const int ConnectionWindowSize = 64 * 1024 * 1024;
 103
 104        // We hold off on sending WINDOW_UPDATE until we hit the minimum threshold.
 105        // This value is somewhat arbitrary; the intent is to ensure it is much smaller than
 106        // the window size itself, or we risk stalling the server because it runs out of window space.
 107        // If we want to further reduce the frequency of WINDOW_UPDATEs, it's probably better to
 108        // increase the window size (and thus increase the threshold proportionally)
 109        // rather than just increase the threshold.
 110        private const int ConnectionWindowUpdateRatio = 8;
 111        private const int ConnectionWindowThreshold = ConnectionWindowSize / ConnectionWindowUpdateRatio;
 112
 113        // When buffering outgoing writes, we will automatically buffer up to this number of bytes.
 114        // Single writes that are larger than the buffer can cause the buffer to expand beyond
 115        // this value, so this is not a hard maximum size.
 116        private const int UnflushedOutgoingBufferSize = 32 * 1024;
 117
 118        // Channel options for creating _writeChannel
 0119        private static readonly UnboundedChannelOptions s_channelOptions = new UnboundedChannelOptions() { SingleReader 
 120
 121        internal enum KeepAliveState
 122        {
 123            None,
 124            PingSent
 125        }
 126
 127        private readonly long _keepAlivePingDelay;
 128        private readonly long _keepAlivePingTimeout;
 129        private readonly HttpKeepAlivePingPolicy _keepAlivePingPolicy;
 130        private long _keepAlivePingPayload;
 131        private long _nextPingRequestTimestamp;
 132        private long _keepAlivePingTimeoutTimestamp;
 133        private volatile KeepAliveState _keepAliveState;
 134        /// <summary>Set once <see cref="SetupAsync"/> completes. Until then, no keep alive PINGs are sent.</summary>
 135        private bool _setupComplete;
 136
 137        public Http2Connection(HttpConnectionPool pool, Stream stream, Activity? connectionSetupActivity, IPEndPoint? re
 0138            : base(pool, connectionId, connectionSetupActivity, remoteEndPoint)
 0139        {
 0140            _stream = stream;
 141
 0142            _incomingBuffer = new ArrayBuffer(initialSize: 0, usePool: true);
 0143            _outgoingBuffer = new ArrayBuffer(initialSize: 0, usePool: true);
 144
 0145            _hpackDecoder = new HPackDecoder(maxHeadersLength: pool.Settings.MaxResponseHeadersByteLength);
 146
 0147            _httpStreams = new Dictionary<int, Http2Stream>();
 148
 0149            _connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialWindowSize);
 150
 0151            _rttEstimator = RttEstimator.Create();
 152
 0153            _writeChannel = Channel.CreateUnbounded<WriteQueueEntry>(s_channelOptions);
 154
 0155            _nextStream = 1;
 0156            _initialServerStreamWindowSize = DefaultInitialWindowSize;
 157
 0158            _maxConcurrentStreams = pool._lastSeenHttp2MaxConcurrentStreams;
 0159            _streamsInUse = 0;
 160
 0161            _pendingWindowUpdate = 0;
 162
 0163            _keepAlivePingDelay = TimeSpanToMs(_pool.Settings._keepAlivePingDelay);
 0164            _keepAlivePingTimeout = TimeSpanToMs(_pool.Settings._keepAlivePingTimeout);
 0165            _nextPingRequestTimestamp = Environment.TickCount64 + _keepAlivePingDelay;
 0166            _keepAlivePingPolicy = _pool.Settings._keepAlivePingPolicy;
 167
 0168            uint maxHeaderListSize = _pool._lastSeenHttp2MaxHeaderListSize;
 0169            if (maxHeaderListSize > 0)
 0170            {
 171                // Previous connections to the same host advertised a limit.
 172                // Use this as an initial value before we receive the SETTINGS frame.
 0173                _maxHeaderListSize = maxHeaderListSize;
 0174            }
 175
 0176            if (NetEventSource.Log.IsEnabled()) TraceConnection(_stream);
 177
 178            // Register with the pool before doing anything that may tear the connection down,
 179            // so that the pool can run keep alive ping logic for the whole lifetime of the connection.
 0180            pool.AddHttp2ConnectionForHeartBeat(this);
 181
 182            static long TimeSpanToMs(TimeSpan value)
 0183            {
 0184                double milliseconds = value.TotalMilliseconds;
 0185                return (long)(milliseconds > int.MaxValue ? int.MaxValue : milliseconds);
 0186            }
 0187        }
 188
 0189        ~Http2Connection() => Dispose();
 190
 0191        private object SyncObject => _httpStreams;
 192
 193        internal TaskCompletionSourceWithCancellation<bool> InitialSettingsReceived =>
 0194            _initialSettingsReceived ??
 0195            Interlocked.CompareExchange(ref _initialSettingsReceived, new(), null) ??
 0196            _initialSettingsReceived;
 197
 0198        internal bool IsConnectEnabled { get; private set; }
 199
 200        public async ValueTask SetupAsync(CancellationToken cancellationToken)
 0201        {
 202            try
 0203            {
 0204                int requiredSpace = Http2ConnectionPreface.Length +
 0205                    FrameHeader.Size + (2 * FrameHeader.SettingLength) +
 0206                    FrameHeader.Size + FrameHeader.WindowUpdateLength;
 207
 0208                _outgoingBuffer.EnsureAvailableSpace(requiredSpace);
 209
 210                // Send connection preface
 0211                Http2ConnectionPreface.CopyTo(_outgoingBuffer.AvailableSpan);
 0212                _outgoingBuffer.Commit(Http2ConnectionPreface.Length);
 213
 214                // Send SETTINGS frame.  Disable push promise & set initial window size.
 0215                FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, 2 * FrameHeader.SettingLength, FrameType.Settings, Fr
 0216                _outgoingBuffer.Commit(FrameHeader.Size);
 0217                BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.EnablePush);
 0218                _outgoingBuffer.Commit(2);
 0219                BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, 0);
 0220                _outgoingBuffer.Commit(4);
 0221                BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.InitialWindowSize
 0222                _outgoingBuffer.Commit(2);
 0223                BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, (uint)_pool.Settings._initialHttp2S
 0224                _outgoingBuffer.Commit(4);
 225
 226                // The connection-level window size can not be initialized by SETTINGS frames:
 227                // https://datatracker.ietf.org/doc/html/rfc7540#section-6.9.2
 228                // Send an initial connection-level WINDOW_UPDATE to setup the desired ConnectionWindowSize:
 0229                uint windowUpdateAmount = ConnectionWindowSize - DefaultInitialWindowSize;
 0230                if (NetEventSource.Log.IsEnabled()) Trace($"Initial connection-level WINDOW_UPDATE, windowUpdateAmount={
 0231                FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, FrameHeader.WindowUpdateLength, FrameType.WindowUpdat
 0232                _outgoingBuffer.Commit(FrameHeader.Size);
 0233                BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, windowUpdateAmount);
 0234                _outgoingBuffer.Commit(4);
 235
 0236                Debug.Assert(requiredSpace == _outgoingBuffer.ActiveLength);
 237
 238                // Processing the incoming frames before sending the client preface and SETTINGS is necessary when using
 239                // If the preface and SETTINGS coming from the server are not read first the below WriteAsync and the Pr
 240                // Avoid capturing the initial request's ExecutionContext for the entire lifetime of the new connection.
 0241                using (ExecutionContext.SuppressFlow())
 0242                {
 0243                    _ = ProcessIncomingFramesAsync();
 0244                }
 245
 0246                await _stream.WriteAsync(_outgoingBuffer.ActiveMemory, cancellationToken).ConfigureAwait(false);
 0247                _rttEstimator.OnInitialSettingsSent();
 0248                _outgoingBuffer.ClearAndReturnBuffer();
 0249            }
 0250            catch (Exception e)
 0251            {
 252                // ProcessIncomingFramesAsync and ProcessOutgoingFramesAsync are responsible for disposing/returning the
 253                // SetupAsync is the exception as it's responsible for starting the ProcessOutgoingFramesAsync loop.
 254                // As we're about to throw and ProcessOutgoingFramesAsync will never be called, we must return the buffe
 0255                _outgoingBuffer.Dispose();
 256
 0257                Dispose();
 258
 0259                if (e is OperationCanceledException oce && oce.CancellationToken == cancellationToken)
 0260                {
 261                    // Note, AddHttp2ConnectionAsync handles this OCE separately so don't wrap it.
 0262                    throw;
 263                }
 264
 265                // Use _abortException if available, as it contains the real reason for the connection failure.
 266                // For example, when ProcessIncomingFramesAsync detects a server-initiated disconnect and calls Abort(),
 267                // _abortException will have the original IOException, while 'e' here may be an uninformative ObjectDisp
 0268                throw new IOException(SR.net_http_http2_connection_not_established, _abortException ?? e);
 269            }
 270
 271            // Avoid capturing the initial request's ExecutionContext for the entire lifetime of the new connection.
 0272            using (ExecutionContext.SuppressFlow())
 0273            {
 0274                _ = ProcessOutgoingFramesAsync();
 0275            }
 276
 277            // The connection is now able to write frames, so it may start sending keep alive PINGs.
 0278            _setupComplete = true;
 0279        }
 280
 281        private void Shutdown()
 0282        {
 0283            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(_shutdown)}={_shutdown}, {nameof(_abortException)}={_abo
 284
 0285            Debug.Assert(Monitor.IsEntered(SyncObject));
 0286            Debug.Assert(!_pool.HasSyncObjLock);
 287
 0288            if (!_shutdown)
 0289            {
 290                // InvalidateHttp2Connection may call back into Shutdown,
 291                // so we set the flag early to prevent executing FinalTeardown twice.
 0292                _shutdown = true;
 293
 0294                _pool.InvalidateHttp2Connection(this);
 0295                SignalAvailableStreamsWaiter(false);
 296
 0297                if (_streamsInUse == 0)
 0298                {
 0299                    FinalTeardown();
 0300                }
 0301            }
 0302        }
 303
 304        public bool TryReserveStream()
 0305        {
 0306            Debug.Assert(!_pool.HasSyncObjLock);
 307
 0308            lock (SyncObject)
 0309            {
 0310                if (_shutdown)
 0311                {
 0312                    return false;
 313                }
 314
 0315                if (_streamsInUse < _maxConcurrentStreams)
 0316                {
 0317                    _streamsInUse++;
 0318                    return true;
 319                }
 0320            }
 321
 0322            return false;
 0323        }
 324
 325        // Can be called by the HttpConnectionPool after TryReserveStream if the stream doesn't end up being used.
 326        // Otherwise, will be called when the request is complete and stream is closed.
 327        public void ReleaseStream()
 0328        {
 0329            Debug.Assert(!_pool.HasSyncObjLock);
 330
 0331            lock (SyncObject)
 0332            {
 0333                if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(_streamsInUse)}={_streamsInUse}");
 334
 0335                Debug.Assert(_availableStreamsWaiter is null || _streamsInUse >= _maxConcurrentStreams);
 336
 0337                _streamsInUse--;
 338
 0339                Debug.Assert(_streamsInUse >= _httpStreams.Count);
 340
 0341                if (_streamsInUse < _maxConcurrentStreams)
 0342                {
 0343                    SignalAvailableStreamsWaiter(true);
 0344                }
 345
 0346                if (_streamsInUse == 0)
 0347                {
 0348                    if (_shutdown)
 0349                    {
 0350                        FinalTeardown();
 0351                    }
 0352                }
 0353            }
 0354        }
 355
 356        // Returns true to indicate at least one stream is available
 357        // Returns false to indicate that the connection is shutting down and cannot be used anymore
 358        public Task<bool> WaitForAvailableStreamsAsync()
 0359        {
 0360            Debug.Assert(!_pool.HasSyncObjLock);
 361
 0362            lock (SyncObject)
 0363            {
 0364                Debug.Assert(_availableStreamsWaiter is null, "As used currently, shouldn't already have a waiter");
 365
 0366                if (_shutdown)
 0367                {
 0368                    return Task.FromResult(false);
 369                }
 370
 0371                if (_streamsInUse < _maxConcurrentStreams)
 0372                {
 0373                    return Task.FromResult(true);
 374                }
 375
 376                // Need to wait for streams to become available.
 0377                _availableStreamsWaiter = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronou
 0378                return _availableStreamsWaiter.Task;
 379            }
 0380        }
 381
 382        private void SignalAvailableStreamsWaiter(bool result)
 0383        {
 0384            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(result)}={result}, {nameof(_availableStreamsWaiter)}?={_
 385
 0386            Debug.Assert(Monitor.IsEntered(SyncObject));
 387
 0388            if (_availableStreamsWaiter is not null)
 0389            {
 0390                Debug.Assert(_shutdown != result);
 0391                _availableStreamsWaiter.SetResult(result);
 0392                _availableStreamsWaiter = null;
 0393            }
 0394        }
 395
 396        private async Task FlushOutgoingBytesAsync()
 0397        {
 0398            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(_outgoingBuffer.ActiveLength)}={_outgoingBuffer.ActiveLe
 399
 0400            if (_outgoingBuffer.ActiveLength > 0)
 0401            {
 402                try
 0403                {
 0404                    await _stream.WriteAsync(_outgoingBuffer.ActiveMemory).ConfigureAwait(false);
 0405                }
 0406                catch (Exception e)
 0407                {
 0408                    Abort(e);
 0409                }
 410
 0411                _lastPendingWriterShouldFlush = false;
 0412                _outgoingBuffer.DiscardAll();
 0413            }
 0414        }
 415
 416        private async ValueTask<FrameHeader> ReadFrameAsync(bool initialFrame = false)
 0417        {
 0418            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(initialFrame)}={initialFrame}");
 419
 420            // Ensure we've read enough data for the frame header.
 0421            if (_incomingBuffer.ActiveLength < FrameHeader.Size)
 0422            {
 423                do
 0424                {
 425                    // Issue a zero-byte read to avoid potentially pinning the buffer while waiting for more data.
 0426                    await _stream.ReadAsync(Memory<byte>.Empty).ConfigureAwait(false);
 427
 0428                    _incomingBuffer.EnsureAvailableSpace(FrameHeader.Size);
 429
 0430                    int bytesRead = await _stream.ReadAsync(_incomingBuffer.AvailableMemory).ConfigureAwait(false);
 0431                    _incomingBuffer.Commit(bytesRead);
 0432                    if (bytesRead == 0)
 0433                    {
 0434                        if (_goAwayErrorCode is not null)
 0435                        {
 0436                            ThrowProtocolError(_goAwayErrorCode.Value, SR.net_http_http2_connection_close);
 437                        }
 0438                        else if (_incomingBuffer.ActiveLength == 0)
 0439                        {
 0440                            ThrowMissingFrame();
 0441                        }
 442                        else
 0443                        {
 0444                            ThrowPrematureEOF(FrameHeader.Size);
 0445                        }
 0446                    }
 0447                }
 0448                while (_incomingBuffer.ActiveLength < FrameHeader.Size);
 0449            }
 450
 451            // Parse the frame header from our read buffer and validate it.
 0452            FrameHeader frameHeader = FrameHeader.ReadFrom(_incomingBuffer.ActiveSpan);
 0453            if (frameHeader.PayloadLength > FrameHeader.MaxPayloadLength)
 0454            {
 0455                if (initialFrame && NetEventSource.Log.IsEnabled())
 0456                {
 0457                    string response = Encoding.ASCII.GetString(_incomingBuffer.ActiveSpan.Slice(0, Math.Min(20, _incomin
 0458                    Trace($"HTTP/2 handshake failed. Server returned {response}");
 0459                }
 460
 0461                _incomingBuffer.Discard(FrameHeader.Size);
 0462                ThrowProtocolError(initialFrame ? Http2ProtocolErrorCode.ProtocolError : Http2ProtocolErrorCode.FrameSiz
 463            }
 0464            _incomingBuffer.Discard(FrameHeader.Size);
 465
 466            // Ensure we've read the frame contents into our buffer.
 0467            if (_incomingBuffer.ActiveLength < frameHeader.PayloadLength)
 0468            {
 0469                _incomingBuffer.EnsureAvailableSpace(frameHeader.PayloadLength - _incomingBuffer.ActiveLength);
 470                do
 0471                {
 472                    // Issue a zero-byte read to avoid potentially pinning the buffer while waiting for more data.
 0473                    await _stream.ReadAsync(Memory<byte>.Empty).ConfigureAwait(false);
 474
 0475                    int bytesRead = await _stream.ReadAsync(_incomingBuffer.AvailableMemory).ConfigureAwait(false);
 0476                    _incomingBuffer.Commit(bytesRead);
 0477                    if (bytesRead == 0) ThrowPrematureEOF(frameHeader.PayloadLength);
 0478                }
 0479                while (_incomingBuffer.ActiveLength < frameHeader.PayloadLength);
 0480            }
 481
 482            // Return the read frame header.
 0483            return frameHeader;
 484
 485            void ThrowPrematureEOF(int requiredBytes) =>
 0486                throw new HttpIOException(HttpRequestError.ResponseEnded, SR.Format(SR.net_http_invalid_response_prematu
 487
 488            void ThrowMissingFrame() =>
 0489                throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_missing_frame);
 0490        }
 491
 492        private async Task ProcessIncomingFramesAsync()
 0493        {
 494            try
 0495            {
 496                FrameHeader frameHeader;
 497                try
 0498                {
 499                    // Read the initial settings frame.
 0500                    frameHeader = await ReadFrameAsync(initialFrame: true).ConfigureAwait(false);
 0501                    if (frameHeader.Type != FrameType.Settings || frameHeader.AckFlag)
 0502                    {
 0503                        if (frameHeader.Type == FrameType.GoAway)
 0504                        {
 0505                            var (_, errorCode) = ReadGoAwayFrame(frameHeader);
 0506                            ThrowProtocolError(errorCode, SR.net_http_http2_connection_close);
 507                        }
 508                        else
 0509                        {
 0510                            ThrowProtocolError();
 511                        }
 512                    }
 513
 0514                    if (NetEventSource.Log.IsEnabled()) Trace($"Frame 0: {frameHeader}.");
 515
 516                    // Process the initial SETTINGS frame. This will send an ACK.
 0517                    ProcessSettingsFrame(frameHeader, initialFrame: true);
 518
 0519                    Debug.Assert(InitialSettingsReceived.Task.IsCompleted);
 0520                }
 0521                catch (HttpProtocolException e)
 0522                {
 0523                    InitialSettingsReceived.TrySetException(e);
 0524                    LogExceptions(InitialSettingsReceived.Task);
 0525                    throw;
 526                }
 0527                catch (Exception e)
 0528                {
 0529                    InitialSettingsReceived.TrySetException(new HttpIOException(HttpRequestError.InvalidResponse, SR.net
 0530                    LogExceptions(InitialSettingsReceived.Task);
 0531                    throw new HttpIOException(HttpRequestError.InvalidResponse, SR.net_http_http2_connection_not_establi
 532                }
 533
 534                // Keep processing frames as they arrive.
 0535                for (long frameNum = 1; ; frameNum++)
 0536                {
 537                    // We could just call ReadFrameAsync here, but we add this code
 538                    // to avoid another state machine allocation in the relatively common case where we
 539                    // currently don't have enough data buffered and issuing a read for the frame header
 540                    // completes asynchronously, but that read ends up also reading enough data to fulfill
 541                    // the entire frame's needs (not just the header).
 0542                    if (_incomingBuffer.ActiveLength < FrameHeader.Size)
 0543                    {
 544                        do
 0545                        {
 546                            // Issue a zero-byte read to avoid potentially pinning the buffer while waiting for more dat
 0547                            ValueTask<int> zeroByteReadTask = _stream.ReadAsync(Memory<byte>.Empty);
 0548                            if (!zeroByteReadTask.IsCompletedSuccessfully && _incomingBuffer.ActiveLength == 0)
 0549                            {
 550                                // No data is available yet. Return the receive buffer back to the pool while we wait.
 0551                                _incomingBuffer.ClearAndReturnBuffer();
 0552                            }
 0553                            await zeroByteReadTask.ConfigureAwait(false);
 554
 555                            // While we only need FrameHeader.Size bytes to complete this read, it's better if we rent m
 556                            // to avoid multiple ReadAsync calls and resizes once we start copying the content.
 0557                            _incomingBuffer.EnsureAvailableSpace(InitialConnectionBufferSize);
 558
 0559                            int bytesRead = await _stream.ReadAsync(_incomingBuffer.AvailableMemory).ConfigureAwait(fals
 0560                            Debug.Assert(bytesRead >= 0);
 0561                            _incomingBuffer.Commit(bytesRead);
 0562                            if (bytesRead == 0)
 0563                            {
 564                                // ReadFrameAsync below will detect that the frame is incomplete and throw the appropria
 0565                                break;
 566                            }
 0567                        }
 0568                        while (_incomingBuffer.ActiveLength < FrameHeader.Size);
 0569                    }
 570
 571                    // Read the frame.
 0572                    frameHeader = await ReadFrameAsync().ConfigureAwait(false);
 0573                    if (NetEventSource.Log.IsEnabled()) Trace($"Frame {frameNum}: {frameHeader}.");
 574
 0575                    RefreshPingTimestamp();
 576
 577                    // Process the frame.
 0578                    switch (frameHeader.Type)
 579                    {
 580                        case FrameType.Headers:
 0581                            await ProcessHeadersFrame(frameHeader).ConfigureAwait(false);
 0582                            break;
 583
 584                        case FrameType.Data:
 0585                            ProcessDataFrame(frameHeader);
 0586                            break;
 587
 588                        case FrameType.Settings:
 0589                            ProcessSettingsFrame(frameHeader);
 0590                            break;
 591
 592                        case FrameType.Priority:
 0593                            ProcessPriorityFrame(frameHeader);
 0594                            break;
 595
 596                        case FrameType.Ping:
 0597                            ProcessPingFrame(frameHeader);
 0598                            break;
 599
 600                        case FrameType.WindowUpdate:
 0601                            ProcessWindowUpdateFrame(frameHeader);
 0602                            break;
 603
 604                        case FrameType.RstStream:
 0605                            ProcessRstStreamFrame(frameHeader);
 0606                            break;
 607
 608                        case FrameType.GoAway:
 0609                            ProcessGoAwayFrame(frameHeader);
 0610                            break;
 611
 612                        case FrameType.AltSvc:
 0613                            ProcessAltSvcFrame(frameHeader);
 0614                            break;
 615
 616                        case FrameType.PushPromise:     // Should not happen, since we disable this in our initial SETTI
 617                        case FrameType.Continuation:    // Should only be received while processing headers in ProcessHe
 618                        default:
 0619                            ThrowProtocolError();
 620                            break;
 621                    }
 0622                }
 623            }
 0624            catch (Exception e)
 0625            {
 0626                if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(ProcessIncomingFramesAsync)}: {e.Message}");
 627
 0628                Abort(e);
 0629            }
 630            finally
 0631            {
 0632                _incomingBuffer.Dispose();
 0633            }
 0634        }
 635
 636        // Note, this will return null for a streamId that's no longer in use.
 637        // Callers must check for this and send a RST_STREAM or ignore as appropriate.
 638        // If the streamId is invalid or the stream is idle, calling this function
 639        // will result in a connection level error.
 640        private Http2Stream? GetStream(int streamId)
 0641        {
 0642            if (streamId <= 0 || streamId >= _nextStream)
 0643            {
 0644                ThrowProtocolError();
 645            }
 646
 0647            lock (SyncObject)
 0648            {
 0649                if (!_httpStreams.TryGetValue(streamId, out Http2Stream? http2Stream))
 0650                {
 0651                    return null;
 652                }
 653
 0654                return http2Stream;
 655            }
 0656        }
 657
 658        private async ValueTask ProcessHeadersFrame(FrameHeader frameHeader)
 0659        {
 0660            if (NetEventSource.Log.IsEnabled()) Trace($"{frameHeader}");
 0661            Debug.Assert(frameHeader.Type == FrameType.Headers);
 662
 0663            bool endStream = frameHeader.EndStreamFlag;
 664
 0665            int streamId = frameHeader.StreamId;
 0666            Http2Stream? http2Stream = GetStream(streamId);
 667
 668            IHttpStreamHeadersHandler headersHandler;
 0669            if (http2Stream != null)
 0670            {
 0671                http2Stream.OnHeadersStart();
 0672                _rttEstimator.OnDataOrHeadersReceived(this, sendWindowUpdateBeforePing: true);
 0673                headersHandler = http2Stream;
 0674            }
 675            else
 0676            {
 677                // http2Stream will be null if this is a closed stream. We will still process the headers,
 678                // to ensure the header decoding state is up-to-date, but we will discard the decoded headers.
 0679                headersHandler = NopHeadersHandler.Instance;
 0680            }
 681
 0682            _hpackDecoder.Decode(
 0683                GetFrameData(_incomingBuffer.ActiveSpan.Slice(0, frameHeader.PayloadLength), frameHeader.PaddedFlag, fra
 0684                frameHeader.EndHeadersFlag,
 0685                headersHandler);
 0686            _incomingBuffer.Discard(frameHeader.PayloadLength);
 687
 0688            while (!frameHeader.EndHeadersFlag)
 0689            {
 0690                frameHeader = await ReadFrameAsync().ConfigureAwait(false);
 691
 0692                if (frameHeader.Type != FrameType.Continuation ||
 0693                    frameHeader.StreamId != streamId)
 0694                {
 0695                    ThrowProtocolError();
 696                }
 697
 0698                _hpackDecoder.Decode(
 0699                    _incomingBuffer.ActiveSpan.Slice(0, frameHeader.PayloadLength),
 0700                    frameHeader.EndHeadersFlag,
 0701                    headersHandler);
 0702                _incomingBuffer.Discard(frameHeader.PayloadLength);
 0703            }
 704
 0705            _hpackDecoder.CompleteDecode();
 706
 0707            http2Stream?.OnHeadersComplete(endStream);
 0708        }
 709
 710        /// <summary>Nop implementation of <see cref="IHttpStreamHeadersHandler"/> used by <see cref="ProcessHeadersFram
 711        private sealed class NopHeadersHandler : IHttpStreamHeadersHandler
 712        {
 0713            public static readonly NopHeadersHandler Instance = new NopHeadersHandler();
 0714            void IHttpStreamHeadersHandler.OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value) { }
 0715            void IHttpStreamHeadersHandler.OnHeadersComplete(bool endStream) { }
 0716            void IHttpStreamHeadersHandler.OnStaticIndexedHeader(int index) { }
 0717            void IHttpStreamHeadersHandler.OnStaticIndexedHeader(int index, ReadOnlySpan<byte> value) { }
 0718            void IHttpStreamHeadersHandler.OnDynamicIndexedHeader(int? index, ReadOnlySpan<byte> name, ReadOnlySpan<byte
 719        }
 720
 721        private static ReadOnlySpan<byte> GetFrameData(ReadOnlySpan<byte> frameData, bool hasPad, bool hasPriority)
 0722        {
 0723            if (hasPad)
 0724            {
 0725                if (frameData.Length == 0)
 0726                {
 0727                    ThrowProtocolError();
 728                }
 729
 0730                int padLength = frameData[0];
 0731                frameData = frameData.Slice(1);
 732
 0733                if (frameData.Length < padLength)
 0734                {
 0735                    ThrowProtocolError();
 736                }
 737
 0738                frameData = frameData.Slice(0, frameData.Length - padLength);
 0739            }
 740
 0741            if (hasPriority)
 0742            {
 0743                if (frameData.Length < FrameHeader.PriorityInfoLength)
 0744                {
 0745                    ThrowProtocolError();
 746                }
 747
 748                // We ignore priority info.
 0749                frameData = frameData.Slice(FrameHeader.PriorityInfoLength);
 0750            }
 751
 0752            return frameData;
 0753        }
 754
 755        /// <summary>
 756        /// Parses an ALTSVC frame, defined by RFC 7838 Section 4.
 757        /// </summary>
 758        /// <remarks>
 759        /// The RFC states that any parse errors should result in ignoring the frame.
 760        /// </remarks>
 761        private void ProcessAltSvcFrame(FrameHeader frameHeader)
 0762        {
 0763            if (NetEventSource.Log.IsEnabled()) Trace($"{frameHeader}");
 0764            Debug.Assert(frameHeader.Type == FrameType.AltSvc);
 0765            Debug.Assert(!Monitor.IsEntered(SyncObject));
 766
 0767            ReadOnlySpan<byte> span = _incomingBuffer.ActiveSpan.Slice(0, frameHeader.PayloadLength);
 768
 0769            if (BinaryPrimitives.TryReadUInt16BigEndian(span, out ushort originLength))
 0770            {
 0771                span = span.Slice(2);
 772
 773                // Check that this ALTSVC frame is valid for our pool's origin. ALTSVC frames can come in one of two way
 774                //  - On stream 0, the origin will be specified. HTTP/2 can service multiple origins per connection, and
 775                //  - Otherwise, the origin is implicitly defined by the request stream and must be of length 0.
 776
 0777                if ((frameHeader.StreamId != 0 && originLength == 0) || (frameHeader.StreamId == 0 && span.Length >= ori
 0778                {
 0779                    span = span.Slice(originLength);
 780
 781                    // The span now contains a string with the same format as Alt-Svc headers.
 782
 0783                    string altSvcHeaderValue = Encoding.ASCII.GetString(span);
 0784                    _pool.HandleAltSvc(new[] { altSvcHeaderValue }, null);
 0785                }
 0786            }
 787
 0788            _incomingBuffer.Discard(frameHeader.PayloadLength);
 0789        }
 790
 791        private void ProcessDataFrame(FrameHeader frameHeader)
 0792        {
 0793            Debug.Assert(frameHeader.Type == FrameType.Data);
 794
 0795            Http2Stream? http2Stream = GetStream(frameHeader.StreamId);
 796
 797            // Note, http2Stream will be null if this is a closed stream.
 798            // Just ignore the frame in this case.
 799
 0800            ReadOnlySpan<byte> frameData = GetFrameData(_incomingBuffer.ActiveSpan.Slice(0, frameHeader.PayloadLength), 
 0801            bool endStream = frameHeader.EndStreamFlag;
 802
 0803            if (frameData.Length > 0 || endStream)
 0804            {
 0805                http2Stream?.OnResponseData(frameData, endStream);
 0806            }
 807
 0808            if (frameData.Length > 0)
 0809            {
 0810                bool windowUpdateSent = ExtendWindow(frameData.Length);
 0811                if (http2Stream is not null && !endStream)
 0812                {
 0813                    _rttEstimator.OnDataOrHeadersReceived(this, sendWindowUpdateBeforePing: !windowUpdateSent);
 0814                }
 0815            }
 816
 0817            _incomingBuffer.Discard(frameHeader.PayloadLength);
 0818        }
 819
 820        private void ProcessSettingsFrame(FrameHeader frameHeader, bool initialFrame = false)
 0821        {
 0822            Debug.Assert(frameHeader.Type == FrameType.Settings);
 823
 0824            if (frameHeader.StreamId != 0)
 0825            {
 0826                ThrowProtocolError();
 827            }
 828
 0829            if (frameHeader.AckFlag)
 0830            {
 0831                if (frameHeader.PayloadLength != 0)
 0832                {
 0833                    ThrowProtocolError(Http2ProtocolErrorCode.FrameSizeError);
 834                }
 835
 0836                if (_receivedSettingsAck)
 0837                {
 0838                    ThrowProtocolError();
 839                }
 840
 841                // We only send SETTINGS once initially, so we don't need to do anything in response to the ACK.
 842                // Just remember that we received one and we won't be expecting any more.
 0843                _receivedSettingsAck = true;
 0844                _rttEstimator.OnInitialSettingsAckReceived(this);
 0845            }
 846            else
 0847            {
 0848                if ((frameHeader.PayloadLength % 6) != 0)
 0849                {
 0850                    ThrowProtocolError(Http2ProtocolErrorCode.FrameSizeError);
 851                }
 852
 853                // Parse settings and process the ones we care about.
 0854                ReadOnlySpan<byte> settings = _incomingBuffer.ActiveSpan.Slice(0, frameHeader.PayloadLength);
 0855                bool maxConcurrentStreamsReceived = false;
 0856                while (settings.Length > 0)
 0857                {
 0858                    Debug.Assert((settings.Length % 6) == 0);
 859
 0860                    ushort settingId = BinaryPrimitives.ReadUInt16BigEndian(settings);
 0861                    settings = settings.Slice(2);
 0862                    uint settingValue = BinaryPrimitives.ReadUInt32BigEndian(settings);
 0863                    settings = settings.Slice(4);
 864
 0865                    if (NetEventSource.Log.IsEnabled()) Trace($"Applying setting {(SettingId)settingId}={settingValue}")
 866
 0867                    switch ((SettingId)settingId)
 868                    {
 869                        case SettingId.MaxConcurrentStreams:
 870                            // Only memorize the value for future connections if it's lower than what we're
 871                            // configured to start with. SocketsHttpHandler.InitialHttp2MaxConcurrentStreams
 872                            // acts as the upper bound for what every new connection starts with.
 0873                            if (settingValue < _pool.Settings._initialHttp2MaxConcurrentStreams)
 0874                            {
 0875                                _pool._lastSeenHttp2MaxConcurrentStreams = settingValue;
 0876                            }
 877
 0878                            ChangeMaxConcurrentStreams(settingValue);
 0879                            maxConcurrentStreamsReceived = true;
 0880                            break;
 881
 882                        case SettingId.InitialWindowSize:
 0883                            if (settingValue > 0x7FFFFFFF)
 0884                            {
 0885                                ThrowProtocolError(Http2ProtocolErrorCode.FlowControlError);
 886                            }
 887
 0888                            ChangeInitialWindowSize((int)settingValue);
 0889                            break;
 890
 891                        case SettingId.MaxFrameSize:
 0892                            if (settingValue < 16384 || settingValue > 16777215)
 0893                            {
 0894                                ThrowProtocolError();
 895                            }
 896
 897                            // We don't actually store this value; we always send frames of the minimum size (16K).
 0898                            break;
 899
 900                        case SettingId.EnableConnect:
 0901                            if (settingValue == 1)
 0902                            {
 0903                                IsConnectEnabled = true;
 0904                            }
 0905                            else if (settingValue == 0 && IsConnectEnabled)
 0906                            {
 907                                // Accroding to RFC: a sender MUST NOT send a SETTINGS_ENABLE_CONNECT_PROTOCOL parameter
 908                                // with the value of 0 after previously sending a value of 1.
 909                                // https://datatracker.ietf.org/doc/html/rfc8441#section-3
 0910                                ThrowProtocolError();
 911                            }
 0912                            break;
 913
 914                        case SettingId.MaxHeaderListSize:
 0915                            _maxHeaderListSize = settingValue;
 0916                            _pool._lastSeenHttp2MaxHeaderListSize = _maxHeaderListSize;
 0917                            break;
 918
 919                        default:
 920                            // All others are ignored because we don't care about them.
 921                            // Note, per RFC, unknown settings IDs should be ignored.
 0922                            break;
 923                    }
 0924                }
 925
 0926                if (initialFrame)
 0927                {
 0928                    if (!maxConcurrentStreamsReceived)
 0929                    {
 930                        // Set to 'infinite' because MaxConcurrentStreams was not set on the initial SETTINGS frame.
 0931                        ChangeMaxConcurrentStreams(int.MaxValue);
 0932                    }
 933
 0934                    if (_initialSettingsReceived is null)
 0935                    {
 0936                        Interlocked.CompareExchange(ref _initialSettingsReceived, s_settingsReceivedSingleton, null);
 0937                    }
 938                    // Set result in case if CompareExchange lost the race
 0939                    InitialSettingsReceived.TrySetResult(true);
 0940                }
 941
 0942                _incomingBuffer.Discard(frameHeader.PayloadLength);
 943
 944                // Send acknowledgement
 945                // Don't wait for completion, which could happen asynchronously.
 0946                QueueSettingsAck();
 0947            }
 0948        }
 949
 950        private void ChangeMaxConcurrentStreams(uint newValue)
 0951        {
 0952            lock (SyncObject)
 0953            {
 0954                if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(newValue)}={newValue}, {nameof(_streamsInUse)}={_str
 955
 0956                Debug.Assert(_availableStreamsWaiter is null || _streamsInUse >= _maxConcurrentStreams);
 957
 0958                _maxConcurrentStreams = newValue;
 0959                if (_streamsInUse < _maxConcurrentStreams)
 0960                {
 0961                    SignalAvailableStreamsWaiter(true);
 0962                }
 0963            }
 0964        }
 965
 966        private void ChangeInitialWindowSize(int newSize)
 0967        {
 0968            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(newSize)}={newSize}");
 0969            Debug.Assert(newSize >= 0);
 970
 0971            lock (SyncObject)
 0972            {
 0973                int delta = newSize - _initialServerStreamWindowSize;
 0974                _initialServerStreamWindowSize = newSize;
 975
 976                // Adjust existing streams
 0977                foreach (KeyValuePair<int, Http2Stream> kvp in _httpStreams)
 0978                {
 0979                    kvp.Value.OnWindowUpdate(delta);
 0980                }
 0981            }
 0982        }
 983
 984        private void ProcessPriorityFrame(FrameHeader frameHeader)
 0985        {
 0986            Debug.Assert(frameHeader.Type == FrameType.Priority);
 987
 0988            if (frameHeader.StreamId == 0 || frameHeader.PayloadLength != FrameHeader.PriorityInfoLength)
 0989            {
 0990                ThrowProtocolError();
 991            }
 992
 993            // Ignore priority info.
 994
 0995            _incomingBuffer.Discard(frameHeader.PayloadLength);
 0996        }
 997
 998        private void ProcessPingFrame(FrameHeader frameHeader)
 0999        {
 01000            Debug.Assert(frameHeader.Type == FrameType.Ping);
 1001
 01002            if (frameHeader.StreamId != 0)
 01003            {
 01004                ThrowProtocolError();
 1005            }
 1006
 01007            if (frameHeader.PayloadLength != FrameHeader.PingLength)
 01008            {
 01009                ThrowProtocolError(Http2ProtocolErrorCode.FrameSizeError);
 1010            }
 1011
 1012            // We don't wait for SendPingAckAsync to complete before discarding
 1013            // the incoming buffer, so we need to take a copy of the data. Read
 1014            // it as a big-endian integer here to avoid allocating an array.
 01015            Debug.Assert(sizeof(long) == FrameHeader.PingLength);
 01016            ReadOnlySpan<byte> pingContent = _incomingBuffer.ActiveSpan.Slice(0, FrameHeader.PingLength);
 01017            long pingContentLong = BinaryPrimitives.ReadInt64BigEndian(pingContent);
 1018
 01019            if (NetEventSource.Log.IsEnabled()) Trace($"Received PING frame, content:{pingContentLong} ack: {frameHeader
 1020
 01021            if (frameHeader.AckFlag)
 01022            {
 01023                ProcessPingAck(pingContentLong);
 01024            }
 1025            else
 01026            {
 01027                QueuePing(pingContentLong, isAck: true);
 01028            }
 01029            _incomingBuffer.Discard(frameHeader.PayloadLength);
 01030        }
 1031
 1032        private void ProcessWindowUpdateFrame(FrameHeader frameHeader)
 01033        {
 01034            Debug.Assert(frameHeader.Type == FrameType.WindowUpdate);
 1035
 01036            if (frameHeader.PayloadLength != FrameHeader.WindowUpdateLength)
 01037            {
 01038                ThrowProtocolError(Http2ProtocolErrorCode.FrameSizeError);
 1039            }
 1040
 01041            int amount = BinaryPrimitives.ReadInt32BigEndian(_incomingBuffer.ActiveSpan) & 0x7FFFFFFF;
 01042            if (NetEventSource.Log.IsEnabled()) Trace($"{frameHeader}. {nameof(amount)}={amount}");
 1043
 01044            Debug.Assert(amount >= 0);
 01045            if (amount == 0)
 01046            {
 01047                ThrowProtocolError();
 1048            }
 1049
 01050            _incomingBuffer.Discard(frameHeader.PayloadLength);
 1051
 01052            if (frameHeader.StreamId == 0)
 01053            {
 01054                _connectionWindow.AdjustCredit(amount);
 01055            }
 1056            else
 01057            {
 01058                Http2Stream? http2Stream = GetStream(frameHeader.StreamId);
 01059                if (http2Stream == null)
 01060                {
 1061                    // Ignore invalid stream ID, as per RFC
 01062                    return;
 1063                }
 1064
 01065                http2Stream.OnWindowUpdate(amount);
 01066            }
 01067        }
 1068
 1069        private void ProcessRstStreamFrame(FrameHeader frameHeader)
 01070        {
 01071            Debug.Assert(frameHeader.Type == FrameType.RstStream);
 1072
 01073            if (frameHeader.PayloadLength != FrameHeader.RstStreamLength)
 01074            {
 01075                ThrowProtocolError(Http2ProtocolErrorCode.FrameSizeError);
 1076            }
 1077
 01078            if (frameHeader.StreamId == 0)
 01079            {
 01080                ThrowProtocolError();
 1081            }
 1082
 01083            Http2Stream? http2Stream = GetStream(frameHeader.StreamId);
 01084            if (http2Stream == null)
 01085            {
 1086                // Ignore invalid stream ID, as per RFC
 01087                _incomingBuffer.Discard(frameHeader.PayloadLength);
 01088                return;
 1089            }
 1090
 01091            var protocolError = (Http2ProtocolErrorCode)BinaryPrimitives.ReadInt32BigEndian(_incomingBuffer.ActiveSpan);
 01092            if (NetEventSource.Log.IsEnabled()) Trace(frameHeader.StreamId, $"{nameof(protocolError)}={protocolError}");
 1093
 01094            _incomingBuffer.Discard(frameHeader.PayloadLength);
 1095
 01096            bool canRetry = protocolError == Http2ProtocolErrorCode.RefusedStream;
 01097            http2Stream.OnReset(HttpProtocolException.CreateHttp2StreamException(protocolError), resetStreamErrorCode: p
 01098        }
 1099
 1100        private void ProcessGoAwayFrame(FrameHeader frameHeader)
 01101        {
 01102            var (lastStreamId, errorCode) = ReadGoAwayFrame(frameHeader);
 1103
 01104            Debug.Assert(lastStreamId >= 0);
 01105            Exception resetException = HttpProtocolException.CreateHttp2ConnectionException(errorCode, SR.net_http_http2
 01106            _goAwayErrorCode = errorCode;
 1107
 1108            // There is no point sending more PING frames for RTT estimation:
 01109            _rttEstimator.OnGoAwayReceived();
 1110
 01111            List<Http2Stream> streamsToAbort = new List<Http2Stream>();
 01112            lock (SyncObject)
 01113            {
 01114                Shutdown();
 1115
 01116                foreach (KeyValuePair<int, Http2Stream> kvp in _httpStreams)
 01117                {
 01118                    int streamId = kvp.Key;
 01119                    Debug.Assert(streamId == kvp.Value.StreamId);
 1120
 01121                    if (streamId > lastStreamId)
 01122                    {
 01123                        streamsToAbort.Add(kvp.Value);
 01124                    }
 01125                }
 01126            }
 1127
 1128            // Avoid calling OnReset under the lock, as it may cause the Http2Stream to call back in to RemoveStream
 01129            foreach (Http2Stream s in streamsToAbort)
 01130            {
 01131                s.OnReset(resetException, canRetry: true);
 01132            }
 01133        }
 1134
 1135        private (int lastStreamId, Http2ProtocolErrorCode errorCode) ReadGoAwayFrame(FrameHeader frameHeader)
 01136        {
 01137            Debug.Assert(frameHeader.Type == FrameType.GoAway);
 1138
 01139            if (frameHeader.PayloadLength < FrameHeader.GoAwayMinLength)
 01140            {
 01141                ThrowProtocolError(Http2ProtocolErrorCode.FrameSizeError);
 1142            }
 1143
 01144            if (frameHeader.StreamId != 0)
 01145            {
 01146                ThrowProtocolError();
 1147            }
 1148
 01149            int lastStreamId = (int)(BinaryPrimitives.ReadUInt32BigEndian(_incomingBuffer.ActiveSpan) & 0x7FFFFFFF);
 01150            Http2ProtocolErrorCode errorCode = (Http2ProtocolErrorCode)BinaryPrimitives.ReadInt32BigEndian(_incomingBuff
 01151            if (NetEventSource.Log.IsEnabled()) Trace(frameHeader.StreamId, $"{nameof(lastStreamId)}={lastStreamId}, {na
 1152
 01153            _incomingBuffer.Discard(frameHeader.PayloadLength);
 1154
 01155            return (lastStreamId, errorCode);
 01156        }
 1157
 1158        internal Task FlushAsync(CancellationToken cancellationToken) =>
 01159            PerformWriteAsync(0, 0, static (_, __) => true, cancellationToken);
 1160
 1161        private abstract class WriteQueueEntry : TaskCompletionSource
 1162        {
 1163            private readonly CancellationTokenRegistration _cancellationRegistration;
 1164
 1165            public WriteQueueEntry(int writeBytes, CancellationToken cancellationToken)
 01166                : base(TaskCreationOptions.RunContinuationsAsynchronously)
 01167            {
 01168                WriteBytes = writeBytes;
 1169
 01170                _cancellationRegistration = cancellationToken.UnsafeRegister(static (s, cancellationToken) =>
 01171                {
 01172                    bool canceled = ((WriteQueueEntry)s!).TrySetCanceled(cancellationToken);
 01173                    Debug.Assert(canceled, "Callback should have been unregistered if the operation was completing succe
 01174                }, this);
 01175            }
 1176
 01177            public int WriteBytes { get; }
 1178
 1179            public bool TryDisableCancellation()
 01180            {
 01181                _cancellationRegistration.Dispose();
 01182                return !Task.IsCanceled;
 01183            }
 1184
 1185            public abstract bool InvokeWriteAction(Memory<byte> writeBuffer);
 1186        }
 1187
 1188        private sealed class WriteQueueEntry<T> : WriteQueueEntry
 1189        {
 1190            private readonly T _state;
 1191            private readonly Func<T, Memory<byte>, bool> _writeAction;
 1192
 1193            public WriteQueueEntry(int writeBytes, T state, Func<T, Memory<byte>, bool> writeAction, CancellationToken c
 01194                : base(writeBytes, cancellationToken)
 01195            {
 01196                _state = state;
 01197                _writeAction = writeAction;
 01198            }
 1199
 1200            public override bool InvokeWriteAction(Memory<byte> writeBuffer)
 01201            {
 01202                return _writeAction(_state, writeBuffer);
 01203            }
 1204        }
 1205
 1206        private Task PerformWriteAsync<T>(int writeBytes, T state, Func<T, Memory<byte>, bool> writeAction, Cancellation
 01207        {
 01208            WriteQueueEntry writeEntry = new WriteQueueEntry<T>(writeBytes, state, writeAction, cancellationToken);
 1209
 01210            if (!_writeChannel.Writer.TryWrite(writeEntry))
 01211            {
 01212                if (_abortException is not null)
 01213                {
 01214                    return Task.FromException(GetRequestAbortedException(_abortException));
 1215                }
 1216
 1217                // We must be trying to send something asynchronously (like RST_STREAM or a PING or a SETTINGS ACK) and 
 1218                // As such, it should not matter that we were not able to actually send the frame.
 1219                // But just in case, throw ObjectDisposedException. Asynchronous callers will ignore the failure.
 01220                Debug.Assert(_shutdown && _streamsInUse == 0);
 01221                return Task.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(new ObjectDisposedException(nameof(
 1222            }
 1223
 01224            return writeEntry.Task;
 01225        }
 1226
 1227        private async Task ProcessOutgoingFramesAsync()
 01228        {
 1229            try
 01230            {
 01231                while (await _writeChannel.Reader.WaitToReadAsync().ConfigureAwait(false))
 01232                {
 01233                    while (_writeChannel.Reader.TryRead(out WriteQueueEntry? writeEntry))
 01234                    {
 01235                        if (_abortException is not null)
 01236                        {
 01237                            if (writeEntry.TryDisableCancellation())
 01238                            {
 01239                                writeEntry.SetException(_abortException);
 01240                            }
 01241                        }
 1242                        else
 01243                        {
 01244                            int writeBytes = writeEntry.WriteBytes;
 1245
 1246                            // If the buffer has already grown to 32k, does not have room for the next request,
 1247                            // and is non-empty, flush the current contents to the wire.
 01248                            int totalBufferLength = _outgoingBuffer.Capacity;
 01249                            if (totalBufferLength >= UnflushedOutgoingBufferSize)
 01250                            {
 01251                                int activeBufferLength = _outgoingBuffer.ActiveLength;
 01252                                if (writeBytes >= totalBufferLength - activeBufferLength)
 01253                                {
 01254                                    await FlushOutgoingBytesAsync().ConfigureAwait(false);
 01255                                }
 01256                            }
 1257
 1258                            // We are ready to process the write, so disable write cancellation now.
 01259                            if (writeEntry.TryDisableCancellation())
 01260                            {
 01261                                _outgoingBuffer.EnsureAvailableSpace(writeBytes);
 1262
 1263                                try
 01264                                {
 01265                                    if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(writeBytes)}={writeBytes}");
 1266
 1267                                    // Invoke the callback with the supplied state and the target write buffer.
 01268                                    bool flush = writeEntry.InvokeWriteAction(_outgoingBuffer.AvailableMemorySliced(writ
 1269
 01270                                    writeEntry.SetResult();
 1271
 01272                                    _outgoingBuffer.Commit(writeBytes);
 01273                                    _lastPendingWriterShouldFlush |= flush;
 01274                                }
 01275                                catch (Exception e)
 01276                                {
 01277                                    writeEntry.SetException(e);
 01278                                }
 01279                            }
 01280                        }
 01281                    }
 1282
 1283                    // Nothing left in the queue to process.
 1284                    // Flush the write buffer if we need to.
 01285                    if (_lastPendingWriterShouldFlush)
 01286                    {
 01287                        await FlushOutgoingBytesAsync().ConfigureAwait(false);
 01288                    }
 1289
 01290                    if (_outgoingBuffer.ActiveLength == 0)
 01291                    {
 01292                        _outgoingBuffer.ClearAndReturnBuffer();
 01293                    }
 01294                }
 01295            }
 01296            catch (Exception e)
 01297            {
 01298                if (NetEventSource.Log.IsEnabled()) Trace($"Unexpected exception in {nameof(ProcessOutgoingFramesAsync)}
 1299
 01300                Debug.Fail($"Unexpected exception in {nameof(ProcessOutgoingFramesAsync)}: {e}");
 1301            }
 1302            finally
 01303            {
 01304                _outgoingBuffer.Dispose();
 01305            }
 01306        }
 1307
 1308        private void QueueSettingsAck()
 01309        {
 01310            if (!TryIncrementQueuedFireAndForgetFrames())
 01311            {
 01312                return;
 1313            }
 1314
 01315            LogExceptions(PerformWriteAsync(FrameHeader.Size, this, static (thisRef, writeBuffer) =>
 01316            {
 01317                if (NetEventSource.Log.IsEnabled()) thisRef.Trace("Started writing.");
 01318
 01319                FrameHeader.WriteTo(writeBuffer.Span, 0, FrameType.Settings, FrameFlags.Ack, streamId: 0);
 01320
 01321                thisRef.DecrementQueuedFireAndForgetFrames();
 01322
 01323                return true;
 01324            }));
 01325        }
 1326
 1327        /// <param name="pingContent">The 8-byte ping content to send, read as a big-endian integer.</param>
 1328        /// <param name="isAck">Determine whether the frame is ping or ping ack.</param>
 1329        private void QueuePing(long pingContent, bool isAck = false)
 01330        {
 01331            if (!TryIncrementQueuedFireAndForgetFrames())
 01332            {
 01333                return;
 1334            }
 1335
 01336            LogExceptions(PerformWriteAsync(FrameHeader.Size + FrameHeader.PingLength, (thisRef: this, pingContent, isAc
 01337            {
 01338                if (NetEventSource.Log.IsEnabled()) state.thisRef.Trace($"Started writing. {nameof(pingContent)}={state.
 01339
 01340                Debug.Assert(sizeof(long) == FrameHeader.PingLength);
 01341
 01342                Span<byte> span = writeBuffer.Span;
 01343                FrameHeader.WriteTo(span, FrameHeader.PingLength, FrameType.Ping, state.isAck ? FrameFlags.Ack : FrameFl
 01344                BinaryPrimitives.WriteInt64BigEndian(span.Slice(FrameHeader.Size), state.pingContent);
 01345
 01346                state.thisRef.DecrementQueuedFireAndForgetFrames();
 01347
 01348                return true;
 01349            }));
 01350        }
 1351
 1352        private Task SendRstStreamAsync(int streamId, Http2ProtocolErrorCode errorCode) =>
 01353            PerformWriteAsync(FrameHeader.Size + FrameHeader.RstStreamLength, (thisRef: this, streamId, errorCode), stat
 01354            {
 01355                if (NetEventSource.Log.IsEnabled()) s.thisRef.Trace(s.streamId, $"Started writing. {nameof(s.errorCode)}
 01356
 01357                Span<byte> span = writeBuffer.Span;
 01358                FrameHeader.WriteTo(span, FrameHeader.RstStreamLength, FrameType.RstStream, FrameFlags.None, s.streamId)
 01359                BinaryPrimitives.WriteInt32BigEndian(span.Slice(FrameHeader.Size), (int)s.errorCode);
 01360
 01361                return true;
 01362            });
 1363
 1364
 1365        internal void HeartBeat()
 01366        {
 01367            Debug.Assert(!_pool.HasSyncObjLock);
 1368
 01369            if (!_setupComplete)
 01370            {
 1371                // The connection is still being established. It can't send PINGs yet, and a server that
 1372                // never completes the handshake is the connect timeout's responsibility, not ours.
 01373                return;
 1374            }
 1375
 01376            if (_shutdown)
 01377            {
 1378                // The connection is shutting down (e.g. we received a GOAWAY frame), but it may still be
 1379                // processing existing requests. Keep sending PINGs while it does, as that's the only way
 1380                // to detect that the server became unresponsive. Once the last stream completes, the
 1381                // connection is torn down and unregistered from the pool, so we'll stop being called.
 01382                lock (SyncObject)
 01383                {
 01384                    if (_streamsInUse == 0)
 01385                    {
 01386                        return;
 1387                    }
 01388                }
 01389            }
 1390
 1391            try
 01392            {
 01393                VerifyKeepAlive();
 01394            }
 01395            catch (Exception e)
 01396            {
 01397                if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(HeartBeat)}: {e.Message}");
 1398
 01399                Abort(e);
 01400            }
 01401        }
 1402
 1403        private static (ReadOnlyMemory<byte> first, ReadOnlyMemory<byte> rest) SplitBuffer(ReadOnlyMemory<byte> buffer, 
 01404            buffer.Length > maxSize ?
 01405                (buffer.Slice(0, maxSize), buffer.Slice(maxSize)) :
 01406                (buffer, Memory<byte>.Empty);
 1407
 1408        private void WriteIndexedHeader(int index, ref ArrayBuffer headerBuffer)
 01409        {
 01410            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(index)}={index}");
 1411
 1412            int bytesWritten;
 01413            while (!HPackEncoder.EncodeIndexedHeaderField(index, headerBuffer.AvailableSpan, out bytesWritten))
 01414            {
 01415                headerBuffer.Grow();
 01416            }
 1417
 01418            headerBuffer.Commit(bytesWritten);
 01419        }
 1420
 1421        private void WriteIndexedHeader(int index, string value, ref ArrayBuffer headerBuffer)
 01422        {
 01423            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(index)}={index}, {nameof(value)}={value}");
 1424
 1425            int bytesWritten;
 01426            while (!HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexing(index, value, valueEncoding: null, headerBuffer
 01427            {
 01428                headerBuffer.Grow();
 01429            }
 1430
 01431            headerBuffer.Commit(bytesWritten);
 01432        }
 1433
 1434        private void WriteLiteralHeader(string name, ReadOnlySpan<string> values, Encoding? valueEncoding, ref ArrayBuff
 01435        {
 01436            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(name)}={name}, {nameof(values)}={string.Join(", ", value
 1437
 1438            int bytesWritten;
 01439            while (!HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingNewName(name, values, HttpHeaderParser.DefaultSe
 01440            {
 01441                headerBuffer.Grow();
 01442            }
 1443
 01444            headerBuffer.Commit(bytesWritten);
 01445        }
 1446
 1447        private void WriteLiteralHeaderValues(ReadOnlySpan<string> values, byte[]? separator, Encoding? valueEncoding, r
 01448        {
 01449            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(values)}={string.Join(Encoding.ASCII.GetString(separator
 1450
 1451            int bytesWritten;
 01452            while (!HPackEncoder.EncodeStringLiterals(values, separator, valueEncoding, headerBuffer.AvailableSpan, out 
 01453            {
 01454                headerBuffer.Grow();
 01455            }
 1456
 01457            headerBuffer.Commit(bytesWritten);
 01458        }
 1459
 1460        private void WriteLiteralHeaderValue(string value, Encoding? valueEncoding, ref ArrayBuffer headerBuffer)
 01461        {
 01462            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(value)}={value}");
 1463
 1464            int bytesWritten;
 01465            while (!HPackEncoder.EncodeStringLiteral(value, valueEncoding, headerBuffer.AvailableSpan, out bytesWritten)
 01466            {
 01467                headerBuffer.Grow();
 01468            }
 1469
 01470            headerBuffer.Commit(bytesWritten);
 01471        }
 1472
 1473        private void WriteBytes(ReadOnlySpan<byte> bytes, ref ArrayBuffer headerBuffer)
 01474        {
 01475            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(bytes.Length)}={bytes.Length}");
 1476
 01477            headerBuffer.EnsureAvailableSpace(bytes.Length);
 01478            bytes.CopyTo(headerBuffer.AvailableSpan);
 01479            headerBuffer.Commit(bytes.Length);
 01480        }
 1481
 1482        private int WriteHeaderCollection(HttpRequestMessage request, HttpHeaders headers, ref ArrayBuffer headerBuffer)
 01483        {
 01484            if (NetEventSource.Log.IsEnabled()) Trace("");
 1485
 01486            HeaderEncodingSelector<HttpRequestMessage>? encodingSelector = _pool.Settings._requestHeaderEncodingSelector
 1487
 01488            ref string[]? tmpHeaderValuesArray = ref t_headerValues;
 1489
 01490            ReadOnlySpan<HeaderEntry> entries = headers.GetEntries();
 01491            int headerListSize = entries.Length * HeaderField.RfcOverhead;
 1492
 01493            foreach (HeaderEntry header in entries)
 01494            {
 01495                int headerValuesCount = HttpHeaders.GetStoreValuesIntoStringArray(header.Key, header.Value, ref tmpHeade
 01496                Debug.Assert(headerValuesCount > 0, "No values for header??");
 01497                ReadOnlySpan<string> headerValues = tmpHeaderValuesArray.AsSpan(0, headerValuesCount);
 1498
 01499                Encoding? valueEncoding = encodingSelector?.Invoke(header.Key.Name, request);
 1500
 01501                KnownHeader? knownHeader = header.Key.KnownHeader;
 01502                if (knownHeader != null)
 01503                {
 1504                    // The Host header is not sent for HTTP2 because we send the ":authority" pseudo-header instead
 1505                    // (see pseudo-header handling below in WriteHeaders).
 1506                    // The Connection, Upgrade and ProxyConnection headers are also not supported in HTTP2.
 01507                    if (knownHeader != KnownHeaders.Host && knownHeader != KnownHeaders.Connection && knownHeader != Kno
 01508                    {
 1509                        // The length of the encoded name may be shorter than the actual name.
 1510                        // Ensure that headerListSize is always >= of the actual size.
 01511                        headerListSize += knownHeader.Name.Length;
 1512
 01513                        if (knownHeader == KnownHeaders.TE)
 01514                        {
 1515                            // HTTP/2 allows only 'trailers' TE header. rfc7540 8.1.2.2
 01516                            foreach (string value in headerValues)
 01517                            {
 01518                                if (string.Equals(value, "trailers", StringComparison.OrdinalIgnoreCase))
 01519                                {
 01520                                    WriteBytes(knownHeader.Http2EncodedName, ref headerBuffer);
 01521                                    WriteLiteralHeaderValue(value, valueEncoding, ref headerBuffer);
 01522                                    break;
 1523                                }
 01524                            }
 01525                            continue;
 1526                        }
 1527
 1528                        // Extended connect requests will use the response content stream for bidirectional communicatio
 1529                        // We will ignore any content set for such requests in Http2Stream.SendRequestBodyAsync, as it h
 1530                        // Drop the Content-Length header as well in the unlikely case it was set.
 01531                        if (knownHeader == KnownHeaders.ContentLength && request.IsExtendedConnectRequest)
 01532                        {
 01533                            continue;
 1534                        }
 1535
 1536                        // For all other known headers, send them via their pre-encoded name and the associated value.
 01537                        WriteBytes(knownHeader.Http2EncodedName, ref headerBuffer);
 1538
 01539                        byte[]? separator = headerValues.Length > 1 ? header.Key.SeparatorBytes : null;
 1540
 01541                        WriteLiteralHeaderValues(headerValues, separator, valueEncoding, ref headerBuffer);
 01542                    }
 01543                }
 1544                else
 01545                {
 1546                    // The header is not known: fall back to just encoding the header name and value(s).
 01547                    WriteLiteralHeader(header.Key.Name, headerValues, valueEncoding, ref headerBuffer);
 01548                }
 01549            }
 1550
 01551            return headerListSize;
 01552        }
 1553
 1554        private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuffer)
 01555        {
 01556            if (NetEventSource.Log.IsEnabled()) Trace("");
 1557
 01558            WriteBytes(request.Method.Http2EncodedBytes, ref headerBuffer);
 1559
 01560            WriteIndexedHeader(_pool.IsSecure ? H2StaticTable.SchemeHttps : H2StaticTable.SchemeHttp, ref headerBuffer);
 1561
 01562            if (request.HasHeaders && request.Headers.Host is string host)
 01563            {
 01564                WriteIndexedHeader(H2StaticTable.Authority, host, ref headerBuffer);
 01565            }
 1566            else
 01567            {
 01568                WriteBytes(_pool._http2EncodedAuthorityHostHeader, ref headerBuffer);
 01569            }
 1570
 01571            Debug.Assert(request.RequestUri != null);
 01572            string pathAndQuery = request.RequestUri.PathAndQuery;
 01573            if (pathAndQuery == "/")
 01574            {
 01575                WriteIndexedHeader(H2StaticTable.PathSlash, ref headerBuffer);
 01576            }
 1577            else
 01578            {
 01579                WriteIndexedHeader(H2StaticTable.PathSlash, pathAndQuery, ref headerBuffer);
 01580            }
 1581
 01582            int headerListSize = 3 * HeaderField.RfcOverhead; // Method, Authority, Path
 1583
 01584            if (request.HasHeaders)
 01585            {
 1586                // HTTP2 does not support Transfer-Encoding: chunked, so disable this on the request.
 01587                if (request.Headers.TransferEncodingChunked == true)
 01588                {
 01589                    request.Headers.TransferEncodingChunked = false;
 01590                }
 1591
 01592                if (request.Headers.Protocol is string protocol)
 01593                {
 01594                    WriteBytes(ProtocolLiteralHeaderBytes, ref headerBuffer);
 01595                    Encoding? protocolEncoding = _pool.Settings._requestHeaderEncodingSelector?.Invoke(":protocol", requ
 01596                    WriteLiteralHeaderValue(protocol, protocolEncoding, ref headerBuffer);
 01597                    headerListSize += HeaderField.RfcOverhead;
 01598                }
 1599
 01600                headerListSize += WriteHeaderCollection(request, request.Headers, ref headerBuffer);
 01601            }
 1602
 1603            // Determine cookies to send.
 01604            if (_pool.Settings._useCookies)
 01605            {
 01606                string cookiesFromContainer = _pool.Settings._cookieContainer!.GetCookieHeader(request.RequestUri);
 01607                if (cookiesFromContainer != string.Empty)
 01608                {
 01609                    WriteBytes(KnownHeaders.Cookie.Http2EncodedName, ref headerBuffer);
 01610                    Encoding? cookieEncoding = _pool.Settings._requestHeaderEncodingSelector?.Invoke(KnownHeaders.Cookie
 01611                    WriteLiteralHeaderValue(cookiesFromContainer, cookieEncoding, ref headerBuffer);
 01612                    headerListSize += HttpKnownHeaderNames.Cookie.Length + HeaderField.RfcOverhead;
 01613                }
 01614            }
 1615
 01616            if (request.Content == null)
 01617            {
 1618                // Write out Content-Length: 0 header to indicate no body,
 1619                // unless this is a method that never has a body.
 01620                if (request.Method.MustHaveRequestBody)
 01621                {
 01622                    WriteBytes(KnownHeaders.ContentLength.Http2EncodedName, ref headerBuffer);
 01623                    WriteLiteralHeaderValue("0", valueEncoding: null, ref headerBuffer);
 01624                    headerListSize += HttpKnownHeaderNames.ContentLength.Length + HeaderField.RfcOverhead;
 01625                }
 01626            }
 1627            else
 01628            {
 01629                headerListSize += WriteHeaderCollection(request, request.Content.Headers, ref headerBuffer);
 01630            }
 1631
 1632            // The headerListSize is an approximation of the total header length.
 1633            // This is acceptable as long as the value is always >= the actual length.
 1634            // We must avoid ever sending more than the server allowed.
 1635            // This approach must be revisited if we ever support the dynamic table or compression when sending requests
 01636            headerListSize += headerBuffer.ActiveLength;
 1637
 01638            uint maxHeaderListSize = _maxHeaderListSize;
 01639            if ((uint)headerListSize > maxHeaderListSize)
 01640            {
 01641                throw new HttpRequestException(SR.Format(SR.net_http_request_headers_exceeded_length, maxHeaderListSize)
 1642            }
 01643        }
 1644
 1645        private void AddStream(Http2Stream http2Stream)
 01646        {
 01647            lock (SyncObject)
 01648            {
 01649                if (_nextStream == MaxStreamId)
 01650                {
 1651                    // We have exhausted StreamIds. Shut down the connection.
 01652                    Shutdown();
 01653                }
 1654
 01655                if (_abortException is not null)
 01656                {
 01657                    throw GetRequestAbortedException(_abortException);
 1658                }
 1659
 01660                if (_shutdown)
 01661                {
 1662                    // The connection has shut down. Throw a retryable exception so that this request will be handled on
 01663                    ThrowRetry(SR.net_http_server_shutdown);
 1664                }
 1665
 01666                if (_streamsInUse > _maxConcurrentStreams)
 01667                {
 1668                    // The server must have sent a downward adjustment to SETTINGS_MAX_CONCURRENT_STREAMS, so our previo
 1669                    // We might want a better exception message here, but in general the user shouldn't see this anyway 
 01670                    ThrowRetry(SR.net_http_request_aborted);
 1671                }
 1672
 01673                if (_httpStreams.Count == 0)
 01674                {
 01675                    MarkConnectionAsNotIdle();
 01676                }
 1677
 1678                // Now that we're holding the lock, configure the stream.  The lock must be held while
 1679                // assigning the stream ID to ensure only one stream gets an ID, and it must be held
 1680                // across setting the initial window size (available credit) and storing the stream into
 1681                // collection such that window size updates are able to atomically affect all known streams.
 01682                http2Stream.Initialize(_nextStream, _initialServerStreamWindowSize);
 1683
 1684                // Client-initiated streams are always odd-numbered, so increase by 2.
 01685                _nextStream += 2;
 1686
 01687                _httpStreams.Add(http2Stream.StreamId, http2Stream);
 01688            }
 01689        }
 1690
 1691        private async ValueTask<Http2Stream> SendHeadersAsync(HttpRequestMessage request, CancellationToken cancellation
 01692        {
 01693            ArrayBuffer headerBuffer = default;
 1694            try
 01695            {
 01696                if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestHeadersStart(Id);
 1697
 1698                // Serialize headers to a temporary buffer, and do as much work to prepare to send the headers as we can
 1699                // before taking the write lock.
 01700                headerBuffer = new ArrayBuffer(InitialConnectionBufferSize, usePool: true);
 01701                WriteHeaders(request, ref headerBuffer);
 01702                ReadOnlyMemory<byte> headerBytes = headerBuffer.ActiveMemory;
 01703                Debug.Assert(headerBytes.Length > 0);
 1704
 1705                // Calculate the total number of bytes we're going to use (content + headers).
 01706                int frameCount = ((headerBytes.Length - 1) / FrameHeader.MaxPayloadLength) + 1;
 01707                int totalSize = headerBytes.Length + (frameCount * FrameHeader.Size);
 1708
 1709                // Construct and initialize the new Http2Stream instance.  It's stream ID must be set below
 1710                // before the instance is used and stored into the dictionary.  However, we construct it here
 1711                // so as to avoid the allocation and initialization expense while holding multiple locks.
 01712                var http2Stream = new Http2Stream(request, this);
 1713
 1714                // Start the write.  This serializes access to write to the connection, and ensures that HEADERS
 1715                // and CONTINUATION frames stay together, as they must do. We use the lock as well to ensure new
 1716                // streams are created and started in order.
 01717                await PerformWriteAsync(totalSize, (thisRef: this, http2Stream, headerBytes, endStream: (request.Content
 01718                {
 01719                    s.thisRef.AddStream(s.http2Stream);
 01720
 01721                    if (NetEventSource.Log.IsEnabled()) s.thisRef.Trace(s.http2Stream.StreamId, $"Started writing. Total
 01722
 01723                    Span<byte> span = writeBuffer.Span;
 01724
 01725                    // Copy the HEADERS frame.
 01726                    ReadOnlyMemory<byte> current, remaining;
 01727                    (current, remaining) = SplitBuffer(s.headerBytes, FrameHeader.MaxPayloadLength);
 01728                    FrameFlags flags = (remaining.Length == 0 ? FrameFlags.EndHeaders : FrameFlags.None);
 01729                    flags |= (s.endStream ? FrameFlags.EndStream : FrameFlags.None);
 01730                    FrameHeader.WriteTo(span, current.Length, FrameType.Headers, flags, s.http2Stream.StreamId);
 01731                    span = span.Slice(FrameHeader.Size);
 01732                    current.Span.CopyTo(span);
 01733                    span = span.Slice(current.Length);
 01734                    if (NetEventSource.Log.IsEnabled()) s.thisRef.Trace(s.http2Stream.StreamId, $"Wrote HEADERS frame. L
 01735
 01736                    // Copy CONTINUATION frames, if any.
 01737                    while (remaining.Length > 0)
 01738                    {
 01739                        (current, remaining) = SplitBuffer(remaining, FrameHeader.MaxPayloadLength);
 01740                        flags = remaining.Length == 0 ? FrameFlags.EndHeaders : FrameFlags.None;
 01741
 01742                        FrameHeader.WriteTo(span, current.Length, FrameType.Continuation, flags, s.http2Stream.StreamId)
 01743                        span = span.Slice(FrameHeader.Size);
 01744                        current.Span.CopyTo(span);
 01745                        span = span.Slice(current.Length);
 01746                        if (NetEventSource.Log.IsEnabled()) s.thisRef.Trace(s.http2Stream.StreamId, $"Wrote CONTINUATION
 01747                    }
 01748
 01749                    Debug.Assert(span.Length == 0);
 01750
 01751                    return s.mustFlush || s.endStream;
 01752                }, cancellationToken).ConfigureAwait(false);
 1753
 01754                if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestHeadersStop();
 1755
 01756                return http2Stream;
 1757            }
 01758            catch
 01759            {
 01760                ReleaseStream();
 01761                throw;
 1762            }
 1763            finally
 01764            {
 01765                headerBuffer.Dispose();
 01766            }
 01767        }
 1768
 1769        private async Task SendStreamDataAsync(int streamId, ReadOnlyMemory<byte> buffer, bool finalFlush, CancellationT
 01770        {
 01771            ReadOnlyMemory<byte> remaining = buffer;
 1772
 01773            while (remaining.Length > 0)
 01774            {
 1775                // Once credit had been granted, we want to actually consume those bytes.
 01776                int frameSize = Math.Min(remaining.Length, FrameHeader.MaxPayloadLength);
 01777                frameSize = await _connectionWindow.RequestCreditAsync(frameSize, cancellationToken).ConfigureAwait(fals
 1778
 1779                ReadOnlyMemory<byte> current;
 01780                (current, remaining) = SplitBuffer(remaining, frameSize);
 1781
 01782                bool flush = false;
 01783                if (finalFlush && remaining.Length == 0)
 01784                {
 01785                    flush = true;
 01786                }
 1787
 1788                // Force a flush if we are out of credit, because we don't know that we will be sending more data any ti
 01789                if (!_connectionWindow.IsCreditAvailable)
 01790                {
 01791                    flush = true;
 01792                }
 1793
 1794                try
 01795                {
 01796                    await PerformWriteAsync(FrameHeader.Size + current.Length, (thisRef: this, streamId, current, flush)
 01797                    {
 01798                        // Invoked while holding the lock:
 01799                        if (NetEventSource.Log.IsEnabled()) s.thisRef.Trace(s.streamId, $"Started writing. {nameof(write
 01800
 01801                        FrameHeader.WriteTo(writeBuffer.Span, s.current.Length, FrameType.Data, FrameFlags.None, s.strea
 01802                        s.current.CopyTo(writeBuffer.Slice(FrameHeader.Size));
 01803
 01804                        return s.flush;
 01805                    }, cancellationToken).ConfigureAwait(false);
 01806                }
 01807                catch
 01808                {
 1809                    // Invoked if waiting for the lock is canceled (in that case, we need to return the credit that we h
 01810                    _connectionWindow.AdjustCredit(frameSize);
 01811                    throw;
 1812                }
 01813            }
 01814        }
 1815
 1816        private Task SendEndStreamAsync(int streamId) =>
 01817            PerformWriteAsync(FrameHeader.Size, (thisRef: this, streamId), static (s, writeBuffer) =>
 01818            {
 01819                if (NetEventSource.Log.IsEnabled()) s.thisRef.Trace(s.streamId, "Started writing.");
 01820
 01821                FrameHeader.WriteTo(writeBuffer.Span, 0, FrameType.Data, FrameFlags.EndStream, s.streamId);
 01822
 01823                return true; // finished sending request body, so flush soon (but ok to wait for pending packets)
 01824            });
 1825
 1826        private Task SendWindowUpdateAsync(int streamId, int amount)
 01827        {
 1828            // We update both the connection-level and stream-level windows at the same time
 01829            Debug.Assert(amount > 0);
 01830            return PerformWriteAsync(FrameHeader.Size + FrameHeader.WindowUpdateLength, (thisRef: this, streamId, amount
 01831            {
 01832                if (NetEventSource.Log.IsEnabled()) s.thisRef.Trace(s.streamId, $"Started writing. {nameof(s.amount)}={s
 01833
 01834                Span<byte> span = writeBuffer.Span;
 01835                FrameHeader.WriteTo(span, FrameHeader.WindowUpdateLength, FrameType.WindowUpdate, FrameFlags.None, s.str
 01836                BinaryPrimitives.WriteInt32BigEndian(span.Slice(FrameHeader.Size), s.amount);
 01837
 01838                return true;
 01839            });
 01840        }
 1841
 1842        private bool ExtendWindow(int amount)
 01843        {
 01844            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(amount)}={amount}");
 01845            Debug.Assert(amount > 0);
 01846            Debug.Assert(_pendingWindowUpdate < ConnectionWindowThreshold);
 1847
 01848            _pendingWindowUpdate += amount;
 01849            if (_pendingWindowUpdate < ConnectionWindowThreshold)
 01850            {
 01851                if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(_pendingWindowUpdate)} {_pendingWindowUpdate} < {Con
 01852                return false;
 1853            }
 1854
 01855            int windowUpdateSize = _pendingWindowUpdate;
 01856            _pendingWindowUpdate = 0;
 1857
 01858            LogExceptions(SendWindowUpdateAsync(0, windowUpdateSize));
 01859            return true;
 01860        }
 1861
 1862        private bool ForceSendConnectionWindowUpdate()
 01863        {
 01864            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(_pendingWindowUpdate)}={_pendingWindowUpdate}");
 01865            if (_pendingWindowUpdate == 0) return false;
 1866
 01867            LogExceptions(SendWindowUpdateAsync(0, _pendingWindowUpdate));
 01868            _pendingWindowUpdate = 0;
 01869            return true;
 01870        }
 1871
 1872        private bool TryIncrementQueuedFireAndForgetFrames()
 01873        {
 01874            if (Interlocked.Increment(ref _queuedFireAndForgetFrames) > MaxQueuedFireAndForgetFrames)
 01875            {
 01876                if (NetEventSource.Log.IsEnabled()) this.Trace("Number of untransmitted PING and SETTING frames exceeded
 1877
 1878                // Close connection when there is too much outstanding frames
 01879                var ex = new HttpIOException(HttpRequestError.Unknown, SR.net_http_http2_frame_limit_exceeded);
 01880                Abort(ex);
 1881
 01882                return false;
 1883            }
 1884
 01885            return true;
 01886        }
 1887
 1888        private void DecrementQueuedFireAndForgetFrames()
 01889        {
 01890            int pending = Interlocked.Decrement(ref _queuedFireAndForgetFrames);
 01891            Debug.Assert(pending >= 0);
 01892        }
 1893
 1894        /// <summary>Abort all streams and cause further processing to fail.</summary>
 1895        /// <param name="abortException">Exception causing Abort to be called.</param>
 1896        private void Abort(Exception abortException)
 01897        {
 01898            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(abortException)}=={abortException}");
 1899
 1900            // The connection has failed, e.g. failed IO or a connection-level protocol error.
 01901            List<Http2Stream> streamsToAbort = new List<Http2Stream>();
 01902            lock (SyncObject)
 01903            {
 01904                if (_abortException is not null)
 01905                {
 01906                    if (NetEventSource.Log.IsEnabled()) Trace($"Abort called while already aborting. {nameof(abortExcept
 01907                    return;
 1908                }
 1909
 01910                _abortException = abortException;
 1911
 01912                Shutdown();
 1913
 01914                foreach (KeyValuePair<int, Http2Stream> kvp in _httpStreams)
 01915                {
 01916                    int streamId = kvp.Key;
 01917                    Debug.Assert(streamId == kvp.Value.StreamId);
 1918
 01919                    streamsToAbort.Add(kvp.Value);
 01920                }
 01921            }
 1922
 1923            // Avoid calling OnReset under the lock, as it may cause the Http2Stream to call back in to RemoveStream
 01924            foreach (Http2Stream s in streamsToAbort)
 01925            {
 01926                s.OnReset(_abortException);
 01927            }
 01928        }
 1929
 1930        private void FinalTeardown()
 01931        {
 01932            if (NetEventSource.Log.IsEnabled()) Trace("");
 1933
 01934            Debug.Assert(_shutdown);
 01935            Debug.Assert(_streamsInUse == 0);
 1936
 01937            GC.SuppressFinalize(this);
 01938            _stream.Dispose();
 1939
 01940            _connectionWindow.Dispose();
 01941            bool completed = _writeChannel.Writer.TryComplete();
 01942            Debug.Assert(completed, "FinalTeardown was called twice");
 1943
 1944            // We're not disposing the _incomingBuffer and _outgoingBuffer here as they may still be in use by
 1945            // ProcessIncomingFramesAsync and ProcessOutgoingFramesAsync respectively, and those methods are
 1946            // responsible for returning the buffers.
 1947
 01948            _pool.RemoveHttp2ConnectionFromHeartBeat(this);
 1949
 01950            MarkConnectionAsClosed();
 01951        }
 1952
 1953        public override void Dispose()
 01954        {
 01955            lock (SyncObject)
 01956            {
 01957                Shutdown();
 01958            }
 01959        }
 1960
 1961        private enum FrameType : byte
 1962        {
 1963            Data = 0,
 1964            Headers = 1,
 1965            Priority = 2,
 1966            RstStream = 3,
 1967            Settings = 4,
 1968            PushPromise = 5,
 1969            Ping = 6,
 1970            GoAway = 7,
 1971            WindowUpdate = 8,
 1972            Continuation = 9,
 1973            AltSvc = 10,
 1974
 1975            Last = 10
 1976        }
 1977
 1978        private readonly struct FrameHeader
 1979        {
 1980            public readonly int PayloadLength;
 1981            public readonly FrameType Type;
 1982            public readonly FrameFlags Flags;
 1983            public readonly int StreamId;
 1984
 1985            public const int Size = 9;
 1986            public const int MaxPayloadLength = 16384;
 1987
 1988            public const int SettingLength = 6;            // per setting (total SETTINGS length must be a multiple of t
 1989            public const int PriorityInfoLength = 5;       // for both PRIORITY frame and priority info within HEADERS
 1990            public const int PingLength = 8;
 1991            public const int WindowUpdateLength = 4;
 1992            public const int RstStreamLength = 4;
 1993            public const int GoAwayMinLength = 8;
 1994
 1995            public FrameHeader(int payloadLength, FrameType type, FrameFlags flags, int streamId)
 01996            {
 01997                Debug.Assert(streamId >= 0);
 1998
 01999                PayloadLength = payloadLength;
 02000                Type = type;
 02001                Flags = flags;
 02002                StreamId = streamId;
 02003            }
 2004
 02005            public bool PaddedFlag => (Flags & FrameFlags.Padded) != 0;
 02006            public bool AckFlag => (Flags & FrameFlags.Ack) != 0;
 02007            public bool EndHeadersFlag => (Flags & FrameFlags.EndHeaders) != 0;
 02008            public bool EndStreamFlag => (Flags & FrameFlags.EndStream) != 0;
 02009            public bool PriorityFlag => (Flags & FrameFlags.Priority) != 0;
 2010
 2011            public static FrameHeader ReadFrom(ReadOnlySpan<byte> buffer)
 02012            {
 02013                Debug.Assert(buffer.Length >= Size);
 2014
 02015                FrameFlags flags = (FrameFlags)buffer[4]; // do first to avoid some bounds checks
 02016                int payloadLength = (buffer[0] << 16) | (buffer[1] << 8) | buffer[2];
 02017                FrameType type = (FrameType)buffer[3];
 02018                int streamId = (int)(BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(5)) & 0x7FFFFFFF);
 2019
 02020                return new FrameHeader(payloadLength, type, flags, streamId);
 02021            }
 2022
 2023            public static void WriteTo(Span<byte> destination, int payloadLength, FrameType type, FrameFlags flags, int 
 02024            {
 02025                Debug.Assert(destination.Length >= Size);
 02026                Debug.Assert(type <= FrameType.Last);
 02027                Debug.Assert((flags & FrameFlags.ValidBits) == flags);
 02028                Debug.Assert((uint)payloadLength <= MaxPayloadLength);
 2029
 2030                // This ordering helps eliminate bounds checks.
 02031                BinaryPrimitives.WriteInt32BigEndian(destination.Slice(5), streamId);
 02032                destination[4] = (byte)flags;
 02033                destination[0] = (byte)((payloadLength & 0x00FF0000) >> 16);
 02034                destination[1] = (byte)((payloadLength & 0x0000FF00) >> 8);
 02035                destination[2] = (byte)(payloadLength & 0x000000FF);
 02036                destination[3] = (byte)type;
 02037            }
 2038
 02039            public override string ToString() => $"StreamId={StreamId}; Type={Type}; Flags={Flags}; PayloadLength={Paylo
 2040        }
 2041
 2042        [Flags]
 2043        private enum FrameFlags : byte
 2044        {
 2045            None = 0,
 2046
 2047            // Some frame types define bits differently.  Define them all here for simplicity.
 2048
 2049            EndStream =     0b00000001,
 2050            Ack =           0b00000001,
 2051            EndHeaders =    0b00000100,
 2052            Padded =        0b00001000,
 2053            Priority =      0b00100000,
 2054
 2055            ValidBits =     0b00101101
 2056        }
 2057
 2058        private enum SettingId : ushort
 2059        {
 2060            HeaderTableSize = 0x1,
 2061            EnablePush = 0x2,
 2062            MaxConcurrentStreams = 0x3,
 2063            InitialWindowSize = 0x4,
 2064            MaxFrameSize = 0x5,
 2065            MaxHeaderListSize = 0x6,
 2066            EnableConnect = 0x8
 2067        }
 2068
 2069        private static TaskCompletionSourceWithCancellation<bool> CreateSuccessfullyCompletedTcs()
 02070        {
 02071            var tcs = new TaskCompletionSourceWithCancellation<bool>();
 02072            tcs.TrySetResult(true);
 02073            return tcs;
 02074        }
 2075
 2076        // Note that this is safe to be called concurrently by multiple threads.
 2077
 2078        public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool async, CancellationToken cance
 02079        {
 02080            request.ConnectionId = Id;
 2081
 02082            Debug.Assert(async);
 02083            Debug.Assert(!_pool.HasSyncObjLock);
 02084            if (NetEventSource.Log.IsEnabled()) Trace($"Sending request: {request}");
 02085            if (ConnectionSetupActivity is not null) ConnectionSetupDistributedTracing.AddConnectionLinkToRequestActivit
 2086
 2087            try
 02088            {
 2089                // Send request headers
 02090                bool shouldExpectContinue = (request.Content != null && request.HasHeaders && request.Headers.ExpectCont
 02091                Http2Stream http2Stream = await SendHeadersAsync(request, cancellationToken, mustFlush: shouldExpectCont
 2092
 02093                bool duplex = request.Content != null && request.Content.AllowDuplex;
 2094
 2095                // If we have duplex content, then don't propagate the cancellation to the request body task.
 2096                // If cancellation occurs before we receive the response headers, then we will cancel the request body a
 02097                CancellationToken requestBodyCancellationToken = duplex ? CancellationToken.None : cancellationToken;
 2098
 2099                // Start sending request body, if any.
 02100                Task requestBodyTask = http2Stream.SendRequestBodyAsync(requestBodyCancellationToken);
 2101
 2102                // Start receiving the response headers.
 02103                Task responseHeadersTask = http2Stream.ReadResponseHeadersAsync(cancellationToken);
 2104
 2105                // Wait for either task to complete.  The best and most common case is when the request body completes
 2106                // before the response headers, in which case we can fully process the sending of the request and then
 2107                // fully process the sending of the response.  WhenAny is not free, so we do a fast-path check to see
 2108                // if the request body completed synchronously, only progressing to do the WhenAny if it didn't. Then
 2109                // if the WhenAny completes and either the WhenAny indicated that the request body completed or
 2110                // both tasks completed, we can proceed to handle the request body as if it completed first.  We also
 2111                // check whether the request content even allows for duplex communication; if it doesn't (none of
 2112                // our built-in content types do), then we can just proceed to wait for the request body content to
 2113                // complete before worrying about response headers completing.
 02114                if (requestBodyTask.IsCompleted ||
 02115                    !duplex ||
 02116                    await Task.WhenAny(requestBodyTask, responseHeadersTask).ConfigureAwait(false) == requestBodyTask ||
 02117                    requestBodyTask.IsCompleted ||
 02118                    http2Stream.SendRequestFinished)
 02119                {
 2120                    // The sending of the request body completed before receiving all of the request headers (or we're
 2121                    // ok waiting for the request body even if it hasn't completed, e.g. because we're not doing duplex)
 2122                    // This is the common and desirable case.
 2123                    try
 02124                    {
 02125                        await requestBodyTask.ConfigureAwait(false);
 02126                    }
 02127                    catch (Exception e)
 02128                    {
 02129                        if (NetEventSource.Log.IsEnabled()) Trace($"Sending request content failed: {e}");
 02130                        LogExceptions(responseHeadersTask); // Observe exception (if any) on responseHeadersTask.
 02131                        throw;
 2132                    }
 02133                }
 2134                else
 02135                {
 2136                    // We received the response headers but the request body hasn't yet finished; this most commonly hap
 2137                    // when the protocol is being used to enable duplex communication. If the connection is aborted or i
 2138                    // get RST or GOAWAY from server, exception will be stored in stream._abortException and propagated 
 2139                    // to caller if possible while processing response, but make sure that we log any exceptions from th
 2140                    // completing asynchronously).
 02141                    LogExceptions(requestBodyTask);
 02142                }
 2143
 2144                // Wait for the response headers to complete if they haven't already, propagating any exceptions.
 02145                await responseHeadersTask.ConfigureAwait(false);
 2146
 02147                HttpResponseMessage response = http2Stream.GetAndClearResponse();
 2148
 2149                // Check if this is a session-based authentication challenge (Negotiate/NTLM) on HTTP/2.
 2150                // These authentication schemes require a persistent connection and don't work properly over HTTP/2.
 02151                if (AuthenticationHelper.IsSessionAuthenticationChallenge(response))
 02152                {
 2153                    // Mark the pool so future requests that can use HTTP/1.1 go directly to HTTP/1.1.
 2154                    // This is set regardless of whether we can retry this particular request,
 2155                    // so that subsequent requests benefit from the downgrade.
 02156                    _pool.OnSessionAuthenticationChallengeSeen();
 2157
 2158                    // We can only safely retry if there's no request content, as we cannot guarantee
 2159                    // that we can rewind arbitrary content streams.
 2160                    // Additionally, we only retry if the version negotiation allows the request to fall back to HTTP/1.
 02161                    if (request.Content is null &&
 02162                        HttpConnectionPool.CanFallBackToHttp11(request) &&
 02163                        !request.IsAuthDisabled())
 02164                    {
 02165                        if (NetEventSource.Log.IsEnabled())
 02166                        {
 02167                            Trace($"Received session-based authentication challenge on HTTP/2, request will be retried o
 02168                        }
 2169
 02170                        response.Dispose();
 02171                        throw new HttpRequestException(HttpRequestError.UserAuthenticationError, SR.net_http_authconnect
 2172                    }
 02173                }
 2174
 02175                return response;
 2176            }
 02177            catch (HttpIOException e)
 02178            {
 02179                throw new HttpRequestException(e.HttpRequestError, e.Message, e);
 2180            }
 02181            catch (Exception e) when (e is IOException || e is ObjectDisposedException || e is InvalidOperationException
 02182            {
 02183                throw new HttpRequestException(HttpRequestError.Unknown, SR.net_http_client_execution_error, e);
 2184            }
 02185        }
 2186
 2187        private void RemoveStream(Http2Stream http2Stream)
 02188        {
 02189            if (NetEventSource.Log.IsEnabled()) Trace(http2Stream.StreamId, "");
 2190
 02191            lock (SyncObject)
 02192            {
 02193                if (!_httpStreams.Remove(http2Stream.StreamId))
 02194                {
 02195                    Debug.Fail($"Stream {http2Stream.StreamId} not found in dictionary during RemoveStream???");
 2196                    return;
 2197                }
 2198
 02199                if (_httpStreams.Count == 0)
 02200                {
 02201                    MarkConnectionAsIdle();
 02202                }
 02203            }
 2204
 02205            ReleaseStream();
 02206        }
 2207
 2208        private void RefreshPingTimestamp()
 02209        {
 02210            _nextPingRequestTimestamp = Environment.TickCount64 + _keepAlivePingDelay;
 02211        }
 2212
 2213        private void ProcessPingAck(long payload)
 02214        {
 2215            // RttEstimator is using negative values in PING payloads.
 2216            // _keepAlivePingPayload is always non-negative.
 02217            if (payload < 0) // RTT ping
 02218            {
 02219                _rttEstimator.OnPingAckReceived(payload, this);
 02220            }
 2221            else // Keepalive ping
 02222            {
 02223                if (_keepAliveState != KeepAliveState.PingSent)
 02224                    ThrowProtocolError();
 02225                if (Interlocked.Read(ref _keepAlivePingPayload) != payload)
 02226                    ThrowProtocolError();
 02227                _keepAliveState = KeepAliveState.None;
 02228            }
 02229        }
 2230
 2231        private void VerifyKeepAlive()
 02232        {
 02233            if (_keepAlivePingPolicy == HttpKeepAlivePingPolicy.WithActiveRequests)
 02234            {
 02235                lock (SyncObject)
 02236                {
 02237                    if (_streamsInUse == 0)
 02238                    {
 02239                        return;
 2240                    }
 02241                }
 02242            }
 2243
 02244            long now = Environment.TickCount64;
 02245            switch (_keepAliveState)
 2246            {
 2247                case KeepAliveState.None:
 2248                    // Check whether keep alive delay has passed since last frame received
 02249                    if (now > _nextPingRequestTimestamp)
 02250                    {
 2251                        // Set the status directly to ping sent and set the timestamp
 02252                        _keepAliveState = KeepAliveState.PingSent;
 02253                        _keepAlivePingTimeoutTimestamp = now + _keepAlivePingTimeout;
 2254
 02255                        long pingPayload = Interlocked.Increment(ref _keepAlivePingPayload);
 02256                        QueuePing(pingPayload);
 02257                        return;
 2258                    }
 02259                    break;
 2260                case KeepAliveState.PingSent:
 02261                    if (now > _keepAlivePingTimeoutTimestamp)
 02262                        ThrowProtocolError(Http2ProtocolErrorCode.ProtocolError, SR.net_ping_request_timed_out);
 02263                    break;
 2264                default:
 02265                    Debug.Fail($"Unexpected keep alive state ({_keepAliveState})");
 2266                    break;
 2267            }
 02268        }
 2269
 02270        public sealed override string ToString() => $"{nameof(Http2Connection)}({_pool})"; // Description for diagnostic
 2271
 2272        public override void Trace(string message, [CallerMemberName] string? memberName = null) =>
 02273            Trace(0, message, memberName);
 2274
 2275        internal void Trace(int streamId, string message, [CallerMemberName] string? memberName = null) =>
 02276            NetEventSource.Log.HandlerMessage(
 02277                _pool?.GetHashCode() ?? 0,    // pool ID
 02278                GetHashCode(),                // connection ID
 02279                streamId,                     // stream ID
 02280                memberName,                   // method name
 02281                message);                     // message
 2282
 2283        [DoesNotReturn]
 2284        private static void ThrowRetry(string message, Exception? innerException = null) =>
 02285            throw new HttpRequestException((innerException as HttpIOException)?.HttpRequestError ?? HttpRequestError.Unk
 2286
 2287        private static Exception GetRequestAbortedException(Exception? innerException = null) =>
 02288            innerException as HttpIOException ?? ExceptionDispatchInfo.SetCurrentStackTrace(new IOException(SR.net_http_
 2289
 2290        [DoesNotReturn]
 2291        private static void ThrowRequestAborted(Exception? innerException = null) =>
 02292            throw GetRequestAbortedException(innerException);
 2293
 2294        [DoesNotReturn]
 2295        private static void ThrowProtocolError() =>
 02296            ThrowProtocolError(Http2ProtocolErrorCode.ProtocolError);
 2297
 2298        [DoesNotReturn]
 2299        private static void ThrowProtocolError(Http2ProtocolErrorCode errorCode, string? message = null) =>
 02300            throw HttpProtocolException.CreateHttp2ConnectionException(errorCode, message);
 2301    }
 2302}
 2303

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.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.Buffers;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.IO;
 8using System.Net.Http.Headers;
 9using System.Net.Http.HPack;
 10using System.Runtime.CompilerServices;
 11using System.Runtime.ExceptionServices;
 12using System.Text;
 13using System.Threading;
 14using System.Threading.Channels;
 15using System.Threading.Tasks;
 16using System.Threading.Tasks.Sources;
 17
 18namespace System.Net.Http
 19{
 20    internal sealed partial class Http2Connection
 21    {
 22        private sealed class Http2Stream : IValueTaskSource, IHttpStreamHeadersHandler, IHttpTrace
 23        {
 24            private const int InitialStreamBufferSize =
 25#if DEBUG
 26                10;
 27#else
 28                1024;
 29#endif
 30
 031            private static ReadOnlySpan<byte> StatusHeaderName => ":status"u8;
 32
 33            private readonly Http2Connection _connection;
 34            private readonly HttpRequestMessage _request;
 35            private HttpResponseMessage? _response;
 36            /// <summary>Stores any trailers received after returning the response content to the caller.</summary>
 37            private HttpResponseHeaders? _trailers;
 38
 39            private MultiArrayBuffer _responseBuffer; // mutable struct, do not make this readonly
 40            private Http2StreamWindowManager _windowManager;
 41            private CreditWaiter? _creditWaiter;
 42            private int _availableCredit;
 043            private readonly object _creditSyncObject = new object(); // split from SyncObject to avoid lock ordering pr
 44
 45            private StreamCompletionState _requestCompletionState;
 46            private StreamCompletionState _responseCompletionState;
 47            private ResponseProtocolState _responseProtocolState;
 48            private bool _responseHeadersReceived;
 49
 50            // If this is not null, then we have received a reset from the server
 51            // (i.e. RST_STREAM or general IO error processing the connection)
 52            private Exception? _resetException;
 53            private bool _canRetry;             // if _resetException != null, this indicates the stream was refused and
 54
 55            // This flag indicates that, per section 8.1 of the RFC, the server completed the response and then sent a R
 56            // This is a signal to stop sending the request body, but the request is still considered successful.
 57            private bool _requestBodyAbandoned;
 58
 59            /// <summary>
 60            /// The core logic for the IValueTaskSource implementation.
 61            ///
 62            /// Thread-safety:
 63            /// _waitSource is used to coordinate between a producer indicating that something is available to process (
 64            /// or a cancellation request) and a consumer doing that processing.  There must only ever be a single consu
 65            /// data associated with the response.  Because there is only ever at most one consumer, producers can trust
 66            /// until the _waitSource is then set, no consumer will attempt to reset the _waitSource.  A producer must s
 67            /// coordinate with other producers (e.g. a race between data arriving from the event loop and cancellation 
 68            /// the lock it can check whether _hasWaiter is true, and if it is, set _hasWaiter to false, exit the lock, 
 69            /// producer coming along will then see _hasWaiter as false and will not attempt to concurrently set _waitSo
 70            /// thread-safety), and no other consumer could come along in the interim, because _hasWaiter being true mea
 71            /// for _waitSource to be set, and legally there can only be one consumer.  Once this producer sets _waitSou
 72            /// around to wait again, but invariants have all been maintained in the interim, and the consumer would nee
 73            /// Reset _waitSource.
 74            /// </summary>
 075            private ManualResetValueTaskSourceCore<bool> _waitSource = new ManualResetValueTaskSourceCore<bool> { RunCon
 76            /// <summary>Cancellation registration used to cancel the <see cref="_waitSource"/>.</summary>
 77            private CancellationTokenRegistration _waitSourceCancellation;
 78            /// <summary>
 79            /// Whether code has requested or is about to request a wait be performed and thus requires a call to SetRes
 80            /// This is read and written while holding the lock so that most operations on _waitSource don't need to be.
 81            /// </summary>
 82            private bool _hasWaiter;
 83
 84            private readonly CancellationTokenSource? _requestBodyCancellationSource;
 85
 86            private readonly TaskCompletionSource<bool>? _expect100ContinueWaiter;
 87
 88            private int _headerBudgetRemaining;
 89
 90            private bool _sendRstOnResponseClose;
 91
 092            public Http2Stream(HttpRequestMessage request, Http2Connection connection)
 093            {
 094                _request = request;
 095                _connection = connection;
 96
 097                _requestCompletionState = StreamCompletionState.InProgress;
 098                _responseCompletionState = StreamCompletionState.InProgress;
 99
 0100                _responseProtocolState = ResponseProtocolState.ExpectingStatus;
 101
 0102                _responseBuffer = new MultiArrayBuffer(InitialStreamBufferSize);
 103
 0104                _windowManager = new Http2StreamWindowManager(connection, this);
 105
 0106                _headerBudgetRemaining = connection._pool.Settings.MaxResponseHeadersByteLength;
 107
 108                // Extended connect requests will use the response content stream for bidirectional communication.
 109                // We will ignore any content set for such requests in SendRequestBodyAsync, as it has no defined semant
 0110                if (_request.Content == null || _request.IsExtendedConnectRequest)
 0111                {
 0112                    _requestCompletionState = StreamCompletionState.Completed;
 0113                    if (_request.IsExtendedConnectRequest)
 0114                    {
 0115                        _requestBodyCancellationSource = new CancellationTokenSource();
 0116                    }
 0117                }
 118                else
 0119                {
 120                    // Create this here because it can be canceled before SendRequestBodyAsync is even called.
 121                    // To avoid race conditions that can result in this being disposed in response to a server reset
 122                    // and then used to issue cancellation, we simply avoid disposing it; that's fine as long as we don'
 123                    // construct this via CreateLinkedTokenSource, in which case disposal is necessary to avoid a potent
 124                    // leak.  If how this is constructed ever changes, we need to revisit disposing it, such as by
 125                    // using synchronization (e.g. using an Interlocked.Exchange to "consume" the _requestBodyCancellati
 126                    // for either disposal or issuing cancellation).
 0127                    _requestBodyCancellationSource = new CancellationTokenSource();
 128
 0129                    if (_request.HasHeaders && _request.Headers.ExpectContinue == true)
 0130                    {
 131                        // Create a TCS for handling Expect: 100-continue semantics. See WaitFor100ContinueAsync.
 132                        // Note we need to create this in the constructor, because we can receive a 100 Continue respons
 0133                        _expect100ContinueWaiter = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAs
 0134                    }
 0135                }
 136
 0137                _response = new HttpResponseMessage()
 0138                {
 0139                    Version = HttpVersion.Version20,
 0140                    RequestMessage = _request,
 0141                    Content = new HttpConnectionResponseContent()
 0142                };
 0143            }
 144
 0145            private object SyncObject => this; // this isn't handed out to code that may lock on it
 146
 147            public void Initialize(int streamId, int initialWindowSize)
 0148            {
 0149                StreamId = streamId;
 0150                _availableCredit = initialWindowSize;
 0151                if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(initialWindowSize)}={initialWindowSize}");
 0152            }
 153
 0154            public int StreamId { get; private set; }
 155
 0156            public bool SendRequestFinished => _requestCompletionState != StreamCompletionState.InProgress;
 157
 0158            public bool ExpectResponseData => _responseProtocolState == ResponseProtocolState.ExpectingData;
 159
 0160            public Http2Connection Connection => _connection;
 161
 0162            public bool ConnectProtocolEstablished { get; private set; }
 163
 164            public HttpResponseMessage GetAndClearResponse()
 0165            {
 166                // Once SendAsync completes, the Http2Stream should no longer hold onto the response message.
 167                // Since the Http2Stream is rooted by the Http2Connection dictionary, doing so would prevent
 168                // the response stream from being collected and finalized if it were to be dropped without
 169                // being disposed first.
 0170                Debug.Assert(_response != null);
 0171                HttpResponseMessage r = _response;
 0172                _response = null;
 0173                return r;
 0174            }
 175
 176            public async Task SendRequestBodyAsync(CancellationToken cancellationToken)
 0177            {
 178                // Extended connect requests will use the response content stream for bidirectional communication.
 179                // Ignore any content set for such requests, as it has no defined semantics.
 0180                if (_request.Content == null || _request.IsExtendedConnectRequest)
 0181                {
 0182                    Debug.Assert(_requestCompletionState == StreamCompletionState.Completed);
 0183                    return;
 184                }
 185
 0186                if (NetEventSource.Log.IsEnabled()) Trace($"{_request.Content}");
 0187                Debug.Assert(_requestBodyCancellationSource != null);
 188
 189                // Cancel the request body sending if cancellation is requested on the supplied cancellation token.
 190                // Normally we might create a linked token, but once cancellation is requested, we can't recover anyway,
 191                // so it's fine to cancel the source representing the whole request body, and doing so allows us to avoi
 192                // creating another CTS instance and the associated nodes inside of it.  With this, cancellation will be
 193                // requested on _requestBodyCancellationSource when we need to cancel the request stream for any reason,
 194                // such as receiving an RST_STREAM or when the passed in token has cancellation requested. However, to
 195                // avoid unnecessarily registering with the cancellation token unless we have to, we wait to do so until
 196                // either we know we need to do a Expect: 100-continue send or until we know that the copying of our
 197                // content completed asynchronously.
 0198                CancellationTokenRegistration linkedRegistration = default;
 0199                bool sendRequestContent = true;
 200                try
 0201                {
 0202                    if (_expect100ContinueWaiter != null)
 0203                    {
 0204                        linkedRegistration = RegisterRequestBodyCancellation(cancellationToken);
 0205                        sendRequestContent = await WaitFor100ContinueAsync(_requestBodyCancellationSource.Token).Configu
 0206                    }
 207
 0208                    if (sendRequestContent)
 0209                    {
 0210                        using var writeStream = new Http2WriteStream(this, _request.Content.Headers.ContentLength.GetVal
 211
 0212                        if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestContentStart();
 213
 0214                        ValueTask vt = _request.Content.InternalCopyToAsync(writeStream, context: null, _requestBodyCanc
 0215                        if (vt.IsCompleted)
 0216                        {
 0217                            vt.GetAwaiter().GetResult();
 0218                        }
 219                        else
 0220                        {
 0221                            if (linkedRegistration.Equals(default))
 0222                            {
 0223                                linkedRegistration = RegisterRequestBodyCancellation(cancellationToken);
 0224                            }
 225
 0226                            await vt.ConfigureAwait(false);
 0227                        }
 228
 0229                        if (writeStream.BytesWritten < writeStream.ContentLength)
 0230                        {
 231                            // The number of bytes we actually sent doesn't match the advertised Content-Length
 0232                            throw new HttpRequestException(SR.Format(SR.net_http_request_content_length_mismatch, writeS
 233                        }
 234
 0235                        if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestContentStop(writeStream.BytesWritten
 0236                    }
 237
 0238                    if (NetEventSource.Log.IsEnabled()) Trace($"Finished sending request body.");
 0239                }
 0240                catch (Exception e)
 0241                {
 0242                    if (NetEventSource.Log.IsEnabled()) Trace($"Failed to send request body: {e}");
 243                    bool signalWaiter;
 244
 0245                    Debug.Assert(!Monitor.IsEntered(SyncObject));
 0246                    lock (SyncObject)
 0247                    {
 0248                        Debug.Assert(_requestCompletionState == StreamCompletionState.InProgress, $"Request already comp
 249
 0250                        if (_requestBodyAbandoned)
 0251                        {
 252                            // See comments on _requestBodyAbandoned.
 253                            // In this case, the request is still considered successful and we do not want to send a RST
 254                            // and we also don't want to propagate any error to the caller, in particular for non-duplex
 0255                            Debug.Assert(_responseCompletionState == StreamCompletionState.Completed);
 0256                            _requestCompletionState = StreamCompletionState.Completed;
 0257                            Debug.Assert(!ConnectProtocolEstablished);
 0258                            Complete();
 0259                            return;
 260                        }
 261
 262                        // This should not cause RST_STREAM to be sent because the request is still marked as in progres
 263                        bool sendReset;
 0264                        (signalWaiter, sendReset) = CancelResponseBody();
 0265                        Debug.Assert(!sendReset);
 266
 0267                        _requestCompletionState = StreamCompletionState.Failed;
 0268                        SendReset();
 0269                        Debug.Assert(!ConnectProtocolEstablished);
 0270                        Complete();
 0271                    }
 272
 0273                    if (signalWaiter)
 0274                    {
 0275                        _waitSource.SetResult(true);
 0276                    }
 277
 0278                    throw;
 279                }
 280                finally
 0281                {
 0282                    linkedRegistration.Dispose();
 0283                }
 284
 285                // New scope here to avoid variable name conflict on "sendReset"
 0286                {
 0287                    Debug.Assert(!Monitor.IsEntered(SyncObject));
 0288                    bool sendReset = false;
 0289                    lock (SyncObject)
 0290                    {
 0291                        Debug.Assert(_requestCompletionState == StreamCompletionState.InProgress, $"Request already comp
 0292                        _requestCompletionState = StreamCompletionState.Completed;
 293
 0294                        bool complete = false;
 0295                        if (_responseCompletionState != StreamCompletionState.InProgress)
 0296                        {
 297                            // Note, we can reach this point if the response stream failed but cancellation didn't propa
 0298                            sendReset = _responseCompletionState == StreamCompletionState.Failed;
 0299                            complete = true;
 0300                        }
 301
 0302                        if (sendReset)
 0303                        {
 0304                            SendReset();
 0305                        }
 0306                        else if (!sendRequestContent)
 0307                        {
 308                            // Request body hasn't been sent, so we need to notify the server that it won't
 309                            // get the body. However, we cannot do it right here because the server can
 310                            // reset the whole stream before we will have a chance to read the response body.
 0311                            _sendRstOnResponseClose = true;
 0312                        }
 313                        else
 0314                        {
 315                            // Send EndStream asynchronously and without cancellation.
 316                            // If this fails, it means that the connection is aborting and we will be reset.
 0317                            _connection.LogExceptions(_connection.SendEndStreamAsync(StreamId));
 0318                        }
 319
 0320                        if (complete)
 0321                        {
 0322                            Debug.Assert(!ConnectProtocolEstablished);
 0323                            Complete();
 0324                        }
 0325                    }
 0326                }
 0327            }
 328
 329            // Delay sending request body if we sent Expect: 100-continue.
 330            // We can either get 100 response from server and send body
 331            // or we may exceed timeout and send request body anyway.
 332            // If we get response status >= 300, we will not send the request body.
 333            public async ValueTask<bool> WaitFor100ContinueAsync(CancellationToken cancellationToken)
 0334            {
 0335                Debug.Assert(_request?.Content != null);
 0336                if (NetEventSource.Log.IsEnabled()) Trace($"Waiting to send request body content for 100-Continue.");
 337
 338                // Use TCS created in constructor. It will complete when one of three things occurs:
 339                // 1. we receive the relevant response from the server.
 340                // 2. the timer fires before we receive the relevant response from the server.
 341                // 3. cancellation is requested before we receive the relevant response from the server.
 342                // We need to run the continuation asynchronously for cases 1 and 3 (for 1 so that we don't starve the b
 343                // for 3 so that we don't run a lot of work as part of code calling Cancel), so the TCS is created to ru
 344                // We await the created Timer's disposal so that we ensure any work associated with it has quiesced prio
 345                // returning, just in case this object is pooled and potentially reused for another operation in the fut
 0346                TaskCompletionSource<bool> waiter = _expect100ContinueWaiter!;
 0347                using (cancellationToken.UnsafeRegister(static s => ((TaskCompletionSource<bool>)s!).TrySetResult(false)
 0348                await using (new Timer(static s =>
 0349                {
 0350                    var thisRef = (Http2Stream)s!;
 0351                    if (NetEventSource.Log.IsEnabled()) thisRef.Trace($"100-Continue timer expired.");
 0352                    thisRef._expect100ContinueWaiter?.TrySetResult(true);
 0353                }, this, _connection._pool.Settings._expect100ContinueTimeout, Timeout.InfiniteTimeSpan).ConfigureAwait(
 0354                {
 0355                    bool shouldSendContent = await waiter.Task.ConfigureAwait(false);
 356                    // By now, either we got a response from the server or the timer expired or cancellation was request
 0357                    CancellationHelper.ThrowIfCancellationRequested(cancellationToken);
 0358                    return shouldSendContent;
 359                }
 0360            }
 361
 362            private void SendReset()
 0363            {
 0364                Debug.Assert(Monitor.IsEntered(SyncObject));
 0365                Debug.Assert(_requestCompletionState != StreamCompletionState.InProgress);
 0366                Debug.Assert(_responseCompletionState != StreamCompletionState.InProgress);
 0367                Debug.Assert(_requestCompletionState == StreamCompletionState.Failed || _responseCompletionState == Stre
 0368                    "Reset called but neither request nor response is failed");
 369
 0370                if (NetEventSource.Log.IsEnabled()) Trace($"Stream reset. Request={_requestCompletionState}, Response={_
 371
 372                // Don't send a RST_STREAM if we've already received one from the server.
 0373                if (_resetException == null)
 0374                {
 375                    // If execution reached this line, it's guaranteed that
 376                    // _requestCompletionState == StreamCompletionState.Failed or _responseCompletionState == StreamComp
 0377                    _connection.LogExceptions(_connection.SendRstStreamAsync(StreamId, Http2ProtocolErrorCode.Cancel));
 0378                }
 0379            }
 380
 381            private void Complete()
 0382            {
 0383                Debug.Assert(Monitor.IsEntered(SyncObject));
 0384                Debug.Assert(_requestCompletionState != StreamCompletionState.InProgress);
 0385                Debug.Assert(_responseCompletionState != StreamCompletionState.InProgress);
 386
 0387                if (NetEventSource.Log.IsEnabled()) Trace($"Stream complete. Request={_requestCompletionState}, Response
 388
 0389                _connection.RemoveStream(this);
 390
 0391                lock (_creditSyncObject)
 0392                {
 0393                    CreditWaiter? waiter = _creditWaiter;
 0394                    if (waiter != null)
 0395                    {
 0396                        waiter.Dispose();
 0397                        _creditWaiter = null;
 0398                    }
 0399                }
 0400            }
 401
 402            private void Cancel()
 0403            {
 0404                if (NetEventSource.Log.IsEnabled()) Trace("");
 405
 0406                CancellationTokenSource? requestBodyCancellationSource = null;
 0407                bool signalWaiter = false;
 0408                bool sendReset = false;
 409
 0410                Debug.Assert(!Monitor.IsEntered(SyncObject));
 0411                lock (SyncObject)
 0412                {
 0413                    if (_requestCompletionState == StreamCompletionState.InProgress)
 0414                    {
 0415                        requestBodyCancellationSource = _requestBodyCancellationSource;
 0416                        Debug.Assert(requestBodyCancellationSource != null);
 0417                    }
 418
 0419                    (signalWaiter, sendReset) = CancelResponseBody();
 0420                }
 421
 422                // When cancellation propagates, SendRequestBodyAsync will set _requestCompletionState to Failed
 0423                requestBodyCancellationSource?.Cancel();
 424
 0425                lock (SyncObject)
 0426                {
 0427                    if (sendReset)
 0428                    {
 0429                        SendReset();
 430
 431                        // Extended CONNECT notes:
 432                        //
 433                        // To prevent from calling it *twice*, Extended CONNECT stream's Complete() is only
 434                        // called from CloseResponseBody(), as CloseResponseBody() is *always* called
 435                        // from Extended CONNECT stream's Dispose().
 436
 0437                        if (!ConnectProtocolEstablished)
 0438                        {
 0439                            Complete();
 0440                        }
 0441                    }
 0442                }
 443
 0444                if (signalWaiter)
 0445                {
 0446                    _waitSource.SetResult(true);
 0447                }
 0448            }
 449
 450            // Returns whether the waiter should be signalled or not.
 451            private (bool signalWaiter, bool sendReset) CancelResponseBody()
 0452            {
 0453                Debug.Assert(Monitor.IsEntered(SyncObject));
 454
 0455                bool sendReset = _sendRstOnResponseClose;
 456
 0457                if (_responseCompletionState == StreamCompletionState.InProgress)
 0458                {
 0459                    _responseCompletionState = StreamCompletionState.Failed;
 0460                    if (_requestCompletionState != StreamCompletionState.InProgress)
 0461                    {
 0462                        sendReset = true;
 0463                    }
 0464                }
 465
 466                // Discard any remaining buffered response data
 0467                _responseBuffer.DiscardAll();
 468
 0469                _responseProtocolState = ResponseProtocolState.Aborted;
 470
 0471                bool signalWaiter = _hasWaiter;
 0472                _hasWaiter = false;
 473
 0474                return (signalWaiter, sendReset);
 0475            }
 476
 477            public void OnWindowUpdate(int amount)
 0478            {
 0479                lock (_creditSyncObject)
 0480                {
 0481                    _availableCredit = checked(_availableCredit + amount);
 0482                    if (_availableCredit > 0 && _creditWaiter != null)
 0483                    {
 0484                        int granted = Math.Min(_availableCredit, _creditWaiter.Amount);
 0485                        if (_creditWaiter.TrySetResult(granted))
 0486                        {
 0487                            _availableCredit -= granted;
 0488                        }
 0489                    }
 0490                }
 0491            }
 492
 493            private const int FirstHPackRequestPseudoHeaderId = 1;
 494            private const int LastHPackRequestPseudoHeaderId = 7;
 495            private const int FirstHPackStatusPseudoHeaderId = 8;
 496            private const int LastHPackStatusPseudoHeaderId = 14;
 497            private const int FirstHPackNormalHeaderId = 15;
 498            private const int LastHPackNormalHeaderId = 61;
 499
 0500            private static ReadOnlySpan<int> HpackStaticStatusCodeTable => [200, 204, 206, 304, 400, 404, 500];
 501
 0502            private static readonly (HeaderDescriptor descriptor, byte[] value)[] s_hpackStaticHeaderTable = new (Header
 0503            {
 0504                (KnownHeaders.AcceptCharset.Descriptor, Array.Empty<byte>()),
 0505                (KnownHeaders.AcceptEncoding.Descriptor, "gzip, deflate"u8.ToArray()),
 0506                (KnownHeaders.AcceptLanguage.Descriptor, Array.Empty<byte>()),
 0507                (KnownHeaders.AcceptRanges.Descriptor, Array.Empty<byte>()),
 0508                (KnownHeaders.Accept.Descriptor, Array.Empty<byte>()),
 0509                (KnownHeaders.AccessControlAllowOrigin.Descriptor, Array.Empty<byte>()),
 0510                (KnownHeaders.Age.Descriptor, Array.Empty<byte>()),
 0511                (KnownHeaders.Allow.Descriptor, Array.Empty<byte>()),
 0512                (KnownHeaders.Authorization.Descriptor, Array.Empty<byte>()),
 0513                (KnownHeaders.CacheControl.Descriptor, Array.Empty<byte>()),
 0514                (KnownHeaders.ContentDisposition.Descriptor, Array.Empty<byte>()),
 0515                (KnownHeaders.ContentEncoding.Descriptor, Array.Empty<byte>()),
 0516                (KnownHeaders.ContentLanguage.Descriptor, Array.Empty<byte>()),
 0517                (KnownHeaders.ContentLength.Descriptor, Array.Empty<byte>()),
 0518                (KnownHeaders.ContentLocation.Descriptor, Array.Empty<byte>()),
 0519                (KnownHeaders.ContentRange.Descriptor, Array.Empty<byte>()),
 0520                (KnownHeaders.ContentType.Descriptor, Array.Empty<byte>()),
 0521                (KnownHeaders.Cookie.Descriptor, Array.Empty<byte>()),
 0522                (KnownHeaders.Date.Descriptor, Array.Empty<byte>()),
 0523                (KnownHeaders.ETag.Descriptor, Array.Empty<byte>()),
 0524                (KnownHeaders.Expect.Descriptor, Array.Empty<byte>()),
 0525                (KnownHeaders.Expires.Descriptor, Array.Empty<byte>()),
 0526                (KnownHeaders.From.Descriptor, Array.Empty<byte>()),
 0527                (KnownHeaders.Host.Descriptor, Array.Empty<byte>()),
 0528                (KnownHeaders.IfMatch.Descriptor, Array.Empty<byte>()),
 0529                (KnownHeaders.IfModifiedSince.Descriptor, Array.Empty<byte>()),
 0530                (KnownHeaders.IfNoneMatch.Descriptor, Array.Empty<byte>()),
 0531                (KnownHeaders.IfRange.Descriptor, Array.Empty<byte>()),
 0532                (KnownHeaders.IfUnmodifiedSince.Descriptor, Array.Empty<byte>()),
 0533                (KnownHeaders.LastModified.Descriptor, Array.Empty<byte>()),
 0534                (KnownHeaders.Link.Descriptor, Array.Empty<byte>()),
 0535                (KnownHeaders.Location.Descriptor, Array.Empty<byte>()),
 0536                (KnownHeaders.MaxForwards.Descriptor, Array.Empty<byte>()),
 0537                (KnownHeaders.ProxyAuthenticate.Descriptor, Array.Empty<byte>()),
 0538                (KnownHeaders.ProxyAuthorization.Descriptor, Array.Empty<byte>()),
 0539                (KnownHeaders.Range.Descriptor, Array.Empty<byte>()),
 0540                (KnownHeaders.Referer.Descriptor, Array.Empty<byte>()),
 0541                (KnownHeaders.Refresh.Descriptor, Array.Empty<byte>()),
 0542                (KnownHeaders.RetryAfter.Descriptor, Array.Empty<byte>()),
 0543                (KnownHeaders.Server.Descriptor, Array.Empty<byte>()),
 0544                (KnownHeaders.SetCookie.Descriptor, Array.Empty<byte>()),
 0545                (KnownHeaders.StrictTransportSecurity.Descriptor, Array.Empty<byte>()),
 0546                (KnownHeaders.TransferEncoding.Descriptor, Array.Empty<byte>()),
 0547                (KnownHeaders.UserAgent.Descriptor, Array.Empty<byte>()),
 0548                (KnownHeaders.Vary.Descriptor, Array.Empty<byte>()),
 0549                (KnownHeaders.Via.Descriptor, Array.Empty<byte>()),
 0550                (KnownHeaders.WWWAuthenticate.Descriptor, Array.Empty<byte>()),
 0551            };
 552
 553            void IHttpStreamHeadersHandler.OnStaticIndexedHeader(int index)
 0554            {
 0555                Debug.Assert(index >= FirstHPackRequestPseudoHeaderId && index <= LastHPackNormalHeaderId);
 556
 0557                if (index <= LastHPackRequestPseudoHeaderId)
 0558                {
 0559                    if (NetEventSource.Log.IsEnabled()) Trace($"Invalid request pseudo-header ID {index}.");
 0560                    throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.net_http_invalid_response);
 561                }
 0562                else if (index <= LastHPackStatusPseudoHeaderId)
 0563                {
 0564                    int statusCode = HpackStaticStatusCodeTable[index - FirstHPackStatusPseudoHeaderId];
 565
 0566                    OnStatus(statusCode);
 0567                }
 568                else
 0569                {
 0570                    (HeaderDescriptor descriptor, byte[] value) = s_hpackStaticHeaderTable[index - FirstHPackNormalHeade
 571
 0572                    OnHeader(descriptor, value);
 0573                }
 0574            }
 575
 576            void IHttpStreamHeadersHandler.OnStaticIndexedHeader(int index, ReadOnlySpan<byte> value)
 0577            {
 0578                Debug.Assert(index >= FirstHPackRequestPseudoHeaderId && index <= LastHPackNormalHeaderId);
 579
 0580                if (index <= LastHPackRequestPseudoHeaderId)
 0581                {
 0582                    if (NetEventSource.Log.IsEnabled()) Trace($"Invalid request pseudo-header ID {index}.");
 0583                    throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.net_http_invalid_response);
 584                }
 0585                else if (index <= LastHPackStatusPseudoHeaderId)
 0586                {
 0587                    int statusCode = ParseStatusCode(value);
 588
 0589                    OnStatus(statusCode);
 0590                }
 591                else
 0592                {
 0593                    (HeaderDescriptor descriptor, _) = s_hpackStaticHeaderTable[index - FirstHPackNormalHeaderId];
 594
 0595                    OnHeader(descriptor, value);
 0596                }
 0597            }
 598
 599            void IHttpStreamHeadersHandler.OnDynamicIndexedHeader(int? index, ReadOnlySpan<byte> name, ReadOnlySpan<byte
 0600            {
 0601                OnHeader(name, value);
 0602            }
 603
 604            private void AdjustHeaderBudget(int amount)
 0605            {
 0606                _headerBudgetRemaining -= amount;
 0607                if (_headerBudgetRemaining < 0)
 0608                {
 0609                    throw new HttpRequestException(HttpRequestError.ConfigurationLimitExceeded, SR.Format(SR.net_http_re
 610                }
 0611            }
 612
 613            private void OnStatus(int statusCode)
 0614            {
 0615                if (NetEventSource.Log.IsEnabled()) Trace($"Status code is {statusCode}");
 616
 0617                AdjustHeaderBudget(10); // for ":status" plus 3-digit status code
 618
 0619                Debug.Assert(!Monitor.IsEntered(SyncObject));
 0620                lock (SyncObject)
 0621                {
 0622                    if (_responseProtocolState == ResponseProtocolState.Aborted)
 0623                    {
 624                        // We could have aborted while processing the header block.
 0625                        return;
 626                    }
 627
 0628                    if (_responseProtocolState == ResponseProtocolState.ExpectingHeaders)
 0629                    {
 0630                        if (NetEventSource.Log.IsEnabled()) Trace("Received extra status header.");
 0631                        throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.net_http_invalid_response_mu
 632                    }
 633
 0634                    if (_responseProtocolState != ResponseProtocolState.ExpectingStatus)
 0635                    {
 636                        // Pseudo-headers are allowed only in header block
 0637                        if (NetEventSource.Log.IsEnabled()) Trace($"Status pseudo-header received in {_responseProtocolS
 0638                        throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.net_http_invalid_response_ps
 639                    }
 640
 0641                    Debug.Assert(_response != null);
 0642                    _response.StatusCode = (HttpStatusCode)statusCode;
 643
 0644                    if (statusCode < 200)
 0645                    {
 646                        // We do not process headers from 1xx responses.
 0647                        _responseProtocolState = ResponseProtocolState.ExpectingIgnoredHeaders;
 648
 0649                        if (_response.StatusCode == HttpStatusCode.Continue && _expect100ContinueWaiter != null)
 0650                        {
 0651                            if (NetEventSource.Log.IsEnabled()) Trace("Received 100-Continue status.");
 0652                            _expect100ContinueWaiter.TrySetResult(true);
 0653                        }
 0654                    }
 655                    else
 0656                    {
 0657                        if (statusCode >= 200 && statusCode <= 299 && _response.RequestMessage!.IsExtendedConnectRequest
 0658                        {
 0659                            ConnectProtocolEstablished = true;
 0660                        }
 661
 0662                        _responseProtocolState = ResponseProtocolState.ExpectingHeaders;
 663
 664                        // If we are waiting for a 100-continue response, signal the waiter now.
 0665                        if (_expect100ContinueWaiter != null)
 0666                        {
 667                            // If the final status code is >= 300, skip sending the body.
 0668                            bool shouldSendBody = (statusCode < 300);
 669
 0670                            if (NetEventSource.Log.IsEnabled()) Trace($"Expecting 100 Continue but received final status
 0671                            _expect100ContinueWaiter.TrySetResult(shouldSendBody);
 0672                        }
 0673                    }
 0674                }
 0675            }
 676
 677            private void OnHeader(HeaderDescriptor descriptor, ReadOnlySpan<byte> value)
 0678            {
 0679                if (NetEventSource.Log.IsEnabled()) Trace($"{descriptor.Name}: {Encoding.ASCII.GetString(value)}");
 680
 0681                AdjustHeaderBudget(descriptor.Name.Length + value.Length);
 682
 0683                Debug.Assert(!Monitor.IsEntered(SyncObject));
 0684                lock (SyncObject)
 0685                {
 0686                    if (_responseProtocolState == ResponseProtocolState.Aborted)
 0687                    {
 688                        // We could have aborted while processing the header block.
 0689                        return;
 690                    }
 691
 0692                    if (_responseProtocolState == ResponseProtocolState.ExpectingIgnoredHeaders)
 0693                    {
 694                        // for 1xx response we ignore all headers.
 0695                        return;
 696                    }
 697
 0698                    if (_responseProtocolState != ResponseProtocolState.ExpectingHeaders && _responseProtocolState != Re
 0699                    {
 0700                        if (NetEventSource.Log.IsEnabled()) Trace("Received header before status.");
 0701                        throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.net_http_invalid_response);
 702                    }
 703
 0704                    Encoding? valueEncoding = _connection._pool.Settings._responseHeaderEncodingSelector?.Invoke(descrip
 705
 706                    // Note we ignore the return value from TryAddWithoutValidation;
 707                    // if the header can't be added, we silently drop it.
 0708                    if (_responseProtocolState == ResponseProtocolState.ExpectingTrailingHeaders)
 0709                    {
 0710                        Debug.Assert(_trailers != null);
 0711                        string headerValue = descriptor.GetHeaderValue(value, valueEncoding);
 0712                        _trailers.TryAddWithoutValidation((descriptor.HeaderType & HttpHeaderType.Request) == HttpHeader
 0713                    }
 0714                    else if ((descriptor.HeaderType & HttpHeaderType.Content) == HttpHeaderType.Content)
 0715                    {
 0716                        Debug.Assert(_response != null && _response.Content != null);
 0717                        string headerValue = descriptor.GetHeaderValue(value, valueEncoding);
 0718                        _response.Content.Headers.TryAddWithoutValidation(descriptor, headerValue);
 0719                    }
 720                    else
 0721                    {
 0722                        Debug.Assert(_response != null);
 0723                        string headerValue = _connection.GetResponseHeaderValueWithCaching(descriptor, value, valueEncod
 0724                        _response.Headers.TryAddWithoutValidation((descriptor.HeaderType & HttpHeaderType.Request) == Ht
 0725                    }
 0726                }
 0727            }
 728
 729            public void OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
 0730            {
 0731                Debug.Assert(name.Length > 0);
 732
 0733                if (name[0] == (byte)':')
 0734                {
 735                    // Pseudo-header
 0736                    if (name.SequenceEqual(StatusHeaderName))
 0737                    {
 0738                        int statusCode = ParseStatusCode(value);
 739
 0740                        OnStatus(statusCode);
 0741                    }
 742                    else
 0743                    {
 0744                        if (NetEventSource.Log.IsEnabled()) Trace($"Invalid response pseudo-header '{Encoding.ASCII.GetS
 0745                        throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.net_http_invalid_response);
 746                    }
 0747                }
 748                else
 0749                {
 750                    // Regular header
 0751                    if (!HeaderDescriptor.TryGet(name, out HeaderDescriptor descriptor))
 0752                    {
 753                        // Invalid header name
 0754                        throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invalid_r
 755                    }
 756
 0757                    OnHeader(descriptor, value);
 0758                }
 0759            }
 760
 761            public void OnHeadersStart()
 0762            {
 0763                Debug.Assert(!Monitor.IsEntered(SyncObject));
 0764                lock (SyncObject)
 0765                {
 0766                    switch (_responseProtocolState)
 767                    {
 768                        case ResponseProtocolState.ExpectingStatus:
 769                        case ResponseProtocolState.Aborted:
 0770                            break;
 771
 772                        case ResponseProtocolState.ExpectingData:
 0773                            _responseProtocolState = ResponseProtocolState.ExpectingTrailingHeaders;
 0774                            _trailers ??= new HttpResponseHeaders(containsTrailingHeaders: true);
 0775                            break;
 776
 777                        default:
 0778                            ThrowProtocolError();
 779                            break;
 780                    }
 0781                }
 0782            }
 783
 784            public void OnHeadersComplete(bool endStream)
 0785            {
 0786                Debug.Assert(!Monitor.IsEntered(SyncObject));
 787                bool signalWaiter;
 0788                lock (SyncObject)
 0789                {
 0790                    switch (_responseProtocolState)
 791                    {
 792                        case ResponseProtocolState.Aborted:
 0793                            return;
 794
 795                        case ResponseProtocolState.ExpectingHeaders:
 0796                            _responseProtocolState = endStream ? ResponseProtocolState.Complete : ResponseProtocolState.
 0797                            _responseHeadersReceived = true;
 0798                            break;
 799
 800                        case ResponseProtocolState.ExpectingTrailingHeaders:
 0801                            if (!endStream)
 0802                            {
 0803                                if (NetEventSource.Log.IsEnabled()) Trace("Trailing headers received without endStream")
 0804                                ThrowProtocolError();
 805                            }
 0806                            _responseProtocolState = ResponseProtocolState.Complete;
 0807                            break;
 808
 809                        case ResponseProtocolState.ExpectingIgnoredHeaders:
 0810                            if (endStream)
 0811                            {
 812                                // we should not get endStream while processing 1xx response.
 0813                                ThrowProtocolError();
 814                            }
 815
 816                            // We should wait for final response before signaling to waiter.
 0817                            _responseProtocolState = ResponseProtocolState.ExpectingStatus;
 0818                            return;
 819
 820                        default:
 0821                            ThrowProtocolError();
 822                            break;
 823                    }
 824
 0825                    if (endStream)
 0826                    {
 0827                        Debug.Assert(_responseCompletionState == StreamCompletionState.InProgress, $"Response already co
 828
 0829                        _responseCompletionState = StreamCompletionState.Completed;
 830
 831                        // Extended CONNECT notes:
 832                        //
 833                        // To prevent from calling it *prematurely*, Extended CONNECT stream's Complete() is only
 834                        // called from CloseResponseBody(), as CloseResponseBody() is *only* called
 835                        // from Extended CONNECT stream's Dispose().
 836                        //
 837                        // Due to bidirectional streaming nature of the Extended CONNECT request,
 838                        // the *write side* of the stream can only be completed by calling Dispose().
 839                        //
 840                        // The streaming in both ways happens over the single "response" stream instance, which makes
 841                        // _requestCompletionState *not indicative* of the actual state of the write side of the stream.
 842
 0843                        if (_requestCompletionState == StreamCompletionState.Completed && !ConnectProtocolEstablished)
 0844                        {
 0845                            Complete();
 0846                        }
 847
 848                        // We should never reach here with the request failed. It's only set to Failed in SendRequestBod
 849                        // which will set the _responseCompletionState to Failed, meaning we'll never get here.
 0850                        Debug.Assert(_requestCompletionState != StreamCompletionState.Failed);
 0851                    }
 852
 0853                    if (_responseProtocolState == ResponseProtocolState.ExpectingData)
 0854                    {
 0855                        _windowManager.Start();
 0856                    }
 0857                    signalWaiter = _hasWaiter;
 0858                    _hasWaiter = false;
 0859                }
 860
 0861                if (signalWaiter)
 0862                {
 0863                    _waitSource.SetResult(true);
 0864                }
 0865            }
 866
 867            public void OnResponseData(ReadOnlySpan<byte> buffer, bool endStream)
 0868            {
 0869                Debug.Assert(!Monitor.IsEntered(SyncObject));
 870                bool signalWaiter;
 0871                lock (SyncObject)
 0872                {
 0873                    switch (_responseProtocolState)
 874                    {
 875                        case ResponseProtocolState.ExpectingData:
 0876                            break;
 877
 878                        case ResponseProtocolState.Aborted:
 0879                            return;
 880
 881                        default:
 882                            // Flow control messages are not valid in this state.
 0883                            ThrowProtocolError();
 884                            break;
 885                    }
 886
 0887                    if (_responseBuffer.ActiveMemory.Length + buffer.Length > _windowManager.StreamWindowSize)
 0888                    {
 889                        // Window size exceeded.
 0890                        ThrowProtocolError(Http2ProtocolErrorCode.FlowControlError);
 891                    }
 892
 0893                    _responseBuffer.EnsureAvailableSpace(buffer.Length);
 0894                    _responseBuffer.AvailableMemory.CopyFrom(buffer);
 0895                    _responseBuffer.Commit(buffer.Length);
 896
 0897                    if (endStream)
 0898                    {
 0899                        _responseProtocolState = ResponseProtocolState.Complete;
 900
 0901                        Debug.Assert(_responseCompletionState == StreamCompletionState.InProgress, $"Response already co
 902
 0903                        _responseCompletionState = StreamCompletionState.Completed;
 904
 905                        // Extended CONNECT notes:
 906                        //
 907                        // To prevent from calling it *prematurely*, Extended CONNECT stream's Complete() is only
 908                        // called from CloseResponseBody(), as CloseResponseBody() is *only* called
 909                        // from Extended CONNECT stream's Dispose().
 910                        //
 911                        // Due to bidirectional streaming nature of the Extended CONNECT request,
 912                        // the *write side* of the stream can only be completed by calling Dispose().
 913                        //
 914                        // The streaming in both ways happens over the single "response" stream instance, which makes
 915                        // _requestCompletionState *not indicative* of the actual state of the write side of the stream.
 916
 0917                        if (_requestCompletionState == StreamCompletionState.Completed && !ConnectProtocolEstablished)
 0918                        {
 0919                            Complete();
 0920                        }
 921
 922                        // We should never reach here with the request failed. It's only set to Failed in SendRequestBod
 923                        // which will set the _responseCompletionState to Failed, meaning we'll never get here.
 0924                        Debug.Assert(_requestCompletionState != StreamCompletionState.Failed);
 0925                    }
 926
 0927                    signalWaiter = _hasWaiter;
 0928                    _hasWaiter = false;
 0929                }
 930
 0931                if (signalWaiter)
 0932                {
 0933                    _waitSource.SetResult(true);
 0934                }
 0935            }
 936
 937            // This is called in several different cases:
 938            // (1) Receiving RST_STREAM on this stream. If so, the resetStreamErrorCode will be non-null, and canRetry w
 939            // (2) Receiving GOAWAY that indicates this stream has not been processed. If so, canRetry will be true.
 940            // (3) Connection IO failure or protocol violation. If so, resetException will contain the relevant exceptio
 941            // (4) Receiving EOF from the server. If so, resetException will contain an exception like "expected 9 bytes
 942            public void OnReset(Exception resetException, Http2ProtocolErrorCode? resetStreamErrorCode = null, bool canR
 0943            {
 0944                if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(resetException)}={resetException}, {nameof(resetStre
 945
 0946                bool cancel = false;
 0947                CancellationTokenSource? requestBodyCancellationSource = null;
 948
 0949                Debug.Assert(!Monitor.IsEntered(SyncObject));
 0950                lock (SyncObject)
 0951                {
 952                    // If we've already finished, don't actually reset the stream.
 953                    // Otherwise, any waiters that haven't executed yet will see the _resetException and throw.
 954                    // This can happen, for example, when the server finishes the request and then closes the connection
 955                    // but the waiter hasn't woken up yet.
 0956                    if (_requestCompletionState == StreamCompletionState.Completed && _responseCompletionState == Stream
 0957                    {
 0958                        return;
 959                    }
 960
 961                    // It's possible we could be called twice, e.g. we receive a RST_STREAM and then the whole connectio
 962                    // before we have a chance to process cancellation and tear everything down. Just ignore this.
 0963                    if (_resetException != null)
 0964                    {
 0965                        return;
 966                    }
 967
 968                    // If the server told us the request has not been processed (via Last-Stream-ID on GOAWAY),
 969                    // but we've already received some response data from the server, then the server lied to us.
 970                    // In this case, don't allow the request to be retried.
 0971                    if (canRetry && _responseProtocolState != ResponseProtocolState.ExpectingStatus)
 0972                    {
 0973                        canRetry = false;
 0974                    }
 975
 976                    // Per section 8.1 in the RFC:
 977                    // If the server has completed the response body (i.e. we've received EndStream)
 978                    // but the request body is still sending, and we then receive a RST_STREAM with errorCode = NO_ERROR
 979                    // we treat this specially and simply cancel sending the request body, rather than treating
 980                    // the entire request as failed.
 0981                    if (resetStreamErrorCode == Http2ProtocolErrorCode.NoError &&
 0982                        _responseCompletionState == StreamCompletionState.Completed)
 0983                    {
 0984                        if (_requestCompletionState == StreamCompletionState.InProgress)
 0985                        {
 0986                            _requestBodyAbandoned = true;
 0987                            requestBodyCancellationSource = _requestBodyCancellationSource;
 0988                            Debug.Assert(requestBodyCancellationSource != null);
 0989                        }
 0990                    }
 991                    else
 0992                    {
 0993                        _resetException = resetException;
 0994                        _canRetry = canRetry;
 0995                        cancel = true;
 0996                    }
 0997                }
 998
 0999                if (requestBodyCancellationSource != null)
 01000                {
 01001                    Debug.Assert(_requestBodyAbandoned);
 01002                    Debug.Assert(!cancel);
 01003                    requestBodyCancellationSource.Cancel();
 01004                }
 1005                else
 01006                {
 01007                    Cancel();
 01008                }
 01009            }
 1010
 1011            private void CheckResponseBodyState()
 01012            {
 01013                Debug.Assert(Monitor.IsEntered(SyncObject));
 1014
 01015                if (_resetException is Exception resetException)
 01016                {
 01017                    if (_canRetry)
 01018                    {
 01019                        ThrowRetry(SR.net_http_request_aborted, resetException);
 1020                    }
 1021
 01022                    ThrowRequestAborted(resetException);
 01023                }
 1024
 01025                if (_responseProtocolState == ResponseProtocolState.Aborted)
 01026                {
 01027                    ThrowRequestAborted();
 01028                }
 01029            }
 1030
 1031            // Determine if we have enough data to process up to complete final response headers.
 1032            private (bool wait, bool isEmptyResponse) TryEnsureHeaders()
 01033            {
 01034                Debug.Assert(!Monitor.IsEntered(SyncObject));
 01035                lock (SyncObject)
 01036                {
 01037                    if (!_responseHeadersReceived)
 01038                    {
 01039                        CheckResponseBodyState();
 01040                        Debug.Assert(!_hasWaiter);
 01041                        _hasWaiter = true;
 01042                        _waitSource.Reset();
 01043                        return (true, false);
 1044                    }
 1045
 01046                    return (false, _responseProtocolState == ResponseProtocolState.Complete && _responseBuffer.IsEmpty);
 1047                }
 01048            }
 1049
 1050            public async Task ReadResponseHeadersAsync(CancellationToken cancellationToken)
 01051            {
 1052                bool emptyResponse;
 1053                try
 01054                {
 01055                    if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersStart();
 1056
 1057                    // Wait for response headers to be read.
 1058                    bool wait;
 1059
 1060                    // Process all informational responses if any and wait for final status.
 01061                    (wait, emptyResponse) = TryEnsureHeaders();
 01062                    if (wait)
 01063                    {
 01064                        await WaitForDataAsync(cancellationToken).ConfigureAwait(false);
 1065
 01066                        (wait, emptyResponse) = TryEnsureHeaders();
 01067                        Debug.Assert(!wait);
 01068                    }
 1069
 01070                    Debug.Assert(_response is not null);
 01071                    if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersStop((int)_response.StatusCode);
 01072                }
 01073                catch
 01074                {
 01075                    Cancel();
 01076                    throw;
 1077                }
 1078
 01079                Debug.Assert(_response != null && _response.Content != null);
 1080                // Start to process the response body.
 01081                var responseContent = (HttpConnectionResponseContent)_response.Content;
 01082                if (ConnectProtocolEstablished)
 01083                {
 01084                    responseContent.SetStream(new Http2ReadWriteStream(this, closeResponseBodyOnDispose: true));
 01085                }
 01086                else if (emptyResponse)
 01087                {
 1088                    // If there are any trailers, copy them over to the response.  Normally this would be handled by
 1089                    // the response stream hitting EOF, but if there is no response body, we do it here.
 01090                    MoveTrailersToResponseMessage(_response);
 01091                    responseContent.SetStream(EmptyReadStream.Instance);
 01092                }
 1093                else
 01094                {
 01095                    responseContent.SetStream(new Http2ReadStream(this));
 01096                }
 01097                if (NetEventSource.Log.IsEnabled()) Trace($"Received response: {_response}");
 1098
 1099                // Process Set-Cookie headers.
 01100                if (_connection._pool.Settings._useCookies)
 01101                {
 01102                    CookieHelper.ProcessReceivedCookies(_response, _connection._pool.Settings._cookieContainer!);
 01103                }
 01104            }
 1105
 1106            private (bool wait, int bytesRead) TryReadFromBuffer(Span<byte> buffer, bool partOfSyncRead = false)
 01107            {
 01108                Debug.Assert(!Monitor.IsEntered(SyncObject));
 01109                lock (SyncObject)
 01110                {
 01111                    CheckResponseBodyState();
 1112
 01113                    if (!_responseBuffer.IsEmpty)
 01114                    {
 01115                        MultiMemory activeBuffer = _responseBuffer.ActiveMemory;
 01116                        int bytesRead = Math.Min(buffer.Length, activeBuffer.Length);
 01117                        activeBuffer.Slice(0, bytesRead).CopyTo(buffer);
 01118                        _responseBuffer.Discard(bytesRead);
 1119
 01120                        return (false, bytesRead);
 1121                    }
 01122                    else if (_responseProtocolState == ResponseProtocolState.Complete)
 01123                    {
 01124                        return (false, 0);
 1125                    }
 1126
 01127                    Debug.Assert(_responseProtocolState == ResponseProtocolState.ExpectingData || _responseProtocolState
 1128
 01129                    Debug.Assert(!_hasWaiter);
 01130                    _hasWaiter = true;
 01131                    _waitSource.Reset();
 01132                    _waitSource.RunContinuationsAsynchronously = !partOfSyncRead;
 01133                    return (true, 0);
 1134                }
 01135            }
 1136
 1137            public int ReadData(Span<byte> buffer, HttpResponseMessage responseMessage)
 01138            {
 01139                (bool wait, int bytesRead) = TryReadFromBuffer(buffer, partOfSyncRead: true);
 01140                if (wait)
 01141                {
 1142                    // Synchronously block waiting for data to be produced.
 01143                    Debug.Assert(bytesRead == 0);
 01144                    WaitForData();
 01145                    (wait, bytesRead) = TryReadFromBuffer(buffer, partOfSyncRead: true);
 01146                    Debug.Assert(!wait);
 01147                }
 1148
 01149                if (bytesRead != 0)
 01150                {
 01151                    _windowManager.AdjustWindow(bytesRead, this);
 01152                }
 01153                else if (buffer.Length != 0)
 01154                {
 1155                    // We've hit EOF.  Pull in from the Http2Stream any trailers that were temporarily stored there.
 01156                    MoveTrailersToResponseMessage(responseMessage);
 01157                }
 1158
 01159                return bytesRead;
 01160            }
 1161
 1162            public async ValueTask<int> ReadDataAsync(Memory<byte> buffer, HttpResponseMessage responseMessage, Cancella
 01163            {
 01164                (bool wait, int bytesRead) = TryReadFromBuffer(buffer.Span);
 01165                if (wait)
 01166                {
 01167                    Debug.Assert(bytesRead == 0);
 01168                    await WaitForDataAsync(cancellationToken).ConfigureAwait(false);
 01169                    (wait, bytesRead) = TryReadFromBuffer(buffer.Span);
 01170                    Debug.Assert(!wait);
 01171                }
 1172
 01173                if (bytesRead != 0)
 01174                {
 01175                    _windowManager.AdjustWindow(bytesRead, this);
 01176                }
 01177                else if (buffer.Length != 0)
 01178                {
 1179                    // We've hit EOF.  Pull in from the Http2Stream any trailers that were temporarily stored there.
 01180                    MoveTrailersToResponseMessage(responseMessage);
 01181                }
 1182
 01183                return bytesRead;
 01184            }
 1185
 1186            public void CopyTo(HttpResponseMessage responseMessage, Stream destination, int bufferSize)
 01187            {
 01188                byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
 1189                try
 01190                {
 1191                    // Generally the same logic as in ReadData, but wrapped in a loop where every read segment is writte
 01192                    while (true)
 01193                    {
 01194                        (bool wait, int bytesRead) = TryReadFromBuffer(buffer, partOfSyncRead: true);
 01195                        if (wait)
 01196                        {
 01197                            Debug.Assert(bytesRead == 0);
 01198                            WaitForData();
 01199                            (wait, bytesRead) = TryReadFromBuffer(buffer, partOfSyncRead: true);
 01200                            Debug.Assert(!wait);
 01201                        }
 1202
 01203                        if (bytesRead != 0)
 01204                        {
 01205                            _windowManager.AdjustWindow(bytesRead, this);
 01206                            destination.Write(new ReadOnlySpan<byte>(buffer, 0, bytesRead));
 01207                        }
 1208                        else
 01209                        {
 1210                            // We've hit EOF.  Pull in from the Http2Stream any trailers that were temporarily stored th
 01211                            MoveTrailersToResponseMessage(responseMessage);
 01212                            return;
 1213                        }
 01214                    }
 1215                }
 1216                finally
 01217                {
 01218                    ArrayPool<byte>.Shared.Return(buffer);
 01219                }
 01220            }
 1221
 1222            public async Task CopyToAsync(HttpResponseMessage responseMessage, Stream destination, int bufferSize, Cance
 01223            {
 01224                byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
 1225                try
 01226                {
 1227                    // Generally the same logic as in ReadDataAsync, but wrapped in a loop where every read segment is w
 01228                    while (true)
 01229                    {
 01230                        (bool wait, int bytesRead) = TryReadFromBuffer(buffer);
 01231                        if (wait)
 01232                        {
 01233                            Debug.Assert(bytesRead == 0);
 01234                            await WaitForDataAsync(cancellationToken).ConfigureAwait(false);
 01235                            (wait, bytesRead) = TryReadFromBuffer(buffer);
 01236                            Debug.Assert(!wait);
 01237                        }
 1238
 01239                        if (bytesRead != 0)
 01240                        {
 01241                            _windowManager.AdjustWindow(bytesRead, this);
 01242                            await destination.WriteAsync(new ReadOnlyMemory<byte>(buffer, 0, bytesRead), cancellationTok
 01243                        }
 1244                        else
 01245                        {
 1246                            // We've hit EOF.  Pull in from the Http2Stream any trailers that were temporarily stored th
 01247                            MoveTrailersToResponseMessage(responseMessage);
 01248                            return;
 1249                        }
 01250                    }
 1251                }
 1252                finally
 01253                {
 01254                    ArrayPool<byte>.Shared.Return(buffer);
 01255                }
 01256            }
 1257
 1258            private void MoveTrailersToResponseMessage(HttpResponseMessage responseMessage)
 01259            {
 01260                if (_trailers != null)
 01261                {
 01262                    responseMessage.StoreReceivedTrailingHeaders(_trailers);
 01263                }
 01264            }
 1265
 1266            private async ValueTask SendDataAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
 01267            {
 01268                Debug.Assert(_requestBodyCancellationSource != null);
 1269
 1270                // Cancel the request body sending if cancellation is requested on the supplied cancellation token.
 01271                CancellationTokenRegistration linkedRegistration = cancellationToken.CanBeCanceled && cancellationToken 
 01272                    RegisterRequestBodyCancellation(cancellationToken) :
 01273                    default;
 1274
 1275                try
 01276                {
 01277                    while (buffer.Length > 0)
 01278                    {
 01279                        int sendSize = -1;
 01280                        bool flush = false;
 01281                        lock (_creditSyncObject)
 01282                        {
 01283                            if (_availableCredit > 0)
 01284                            {
 01285                                sendSize = Math.Min(buffer.Length, _availableCredit);
 01286                                _availableCredit -= sendSize;
 1287
 1288                                // Force a flush if we are out of credit, because we don't know that we will be sending 
 01289                                if (_availableCredit == 0)
 01290                                {
 01291                                    flush = true;
 01292                                }
 01293                            }
 1294                            else
 01295                            {
 01296                                if (_creditWaiter is null)
 01297                                {
 01298                                    _creditWaiter = new CreditWaiter(_requestBodyCancellationSource.Token);
 01299                                }
 1300                                else
 01301                                {
 01302                                    _creditWaiter.ResetForAwait(_requestBodyCancellationSource.Token);
 01303                                }
 01304                                _creditWaiter.Amount = buffer.Length;
 01305                            }
 01306                        }
 1307
 01308                        if (sendSize == -1)
 01309                        {
 1310                            // Logically this is part of the else block above, but we can't await while holding the lock
 01311                            Debug.Assert(_creditWaiter != null);
 01312                            sendSize = await _creditWaiter.AsValueTask().ConfigureAwait(false);
 1313
 01314                            lock (_creditSyncObject)
 01315                            {
 1316                                // Force a flush if we are out of credit, because we don't know that we will be sending 
 01317                                if (_availableCredit == 0)
 01318                                {
 01319                                    flush = true;
 01320                                }
 01321                            }
 01322                        }
 1323
 01324                        Debug.Assert(sendSize > 0);
 1325
 1326                        ReadOnlyMemory<byte> current;
 01327                        (current, buffer) = SplitBuffer(buffer, sendSize);
 1328
 01329                        await _connection.SendStreamDataAsync(StreamId, current, flush, _requestBodyCancellationSource.T
 01330                    }
 01331                }
 01332                catch (OperationCanceledException e) when (e.CancellationToken == _requestBodyCancellationSource.Token)
 01333                {
 01334                    lock (SyncObject)
 01335                    {
 01336                        if (_resetException is Exception resetException)
 01337                        {
 01338                            if (_canRetry)
 01339                            {
 01340                                ThrowRetry(SR.net_http_request_aborted, resetException);
 1341                            }
 1342
 01343                            ThrowRequestAborted(resetException);
 01344                        }
 01345                    }
 1346
 01347                    throw;
 1348                }
 1349                finally
 01350                {
 01351                    linkedRegistration.Dispose();
 01352                }
 01353            }
 1354
 1355            // This method should only be called from Http2ReadWriteStream.Dispose()
 1356            private void CloseResponseBody()
 01357            {
 1358                // Extended CONNECT notes:
 1359                //
 1360                // Due to bidirectional streaming nature of the Extended CONNECT request,
 1361                // the *write side* of the stream can only be completed by calling Dispose()
 1362                // (which, for Extended CONNECT case, will in turn call CloseResponseBody())
 1363                //
 1364                // Similarly to QuicStream, disposal *gracefully* closes the write side of the stream
 1365                // (unless we've received RST_STREAM before) and *abortively* closes the read side
 1366                // of the stream (unless we've received EOS before).
 1367
 01368                if (ConnectProtocolEstablished && _resetException is null)
 01369                {
 1370                    // Gracefully close the write side of the Extended CONNECT stream
 01371                    _connection.LogExceptions(_connection.SendEndStreamAsync(StreamId));
 01372                }
 1373
 1374                // Check if the response body has been fully consumed.
 01375                bool fullyConsumed = false;
 01376                Debug.Assert(!Monitor.IsEntered(SyncObject));
 01377                lock (SyncObject)
 01378                {
 01379                    if (_responseBuffer.IsEmpty && _responseProtocolState == ResponseProtocolState.Complete)
 01380                    {
 01381                        fullyConsumed = true;
 01382                    }
 01383                }
 1384
 1385                // If the response body isn't completed, cancel it now.
 1386                // This includes aborting the read side of the Extended CONNECT stream.
 01387                if (!fullyConsumed)
 01388                {
 01389                    Cancel();
 01390                }
 01391                else if (_sendRstOnResponseClose)
 01392                {
 1393                    // Send RST_STREAM with CANCEL to notify the server that it shouldn't
 1394                    // expect the request body.
 1395                    // If this fails, it means that the connection is aborting and we will be reset.
 01396                    _connection.LogExceptions(_connection.SendRstStreamAsync(StreamId, Http2ProtocolErrorCode.Cancel));
 01397                }
 1398
 01399                lock (SyncObject)
 01400                {
 01401                    if (ConnectProtocolEstablished)
 01402                    {
 1403                        // This should be the only place where Extended Connect stream is completed
 01404                        Complete();
 01405                    }
 1406
 01407                    _responseBuffer.Dispose();
 01408                }
 01409            }
 1410
 1411            private CancellationTokenRegistration RegisterRequestBodyCancellation(CancellationToken cancellationToken) =
 01412                cancellationToken.UnsafeRegister(static s => ((CancellationTokenSource)s!).Cancel(), _requestBodyCancell
 1413
 1414            // This object is itself usable as a backing source for ValueTask.  Since there's only ever one awaiter
 1415            // for this object's state transitions at a time, we allow the object to be awaited directly. All functional
 1416            // associated with the implementation is just delegated to the ManualResetValueTaskSourceCore.
 01417            ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) => _waitSource.GetStatus(token);
 01418            void IValueTaskSource.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceO
 1419            void IValueTaskSource.GetResult(short token)
 01420            {
 01421                Debug.Assert(!Monitor.IsEntered(SyncObject));
 1422
 1423                // Clean up the registration.  It's important to Dispose rather than Unregister, so that we wait
 1424                // for any in-flight cancellation to complete.
 01425                _waitSourceCancellation.Dispose();
 01426                _waitSourceCancellation = default;
 1427
 1428                // Propagate any exceptions if there were any.
 01429                _waitSource.GetResult(token);
 01430            }
 1431
 1432            private void WaitForData()
 01433            {
 1434                // See comments in WaitAsync.
 01435                Debug.Assert(!_waitSource.RunContinuationsAsynchronously);
 01436                new ValueTask(this, _waitSource.Version).AsTask().GetAwaiter().GetResult();
 01437            }
 1438
 1439            private ValueTask WaitForDataAsync(CancellationToken cancellationToken)
 01440            {
 01441                Debug.Assert(_waitSource.RunContinuationsAsynchronously);
 1442
 1443                // No locking is required here to access _waitSource.  To be here, we've already updated _hasWaiter (whi
 1444                // to indicate that we would be creating this waiter, and at that point the only code that could be awai
 1445                // Reset'ing it is this code here.  It's possible for this to race with the _waitSource being completed,
 1446                // handled by _waitSource as one of its primary purposes.  We can't assert _hasWaiter here, though, as o
 1447                // lock, a producer could have seen _hasWaiter as true and both set it to false and signaled _waitSource
 1448
 1449                // With HttpClient, the supplied cancellation token will always be cancelable, as HttpClient supplies a 
 1450                // will have cancellation requested if CancelPendingRequests is called (or when a non-infinite Timeout e
 1451                // However, this could still be non-cancelable if HttpMessageInvoker was used, at which point this will 
 1452                // cancelable if the caller's token was cancelable.
 1453
 01454                _waitSourceCancellation = cancellationToken.UnsafeRegister(static (s, cancellationToken) =>
 01455                {
 01456                    var thisRef = (Http2Stream)s!;
 01457
 01458                    bool signalWaiter;
 01459                    Debug.Assert(!Monitor.IsEntered(thisRef.SyncObject));
 01460                    lock (thisRef.SyncObject)
 01461                    {
 01462                        signalWaiter = thisRef._hasWaiter;
 01463                        thisRef._hasWaiter = false;
 01464                    }
 01465
 01466                    if (signalWaiter)
 01467                    {
 01468                        // Wake up the wait.  It will then immediately check whether cancellation was requested and thro
 01469                        thisRef._waitSource.SetException(ExceptionDispatchInfo.SetCurrentStackTrace(
 01470                            CancellationHelper.CreateOperationCanceledException(null, cancellationToken)));
 01471                    }
 01472                }, this);
 1473
 01474                return new ValueTask(this, _waitSource.Version);
 01475            }
 1476
 1477            public void Trace(string message, [CallerMemberName] string? memberName = null) =>
 01478                _connection.Trace(StreamId, message, memberName);
 1479
 1480            private enum ResponseProtocolState : byte
 1481            {
 1482                ExpectingStatus,
 1483                ExpectingIgnoredHeaders,
 1484                ExpectingHeaders,
 1485                ExpectingData,
 1486                ExpectingTrailingHeaders,
 1487                Complete,
 1488                Aborted
 1489            }
 1490
 1491            private enum StreamCompletionState : byte
 1492            {
 1493                InProgress,
 1494                Completed,
 1495                Failed
 1496            }
 1497
 1498            private sealed class Http2ReadStream : Http2ReadWriteStream
 1499            {
 01500                public Http2ReadStream(Http2Stream http2Stream) : base(http2Stream, closeResponseBodyOnDispose: true) { 
 1501
 01502                public override bool CanWrite => false;
 1503
 01504                public override void Write(ReadOnlySpan<byte> buffer) => throw new NotSupportedException(SR.net_http_con
 1505
 01506                public override ValueTask WriteAsync(ReadOnlyMemory<byte> destination, CancellationToken cancellationTok
 1507            }
 1508
 1509            private sealed class Http2WriteStream : Http2ReadWriteStream
 1510            {
 01511                public long BytesWritten { get; private set; }
 1512
 01513                public long ContentLength { get; }
 1514
 01515                public Http2WriteStream(Http2Stream http2Stream, long contentLength) : base(http2Stream)
 01516                {
 01517                    Debug.Assert(contentLength >= -1);
 01518                    ContentLength = contentLength;
 01519                }
 1520
 01521                public override bool CanRead => false;
 1522
 01523                public override int Read(Span<byte> buffer) => throw new NotSupportedException(SR.net_http_content_write
 1524
 01525                public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken) => Va
 1526
 01527                public override void CopyTo(Stream destination, int bufferSize) => throw new NotSupportedException(SR.ne
 1528
 01529                public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken
 1530
 1531                public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
 01532                {
 01533                    BytesWritten += buffer.Length;
 1534
 01535                    if ((ulong)BytesWritten > (ulong)ContentLength) // If ContentLength == -1, this will always be false
 01536                    {
 01537                        return ValueTask.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(new HttpRequestExcepti
 1538                    }
 1539
 01540                    return base.WriteAsync(buffer, cancellationToken);
 01541                }
 1542            }
 1543
 1544            public class Http2ReadWriteStream : HttpBaseStream
 1545            {
 1546                private Http2Stream? _http2Stream;
 1547                private readonly HttpResponseMessage _responseMessage;
 1548
 01549                public Http2ReadWriteStream(Http2Stream http2Stream, bool closeResponseBodyOnDispose = false)
 01550                {
 01551                    Debug.Assert(http2Stream != null);
 01552                    Debug.Assert(http2Stream._response != null);
 01553                    _http2Stream = http2Stream;
 01554                    _responseMessage = _http2Stream._response;
 01555                    CloseResponseBodyOnDispose = closeResponseBodyOnDispose;
 01556                }
 1557
 1558                ~Http2ReadWriteStream()
 01559                {
 01560                    if (NetEventSource.Log.IsEnabled()) _http2Stream?.Trace("");
 1561                    try
 01562                    {
 01563                        Dispose(disposing: false);
 01564                    }
 01565                    catch (Exception e)
 01566                    {
 01567                        if (NetEventSource.Log.IsEnabled()) _http2Stream?.Trace($"Error: {e}");
 01568                    }
 01569                }
 1570
 01571                protected bool CloseResponseBodyOnDispose { get; private init; }
 1572
 1573                protected override void Dispose(bool disposing)
 01574                {
 01575                    Http2Stream? http2Stream = Interlocked.Exchange(ref _http2Stream, null);
 01576                    if (http2Stream == null)
 01577                    {
 01578                        return;
 1579                    }
 1580
 1581                    // Technically we shouldn't be doing the following work when disposing == false,
 1582                    // as the following work relies on other finalizable objects.  But given the HTTP/2
 1583                    // protocol, we have little choice: if someone drops the Http2ReadStream without
 1584                    // disposing of it, we need to a) signal to the server that the stream is being
 1585                    // canceled, and b) clean up the associated state in the Http2Connection.
 01586                    if (CloseResponseBodyOnDispose)
 01587                    {
 01588                        http2Stream.CloseResponseBody();
 01589                    }
 1590
 01591                    base.Dispose(disposing);
 01592                }
 1593
 01594                public override bool CanRead => _http2Stream != null;
 01595                public override bool CanWrite => _http2Stream != null;
 1596
 1597                public override int Read(Span<byte> destination)
 01598                {
 01599                    Http2Stream? http2Stream = _http2Stream;
 01600                    ObjectDisposedException.ThrowIf(http2Stream is null, this);
 1601
 01602                    return http2Stream.ReadData(destination, _responseMessage);
 01603                }
 1604
 1605                public override ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken)
 01606                {
 01607                    Http2Stream? http2Stream = _http2Stream;
 1608
 01609                    if (http2Stream == null)
 01610                    {
 01611                        return ValueTask.FromException<int>(ExceptionDispatchInfo.SetCurrentStackTrace(new ObjectDispose
 1612                    }
 1613
 01614                    if (cancellationToken.IsCancellationRequested)
 01615                    {
 01616                        return ValueTask.FromCanceled<int>(cancellationToken);
 1617                    }
 1618
 01619                    return http2Stream.ReadDataAsync(destination, _responseMessage, cancellationToken);
 01620                }
 1621
 1622                public override void CopyTo(Stream destination, int bufferSize)
 01623                {
 01624                    ValidateCopyToArguments(destination, bufferSize);
 01625                    Http2Stream http2Stream = _http2Stream ?? throw ExceptionDispatchInfo.SetCurrentStackTrace(new Objec
 01626                    http2Stream.CopyTo(_responseMessage, destination, bufferSize);
 01627                }
 1628
 1629                public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken
 01630                {
 01631                    ValidateCopyToArguments(destination, bufferSize);
 01632                    Http2Stream? http2Stream = _http2Stream;
 01633                    return
 01634                        http2Stream is null ? Task.FromException<int>(ExceptionDispatchInfo.SetCurrentStackTrace(new Obj
 01635                        cancellationToken.IsCancellationRequested ? Task.FromCanceled<int>(cancellationToken) :
 01636                        http2Stream.CopyToAsync(_responseMessage, destination, bufferSize, cancellationToken);
 01637                }
 1638
 1639                public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
 01640                {
 1641
 01642                    Http2Stream? http2Stream = _http2Stream;
 1643
 01644                    if (http2Stream == null)
 01645                    {
 01646                        return ValueTask.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(new ObjectDisposedExce
 1647                    }
 1648
 01649                    return http2Stream.SendDataAsync(buffer, cancellationToken);
 01650                }
 1651
 1652                public override Task FlushAsync(CancellationToken cancellationToken)
 01653                {
 01654                    if (cancellationToken.IsCancellationRequested)
 01655                    {
 01656                        return Task.FromCanceled(cancellationToken);
 1657                    }
 1658
 01659                    Http2Stream? http2Stream = _http2Stream;
 1660
 01661                    if (http2Stream == null)
 01662                    {
 01663                        return Task.CompletedTask;
 1664                    }
 1665
 1666                    // In order to flush this stream's previous writes, we need to flush the connection. We
 1667                    // really only need to do any work here if the connection's buffer has any pending writes
 1668                    // from this stream, but we currently lack a good/efficient/safe way of doing that.
 01669                    return http2Stream._connection.FlushAsync(cancellationToken);
 01670                }
 1671            }
 1672        }
 1673    }
 1674}
 1675

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.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.Diagnostics;
 5using System.Threading;
 6using System.Threading.Tasks;
 7
 8namespace System.Net.Http
 9{
 10    internal sealed partial class Http2Connection
 11    {
 12        // Maintains a dynamically-sized stream receive window, and sends WINDOW_UPDATE frames to the server.
 13        private struct Http2StreamWindowManager
 14        {
 015            private static double WindowScaleThresholdMultiplier => GlobalHttpSettings.SocketsHttpHandler.Http2StreamWin
 016            private static int MaxStreamWindowSize => GlobalHttpSettings.SocketsHttpHandler.MaxHttp2StreamWindowSize;
 017            private static bool WindowScalingEnabled => !GlobalHttpSettings.SocketsHttpHandler.DisableDynamicHttp2Window
 18
 19            private int _deliveredBytes;
 20            private int _streamWindowSize;
 21            private long _lastWindowUpdate;
 22
 23            public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream)
 024            {
 025                HttpConnectionSettings settings = connection._pool.Settings;
 026                _streamWindowSize = settings._initialHttp2StreamWindowSize;
 027                _deliveredBytes = 0;
 028                _lastWindowUpdate = default;
 29
 030                if (NetEventSource.Log.IsEnabled()) stream.Trace($"[FlowControl] InitialClientStreamWindowSize: {StreamW
 031            }
 32
 33            // We hold off on sending WINDOW_UPDATE until we hit the minimum threshold.
 34            // This value is somewhat arbitrary; the intent is to ensure it is much smaller than
 35            // the window size itself, or we risk stalling the server because it runs out of window space.
 36            public const int StreamWindowUpdateRatio = 8;
 037            internal int StreamWindowThreshold => _streamWindowSize / StreamWindowUpdateRatio;
 38
 039            internal int StreamWindowSize => _streamWindowSize;
 40
 41            public void Start()
 042            {
 043                _lastWindowUpdate = Stopwatch.GetTimestamp();
 044            }
 45
 46            public void AdjustWindow(int bytesConsumed, Http2Stream stream)
 047            {
 048                Debug.Assert(_lastWindowUpdate != default); // Make sure Start() has been invoked, otherwise we should n
 049                Debug.Assert(bytesConsumed > 0);
 050                Debug.Assert(_deliveredBytes < StreamWindowThreshold);
 51
 052                if (!stream.ExpectResponseData)
 053                {
 54                    // We are not expecting any more data (because we've either completed or aborted).
 55                    // So no need to send any more WINDOW_UPDATEs.
 056                    return;
 57                }
 58
 059                if (WindowScalingEnabled)
 060                {
 061                    AdjustWindowDynamic(bytesConsumed, stream);
 062                }
 63                else
 064                {
 065                    AjdustWindowStatic(bytesConsumed, stream);
 066                }
 067            }
 68
 69            private void AjdustWindowStatic(int bytesConsumed, Http2Stream stream)
 070            {
 071                _deliveredBytes += bytesConsumed;
 072                if (_deliveredBytes < StreamWindowThreshold)
 073                {
 074                    return;
 75                }
 76
 077                int windowUpdateIncrement = _deliveredBytes;
 078                _deliveredBytes = 0;
 79
 080                Http2Connection connection = stream.Connection;
 081                Task sendWindowUpdateTask = connection.SendWindowUpdateAsync(stream.StreamId, windowUpdateIncrement);
 082                connection.LogExceptions(sendWindowUpdateTask);
 083            }
 84
 85            private void AdjustWindowDynamic(int bytesConsumed, Http2Stream stream)
 086            {
 087                _deliveredBytes += bytesConsumed;
 88
 089                if (_deliveredBytes < StreamWindowThreshold)
 090                {
 091                    return;
 92                }
 93
 094                int windowUpdateIncrement = _deliveredBytes;
 095                long currentTime = Stopwatch.GetTimestamp();
 096                Http2Connection connection = stream.Connection;
 97
 098                TimeSpan rtt = connection._rttEstimator.MinRtt;
 099                if (rtt > TimeSpan.Zero && _streamWindowSize < MaxStreamWindowSize)
 0100                {
 0101                    TimeSpan dt = Stopwatch.GetElapsedTime(_lastWindowUpdate, currentTime);
 102
 103                    // We are detecting bursts in the amount of data consumed within a single 'dt' window update period.
 104                    // The value "_deliveredBytes / dt" correlates with the bandwidth of the connection.
 105                    // We need to extend the window, if the bandwidth-delay product grows over the current window size.
 106                    // To enable empirical fine tuning, we apply a configurable multiplier (_windowScaleThresholdMultipl
 107                    //
 108                    // The condition to extend the window is:
 109                    // (_deliveredBytes / dt) * rtt > _streamWindowSize * _windowScaleThresholdMultiplier
 110                    //
 111                    // Which is reordered into the form below, to avoid the division:
 0112                    if (_deliveredBytes * (double)rtt.Ticks > _streamWindowSize * dt.Ticks * WindowScaleThresholdMultipl
 0113                    {
 0114                        int extendedWindowSize = Math.Min(MaxStreamWindowSize, _streamWindowSize * 2);
 0115                        windowUpdateIncrement += extendedWindowSize - _streamWindowSize;
 0116                        _streamWindowSize = extendedWindowSize;
 117
 0118                        if (NetEventSource.Log.IsEnabled()) stream.Trace($"[FlowControl] Updated Stream Window. StreamWi
 119
 0120                        Debug.Assert(_streamWindowSize <= MaxStreamWindowSize);
 0121                        if (_streamWindowSize == MaxStreamWindowSize)
 0122                        {
 0123                            if (NetEventSource.Log.IsEnabled()) stream.Trace($"[FlowControl] StreamWindowSize reached th
 0124                        }
 0125                    }
 0126                }
 127
 0128                _deliveredBytes = 0;
 129
 0130                Task sendWindowUpdateTask = connection.SendWindowUpdateAsync(stream.StreamId, windowUpdateIncrement);
 0131                connection.LogExceptions(sendWindowUpdateTask);
 132
 0133                _lastWindowUpdate = currentTime;
 0134            }
 135        }
 136
 137        // Estimates Round Trip Time between the client and the server by sending PING frames, and measuring the time in
 138        // Assuming that the network characteristics of the connection wouldn't change much within its lifetime, we are 
 139        // The more PINGs we send, the more accurate is the estimation of MinRtt, however we should be careful not to se
 140        // to avoid triggering the server's PING flood protection which may result in an unexpected GOAWAY.
 141        //
 142        // Several strategies have been implemented to conform with real life servers.
 143        // 1. With most servers we are fine to send PINGs as long as we are reading their data, a rule formalized by a g
 144        // https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md
 145        // According to this rule, we are OK to send a PING whenever we receive DATA or HEADERS, since the servers confo
 146        // will reset their unsolicited ping counter whenever they *send* DATA or HEADERS.
 147        // 2. Some servers allow receiving only a limited amount of PINGs within a given timeframe.
 148        // To deal with this, we send an initial burst of 'InitialBurstCount' (=4) PINGs, to get a relatively good estim
 149        // we send PINGs each 'PingIntervalInSeconds' second, to maintain our estimation without triggering these server
 150        // 3. Some servers in Google's backends reset their unsolicited ping counter when they *receive* DATA, HEADERS, 
 151        // To deal with this, we need to make sure to send a connection WINDOW_UPDATE before sending a PING. The initial
 152        // to this rule, since the mentioned server can tolerate 4 PINGs without receiving a WINDOW_UPDATE.
 153        //
 154        // Threading:
 155        // OnInitialSettingsSent() is called during initialization, all other methods are triggered by HttpConnection.Pr
 156        // therefore the assumption is that the invocation of RttEstimator's methods is sequential, and there is no race
 157        // Http2StreamWindowManager is reading MinRtt from another concurrent thread, therefore its value has to be chan
 158        private struct RttEstimator
 159        {
 160            private enum State
 161            {
 162                Disabled,
 163                Init,
 164                Waiting,
 165                PingSent,
 166                TerminatingMayReceivePingAck
 167            }
 168
 169            private const double PingIntervalInSeconds = 2;
 170            private const int InitialBurstCount = 4;
 0171            private static readonly long PingIntervalInTicks = (long)(PingIntervalInSeconds * Stopwatch.Frequency);
 172
 173            private State _state;
 174            private long _pingSentTimestamp;
 175            private long _pingCounter;
 176            private int _initialBurst;
 177            private long _minRtt;
 178
 0179            public TimeSpan MinRtt => new TimeSpan(_minRtt);
 180
 181            public static RttEstimator Create()
 0182            {
 0183                RttEstimator e = default;
 0184                e._state = GlobalHttpSettings.SocketsHttpHandler.DisableDynamicHttp2WindowSizing ? State.Disabled : Stat
 0185                e._initialBurst = InitialBurstCount;
 0186                return e;
 0187            }
 188
 189            internal void OnInitialSettingsSent()
 0190            {
 0191                if (_state == State.Disabled) return;
 0192                _pingSentTimestamp = Stopwatch.GetTimestamp();
 0193            }
 194
 195            internal void OnInitialSettingsAckReceived(Http2Connection connection)
 0196            {
 0197                if (_state == State.Disabled) return;
 0198                RefreshRtt(connection);
 0199                _state = State.Waiting;
 0200            }
 201
 202            internal void OnDataOrHeadersReceived(Http2Connection connection, bool sendWindowUpdateBeforePing)
 0203            {
 0204                if (_state != State.Waiting) return;
 205
 0206                long now = Stopwatch.GetTimestamp();
 0207                bool initial = _initialBurst > 0;
 0208                if (initial || now - _pingSentTimestamp > PingIntervalInTicks)
 0209                {
 0210                    if (initial) _initialBurst--;
 211
 212                    // When sendWindowUpdateBeforePing is true, try to send a WINDOW_UPDATE to make Google backends happ
 213                    // Unless we are doing the initial burst, do not send PING if we were not able to send the WINDOW_UP
 214                    // See point 3. in the comments above the class definition for more info.
 0215                    if (sendWindowUpdateBeforePing && !connection.ForceSendConnectionWindowUpdate() && !initial)
 0216                    {
 0217                        return;
 218                    }
 219
 220                    // Send a PING
 0221                    _pingCounter--;
 0222                    if (NetEventSource.Log.IsEnabled()) connection.Trace($"[FlowControl] Sending RTT PING with payload {
 0223                    connection.QueuePing(_pingCounter, isAck: false);
 0224                    _pingSentTimestamp = now;
 0225                    _state = State.PingSent;
 0226                }
 0227            }
 228
 229            internal void OnPingAckReceived(long payload, Http2Connection connection)
 0230            {
 0231                if (_state != State.PingSent && _state != State.TerminatingMayReceivePingAck)
 0232                {
 0233                    if (NetEventSource.Log.IsEnabled()) connection.Trace($"[FlowControl] Unexpected PING ACK in state {_
 0234                    ThrowProtocolError();
 235                }
 236
 0237                if (_state == State.TerminatingMayReceivePingAck)
 0238                {
 0239                    _state = State.Disabled;
 0240                    return;
 241                }
 242
 243                // RTT PINGs always carry negative payload, positive values indicate a response to KeepAlive PING.
 0244                Debug.Assert(payload < 0);
 245
 0246                if (_pingCounter != payload)
 0247                {
 0248                    if (NetEventSource.Log.IsEnabled()) connection.Trace($"[FlowControl] Unexpected RTT PING ACK payload
 0249                    ThrowProtocolError();
 250                }
 251
 0252                RefreshRtt(connection);
 0253                _state = State.Waiting;
 0254            }
 255
 256            internal void OnGoAwayReceived()
 0257            {
 0258                if (_state == State.PingSent)
 0259                {
 260                    // We may still receive a PING ACK, but we should not send anymore PING:
 0261                    _state = State.TerminatingMayReceivePingAck;
 0262                }
 263                else
 0264                {
 0265                    _state = State.Disabled;
 0266                }
 0267            }
 268
 269            private void RefreshRtt(Http2Connection connection)
 0270            {
 0271                long prevRtt = _minRtt == 0 ? long.MaxValue : _minRtt;
 0272                TimeSpan currentRtt = Stopwatch.GetElapsedTime(_pingSentTimestamp);
 0273                long minRtt = Math.Min(prevRtt, currentRtt.Ticks);
 274
 0275                Interlocked.Exchange(ref _minRtt, minRtt); // MinRtt is being queried from another thread
 276
 0277                if (NetEventSource.Log.IsEnabled()) connection.Trace($"[FlowControl] Updated MinRtt: {MinRtt.TotalMillis
 0278            }
 279        }
 280    }
 281}
 282

Methods/Properties

ProtocolLiteralHeaderBytes()
.cctor()
.ctor(System.Net.Http.HttpConnectionPool,System.IO.Stream,System.Diagnostics.Activity,System.Net.IPEndPoint,System.Int64)
Http2ConnectionPreface()
TimeSpanToMs(System.TimeSpan)
Finalize()
SyncObject()
InitialSettingsReceived()
IsConnectEnabled()
SetupAsync(System.Threading.CancellationToken)
Shutdown()
TryReserveStream()
ReleaseStream()
WaitForAvailableStreamsAsync()
SignalAvailableStreamsWaiter(System.Boolean)
FlushOutgoingBytesAsync()
ReadFrameAsync(System.Boolean)
ThrowPrematureEOF(System.Int32)
ThrowMissingFrame()
ProcessIncomingFramesAsync()
GetStream(System.Int32)
ProcessHeadersFrame(System.Net.Http.Http2Connection/FrameHeader)
.cctor()
System.Net.Http.IHttpStreamHeadersHandler.OnHeader(System.ReadOnlySpan`1<System.Byte>,System.ReadOnlySpan`1<System.Byte>)
System.Net.Http.IHttpStreamHeadersHandler.OnHeadersComplete(System.Boolean)
System.Net.Http.IHttpStreamHeadersHandler.OnStaticIndexedHeader(System.Int32)
System.Net.Http.IHttpStreamHeadersHandler.OnStaticIndexedHeader(System.Int32,System.ReadOnlySpan`1<System.Byte>)
System.Net.Http.IHttpStreamHeadersHandler.OnDynamicIndexedHeader(System.Nullable`1<System.Int32>,System.ReadOnlySpan`1<System.Byte>,System.ReadOnlySpan`1<System.Byte>)
GetFrameData(System.ReadOnlySpan`1<System.Byte>,System.Boolean,System.Boolean)
ProcessAltSvcFrame(System.Net.Http.Http2Connection/FrameHeader)
ProcessDataFrame(System.Net.Http.Http2Connection/FrameHeader)
ProcessSettingsFrame(System.Net.Http.Http2Connection/FrameHeader,System.Boolean)
ChangeMaxConcurrentStreams(System.UInt32)
ChangeInitialWindowSize(System.Int32)
ProcessPriorityFrame(System.Net.Http.Http2Connection/FrameHeader)
ProcessPingFrame(System.Net.Http.Http2Connection/FrameHeader)
ProcessWindowUpdateFrame(System.Net.Http.Http2Connection/FrameHeader)
ProcessRstStreamFrame(System.Net.Http.Http2Connection/FrameHeader)
ProcessGoAwayFrame(System.Net.Http.Http2Connection/FrameHeader)
ReadGoAwayFrame(System.Net.Http.Http2Connection/FrameHeader)
FlushAsync(System.Threading.CancellationToken)
.ctor(System.Int32,System.Threading.CancellationToken)
WriteBytes()
TryDisableCancellation()
.ctor(System.Int32,T,System.Func`3<T,System.Memory`1<System.Byte>,System.Boolean>,System.Threading.CancellationToken)
InvokeWriteAction(System.Memory`1<System.Byte>)
PerformWriteAsync(System.Int32,T,System.Func`3<T,System.Memory`1<System.Byte>,System.Boolean>,System.Threading.CancellationToken)
ProcessOutgoingFramesAsync()
QueueSettingsAck()
QueuePing(System.Int64,System.Boolean)
SendRstStreamAsync(System.Int32,System.Net.Http.Http2ProtocolErrorCode)
HeartBeat()
SplitBuffer(System.ReadOnlyMemory`1<System.Byte>,System.Int32)
WriteIndexedHeader(System.Int32,System.Net.ArrayBuffer&)
WriteIndexedHeader(System.Int32,System.String,System.Net.ArrayBuffer&)
WriteLiteralHeader(System.String,System.ReadOnlySpan`1<System.String>,System.Text.Encoding,System.Net.ArrayBuffer&)
WriteLiteralHeaderValues(System.ReadOnlySpan`1<System.String>,System.Byte[],System.Text.Encoding,System.Net.ArrayBuffer&)
WriteLiteralHeaderValue(System.String,System.Text.Encoding,System.Net.ArrayBuffer&)
WriteBytes(System.ReadOnlySpan`1<System.Byte>,System.Net.ArrayBuffer&)
WriteHeaderCollection(System.Net.Http.HttpRequestMessage,System.Net.Http.Headers.HttpHeaders,System.Net.ArrayBuffer&)
WriteHeaders(System.Net.Http.HttpRequestMessage,System.Net.ArrayBuffer&)
AddStream(System.Net.Http.Http2Connection/Http2Stream)
SendHeadersAsync(System.Net.Http.HttpRequestMessage,System.Threading.CancellationToken,System.Boolean)
SendStreamDataAsync(System.Int32,System.ReadOnlyMemory`1<System.Byte>,System.Boolean,System.Threading.CancellationToken)
SendEndStreamAsync(System.Int32)
SendWindowUpdateAsync(System.Int32,System.Int32)
ExtendWindow(System.Int32)
ForceSendConnectionWindowUpdate()
TryIncrementQueuedFireAndForgetFrames()
DecrementQueuedFireAndForgetFrames()
Abort(System.Exception)
FinalTeardown()
Dispose()
.ctor(System.Int32,System.Net.Http.Http2Connection/FrameType,System.Net.Http.Http2Connection/FrameFlags,System.Int32)
PaddedFlag()
AckFlag()
EndHeadersFlag()
EndStreamFlag()
PriorityFlag()
ReadFrom(System.ReadOnlySpan`1<System.Byte>)
WriteTo(System.Span`1<System.Byte>,System.Int32,System.Net.Http.Http2Connection/FrameType,System.Net.Http.Http2Connection/FrameFlags,System.Int32)
ToString()
CreateSuccessfullyCompletedTcs()
SendAsync(System.Net.Http.HttpRequestMessage,System.Boolean,System.Threading.CancellationToken)
RemoveStream(System.Net.Http.Http2Connection/Http2Stream)
RefreshPingTimestamp()
ProcessPingAck(System.Int64)
VerifyKeepAlive()
ToString()
Trace(System.String,System.String)
Trace(System.Int32,System.String,System.String)
ThrowRetry(System.String,System.Exception)
GetRequestAbortedException(System.Exception)
ThrowRequestAborted(System.Exception)
ThrowProtocolError()
ThrowProtocolError(System.Net.Http.Http2ProtocolErrorCode,System.String)
StatusHeaderName()
.ctor(System.Net.Http.HttpRequestMessage,System.Net.Http.Http2Connection)
SyncObject()
Initialize(System.Int32,System.Int32)
StreamId()
SendRequestFinished()
ExpectResponseData()
Connection()
ConnectProtocolEstablished()
GetAndClearResponse()
SendRequestBodyAsync(System.Threading.CancellationToken)
WaitFor100ContinueAsync(System.Threading.CancellationToken)
SendReset()
Complete()
Cancel()
CancelResponseBody()
OnWindowUpdate(System.Int32)
HpackStaticStatusCodeTable()
.cctor()
System.Net.Http.IHttpStreamHeadersHandler.OnStaticIndexedHeader(System.Int32)
System.Net.Http.IHttpStreamHeadersHandler.OnStaticIndexedHeader(System.Int32,System.ReadOnlySpan`1<System.Byte>)
System.Net.Http.IHttpStreamHeadersHandler.OnDynamicIndexedHeader(System.Nullable`1<System.Int32>,System.ReadOnlySpan`1<System.Byte>,System.ReadOnlySpan`1<System.Byte>)
AdjustHeaderBudget(System.Int32)
OnStatus(System.Int32)
OnHeader(System.Net.Http.Headers.HeaderDescriptor,System.ReadOnlySpan`1<System.Byte>)
OnHeader(System.ReadOnlySpan`1<System.Byte>,System.ReadOnlySpan`1<System.Byte>)
OnHeadersStart()
OnHeadersComplete(System.Boolean)
OnResponseData(System.ReadOnlySpan`1<System.Byte>,System.Boolean)
OnReset(System.Exception,System.Nullable`1<System.Net.Http.Http2ProtocolErrorCode>,System.Boolean)
CheckResponseBodyState()
TryEnsureHeaders()
ReadResponseHeadersAsync(System.Threading.CancellationToken)
TryReadFromBuffer(System.Span`1<System.Byte>,System.Boolean)
ReadData(System.Span`1<System.Byte>,System.Net.Http.HttpResponseMessage)
ReadDataAsync(System.Memory`1<System.Byte>,System.Net.Http.HttpResponseMessage,System.Threading.CancellationToken)
CopyTo(System.Net.Http.HttpResponseMessage,System.IO.Stream,System.Int32)
CopyToAsync(System.Net.Http.HttpResponseMessage,System.IO.Stream,System.Int32,System.Threading.CancellationToken)
MoveTrailersToResponseMessage(System.Net.Http.HttpResponseMessage)
SendDataAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
CloseResponseBody()
RegisterRequestBodyCancellation(System.Threading.CancellationToken)
System.Threading.Tasks.Sources.IValueTaskSource.GetStatus(System.Int16)
System.Threading.Tasks.Sources.IValueTaskSource.OnCompleted(System.Action`1<System.Object>,System.Object,System.Int16,System.Threading.Tasks.Sources.ValueTaskSourceOnCompletedFlags)
System.Threading.Tasks.Sources.IValueTaskSource.GetResult(System.Int16)
WaitForData()
WaitForDataAsync(System.Threading.CancellationToken)
Trace(System.String,System.String)
.ctor(System.Net.Http.Http2Connection/Http2Stream)
CanWrite()
Write(System.ReadOnlySpan`1<System.Byte>)
WriteAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
BytesWritten()
ContentLength()
.ctor(System.Net.Http.Http2Connection/Http2Stream,System.Int64)
CanRead()
Read(System.Span`1<System.Byte>)
ReadAsync(System.Memory`1<System.Byte>,System.Threading.CancellationToken)
CopyTo(System.IO.Stream,System.Int32)
CopyToAsync(System.IO.Stream,System.Int32,System.Threading.CancellationToken)
WriteAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
.ctor(System.Net.Http.Http2Connection/Http2Stream,System.Boolean)
Finalize()
CloseResponseBodyOnDispose()
Dispose(System.Boolean)
CanRead()
CanWrite()
Read(System.Span`1<System.Byte>)
ReadAsync(System.Memory`1<System.Byte>,System.Threading.CancellationToken)
CopyTo(System.IO.Stream,System.Int32)
CopyToAsync(System.IO.Stream,System.Int32,System.Threading.CancellationToken)
WriteAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
FlushAsync(System.Threading.CancellationToken)
WindowScaleThresholdMultiplier()
MaxStreamWindowSize()
WindowScalingEnabled()
.ctor(System.Net.Http.Http2Connection,System.Net.Http.Http2Connection/Http2Stream)
StreamWindowThreshold()
StreamWindowSize()
Start()
AdjustWindow(System.Int32,System.Net.Http.Http2Connection/Http2Stream)
AjdustWindowStatic(System.Int32,System.Net.Http.Http2Connection/Http2Stream)
AdjustWindowDynamic(System.Int32,System.Net.Http.Http2Connection/Http2Stream)
.cctor()
MinRtt()
Create()
OnInitialSettingsSent()
OnInitialSettingsAckReceived(System.Net.Http.Http2Connection)
OnDataOrHeadersReceived(System.Net.Http.Http2Connection,System.Boolean)
OnPingAckReceived(System.Int64,System.Net.Http.Http2Connection)
OnGoAwayReceived()
RefreshRtt(System.Net.Http.Http2Connection)