< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 2358
Coverable lines: 2358
Total lines: 4146
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 902
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%660%
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%14140%
File 1: ProcessDataFrame(...)0%10100%
File 1: ProcessSettingsFrame(...)0%39390%
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: SendSettingsAckAsync()0%220%
File 1: SendPingAsync(...)0%440%
File 1: SendRstStreamAsync(...)0%220%
File 1: HeartBeat()0%440%
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%660%
File 1: SendStreamDataAsync()0%880%
File 1: SendEndStreamAsync(...)0%220%
File 1: SendWindowUpdateAsync(...)0%220%
File 1: ExtendWindow(...)0%660%
File 1: ForceSendConnectionWindowUpdate()0%440%
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%26260%
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%440%
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)

D:\runner\runtime\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        private const int MaxStreamId = int.MaxValue;
 79
 80        // Temporary workaround for request burst handling on connection start.
 81        private const int InitialMaxConcurrentStreams = 100;
 82
 083        private static ReadOnlySpan<byte> Http2ConnectionPreface => "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"u8;
 84
 85#if DEBUG
 86        // In debug builds, start with a very small buffer to induce buffer growing logic.
 87        private const int InitialConnectionBufferSize = FrameHeader.Size;
 88#else
 89        // Rent enough space to receive a full data frame in one read call.
 90        private const int InitialConnectionBufferSize = FrameHeader.Size + FrameHeader.MaxPayloadLength;
 91#endif
 92
 93        // The default initial window size for streams and connections according to the RFC:
 94        // https://datatracker.ietf.org/doc/html/rfc7540#section-5.2.1
 95        // Unlike HttpHandlerDefaults.DefaultInitialHttp2StreamWindowSize, this value should never be changed.
 96        internal const int DefaultInitialWindowSize = 65535;
 97
 98        // We don't really care about limiting control flow at the connection level.
 99        // We limit it per stream, and the user controls how many streams are created.
 100        // So set the connection window size to a large value.
 101        private const int ConnectionWindowSize = 64 * 1024 * 1024;
 102
 103        // We hold off on sending WINDOW_UPDATE until we hit the minimum threshold.
 104        // This value is somewhat arbitrary; the intent is to ensure it is much smaller than
 105        // the window size itself, or we risk stalling the server because it runs out of window space.
 106        // If we want to further reduce the frequency of WINDOW_UPDATEs, it's probably better to
 107        // increase the window size (and thus increase the threshold proportionally)
 108        // rather than just increase the threshold.
 109        private const int ConnectionWindowUpdateRatio = 8;
 110        private const int ConnectionWindowThreshold = ConnectionWindowSize / ConnectionWindowUpdateRatio;
 111
 112        // When buffering outgoing writes, we will automatically buffer up to this number of bytes.
 113        // Single writes that are larger than the buffer can cause the buffer to expand beyond
 114        // this value, so this is not a hard maximum size.
 115        private const int UnflushedOutgoingBufferSize = 32 * 1024;
 116
 117        // Channel options for creating _writeChannel
 0118        private static readonly UnboundedChannelOptions s_channelOptions = new UnboundedChannelOptions() { SingleReader 
 119
 120        internal enum KeepAliveState
 121        {
 122            None,
 123            PingSent
 124        }
 125
 126        private readonly long _keepAlivePingDelay;
 127        private readonly long _keepAlivePingTimeout;
 128        private readonly HttpKeepAlivePingPolicy _keepAlivePingPolicy;
 129        private long _keepAlivePingPayload;
 130        private long _nextPingRequestTimestamp;
 131        private long _keepAlivePingTimeoutTimestamp;
 132        private volatile KeepAliveState _keepAliveState;
 133
 134        public Http2Connection(HttpConnectionPool pool, Stream stream, Activity? connectionSetupActivity, IPEndPoint? re
 0135            : base(pool, connectionSetupActivity, remoteEndPoint)
 0136        {
 0137            _stream = stream;
 138
 0139            _incomingBuffer = new ArrayBuffer(initialSize: 0, usePool: true);
 0140            _outgoingBuffer = new ArrayBuffer(initialSize: 0, usePool: true);
 141
 0142            _hpackDecoder = new HPackDecoder(maxHeadersLength: pool.Settings.MaxResponseHeadersByteLength);
 143
 0144            _httpStreams = new Dictionary<int, Http2Stream>();
 145
 0146            _connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialWindowSize);
 147
 0148            _rttEstimator = RttEstimator.Create();
 149
 0150            _writeChannel = Channel.CreateUnbounded<WriteQueueEntry>(s_channelOptions);
 151
 0152            _nextStream = 1;
 0153            _initialServerStreamWindowSize = DefaultInitialWindowSize;
 154
 0155            _maxConcurrentStreams = InitialMaxConcurrentStreams;
 0156            _streamsInUse = 0;
 157
 0158            _pendingWindowUpdate = 0;
 159
 0160            _keepAlivePingDelay = TimeSpanToMs(_pool.Settings._keepAlivePingDelay);
 0161            _keepAlivePingTimeout = TimeSpanToMs(_pool.Settings._keepAlivePingTimeout);
 0162            _nextPingRequestTimestamp = Environment.TickCount64 + _keepAlivePingDelay;
 0163            _keepAlivePingPolicy = _pool.Settings._keepAlivePingPolicy;
 164
 0165            uint maxHeaderListSize = _pool._lastSeenHttp2MaxHeaderListSize;
 0166            if (maxHeaderListSize > 0)
 0167            {
 168                // Previous connections to the same host advertised a limit.
 169                // Use this as an initial value before we receive the SETTINGS frame.
 0170                _maxHeaderListSize = maxHeaderListSize;
 0171            }
 172
 0173            if (NetEventSource.Log.IsEnabled()) TraceConnection(_stream);
 174
 175            static long TimeSpanToMs(TimeSpan value)
 0176            {
 0177                double milliseconds = value.TotalMilliseconds;
 0178                return (long)(milliseconds > int.MaxValue ? int.MaxValue : milliseconds);
 0179            }
 0180        }
 181
 0182        ~Http2Connection() => Dispose();
 183
 0184        private object SyncObject => _httpStreams;
 185
 186        internal TaskCompletionSourceWithCancellation<bool> InitialSettingsReceived =>
 0187            _initialSettingsReceived ??
 0188            Interlocked.CompareExchange(ref _initialSettingsReceived, new(), null) ??
 0189            _initialSettingsReceived;
 190
 0191        internal bool IsConnectEnabled { get; private set; }
 192
 193        public async ValueTask SetupAsync(CancellationToken cancellationToken)
 0194        {
 195            try
 0196            {
 0197                int requiredSpace = Http2ConnectionPreface.Length +
 0198                    FrameHeader.Size + (2 * FrameHeader.SettingLength) +
 0199                    FrameHeader.Size + FrameHeader.WindowUpdateLength;
 200
 0201                _outgoingBuffer.EnsureAvailableSpace(requiredSpace);
 202
 203                // Send connection preface
 0204                Http2ConnectionPreface.CopyTo(_outgoingBuffer.AvailableSpan);
 0205                _outgoingBuffer.Commit(Http2ConnectionPreface.Length);
 206
 207                // Send SETTINGS frame.  Disable push promise & set initial window size.
 0208                FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, 2 * FrameHeader.SettingLength, FrameType.Settings, Fr
 0209                _outgoingBuffer.Commit(FrameHeader.Size);
 0210                BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.EnablePush);
 0211                _outgoingBuffer.Commit(2);
 0212                BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, 0);
 0213                _outgoingBuffer.Commit(4);
 0214                BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.InitialWindowSize
 0215                _outgoingBuffer.Commit(2);
 0216                BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, (uint)_pool.Settings._initialHttp2S
 0217                _outgoingBuffer.Commit(4);
 218
 219                // The connection-level window size can not be initialized by SETTINGS frames:
 220                // https://datatracker.ietf.org/doc/html/rfc7540#section-6.9.2
 221                // Send an initial connection-level WINDOW_UPDATE to setup the desired ConnectionWindowSize:
 0222                uint windowUpdateAmount = ConnectionWindowSize - DefaultInitialWindowSize;
 0223                if (NetEventSource.Log.IsEnabled()) Trace($"Initial connection-level WINDOW_UPDATE, windowUpdateAmount={
 0224                FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, FrameHeader.WindowUpdateLength, FrameType.WindowUpdat
 0225                _outgoingBuffer.Commit(FrameHeader.Size);
 0226                BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, windowUpdateAmount);
 0227                _outgoingBuffer.Commit(4);
 228
 0229                Debug.Assert(requiredSpace == _outgoingBuffer.ActiveLength);
 230
 231                // Processing the incoming frames before sending the client preface and SETTINGS is necessary when using
 232                // If the preface and SETTINGS coming from the server are not read first the below WriteAsync and the Pr
 233                // Avoid capturing the initial request's ExecutionContext for the entire lifetime of the new connection.
 0234                using (ExecutionContext.SuppressFlow())
 0235                {
 0236                    _ = ProcessIncomingFramesAsync();
 0237                }
 238
 0239                await _stream.WriteAsync(_outgoingBuffer.ActiveMemory, cancellationToken).ConfigureAwait(false);
 0240                _rttEstimator.OnInitialSettingsSent();
 0241                _outgoingBuffer.ClearAndReturnBuffer();
 0242            }
 0243            catch (Exception e)
 0244            {
 245                // ProcessIncomingFramesAsync and ProcessOutgoingFramesAsync are responsible for disposing/returning the
 246                // SetupAsync is the exception as it's responsible for starting the ProcessOutgoingFramesAsync loop.
 247                // As we're about to throw and ProcessOutgoingFramesAsync will never be called, we must return the buffe
 0248                _outgoingBuffer.Dispose();
 249
 0250                Dispose();
 251
 0252                if (e is OperationCanceledException oce && oce.CancellationToken == cancellationToken)
 0253                {
 254                    // Note, AddHttp2ConnectionAsync handles this OCE separately so don't wrap it.
 0255                    throw;
 256                }
 257
 258                // TODO: Review this case!
 0259                throw new IOException(SR.net_http_http2_connection_not_established, e);
 260            }
 261
 262            // Avoid capturing the initial request's ExecutionContext for the entire lifetime of the new connection.
 0263            using (ExecutionContext.SuppressFlow())
 0264            {
 0265                _ = ProcessOutgoingFramesAsync();
 0266            }
 0267        }
 268
 269        private void Shutdown()
 0270        {
 0271            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(_shutdown)}={_shutdown}, {nameof(_abortException)}={_abo
 272
 0273            Debug.Assert(Monitor.IsEntered(SyncObject));
 0274            Debug.Assert(!_pool.HasSyncObjLock);
 275
 0276            if (!_shutdown)
 0277            {
 278                // InvalidateHttp2Connection may call back into Shutdown,
 279                // so we set the flag early to prevent executing FinalTeardown twice.
 0280                _shutdown = true;
 281
 0282                _pool.InvalidateHttp2Connection(this);
 0283                SignalAvailableStreamsWaiter(false);
 284
 0285                if (_streamsInUse == 0)
 0286                {
 0287                    FinalTeardown();
 0288                }
 0289            }
 0290        }
 291
 292        public bool TryReserveStream()
 0293        {
 0294            Debug.Assert(!_pool.HasSyncObjLock);
 295
 0296            lock (SyncObject)
 0297            {
 0298                if (_shutdown)
 0299                {
 0300                    return false;
 301                }
 302
 0303                if (_streamsInUse < _maxConcurrentStreams)
 0304                {
 0305                    _streamsInUse++;
 0306                    return true;
 307                }
 0308            }
 309
 0310            return false;
 0311        }
 312
 313        // Can be called by the HttpConnectionPool after TryReserveStream if the stream doesn't end up being used.
 314        // Otherwise, will be called when the request is complete and stream is closed.
 315        public void ReleaseStream()
 0316        {
 0317            Debug.Assert(!_pool.HasSyncObjLock);
 318
 0319            lock (SyncObject)
 0320            {
 0321                if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(_streamsInUse)}={_streamsInUse}");
 322
 0323                Debug.Assert(_availableStreamsWaiter is null || _streamsInUse >= _maxConcurrentStreams);
 324
 0325                _streamsInUse--;
 326
 0327                Debug.Assert(_streamsInUse >= _httpStreams.Count);
 328
 0329                if (_streamsInUse < _maxConcurrentStreams)
 0330                {
 0331                    SignalAvailableStreamsWaiter(true);
 0332                }
 333
 0334                if (_streamsInUse == 0)
 0335                {
 0336                    if (_shutdown)
 0337                    {
 0338                        FinalTeardown();
 0339                    }
 0340                }
 0341            }
 0342        }
 343
 344        // Returns true to indicate at least one stream is available
 345        // Returns false to indicate that the connection is shutting down and cannot be used anymore
 346        public Task<bool> WaitForAvailableStreamsAsync()
 0347        {
 0348            Debug.Assert(!_pool.HasSyncObjLock);
 349
 0350            lock (SyncObject)
 0351            {
 0352                Debug.Assert(_availableStreamsWaiter is null, "As used currently, shouldn't already have a waiter");
 353
 0354                if (_shutdown)
 0355                {
 0356                    return Task.FromResult(false);
 357                }
 358
 0359                if (_streamsInUse < _maxConcurrentStreams)
 0360                {
 0361                    return Task.FromResult(true);
 362                }
 363
 364                // Need to wait for streams to become available.
 0365                _availableStreamsWaiter = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronou
 0366                return _availableStreamsWaiter.Task;
 367            }
 0368        }
 369
 370        private void SignalAvailableStreamsWaiter(bool result)
 0371        {
 0372            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(result)}={result}, {nameof(_availableStreamsWaiter)}?={_
 373
 0374            Debug.Assert(Monitor.IsEntered(SyncObject));
 375
 0376            if (_availableStreamsWaiter is not null)
 0377            {
 0378                Debug.Assert(_shutdown != result);
 0379                _availableStreamsWaiter.SetResult(result);
 0380                _availableStreamsWaiter = null;
 0381            }
 0382        }
 383
 384        private async Task FlushOutgoingBytesAsync()
 0385        {
 0386            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(_outgoingBuffer.ActiveLength)}={_outgoingBuffer.ActiveLe
 387
 0388            if (_outgoingBuffer.ActiveLength > 0)
 0389            {
 390                try
 0391                {
 0392                    await _stream.WriteAsync(_outgoingBuffer.ActiveMemory).ConfigureAwait(false);
 0393                }
 0394                catch (Exception e)
 0395                {
 0396                    Abort(e);
 0397                }
 398
 0399                _lastPendingWriterShouldFlush = false;
 0400                _outgoingBuffer.Discard(_outgoingBuffer.ActiveLength);
 0401            }
 0402        }
 403
 404        private async ValueTask<FrameHeader> ReadFrameAsync(bool initialFrame = false)
 0405        {
 0406            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(initialFrame)}={initialFrame}");
 407
 408            // Ensure we've read enough data for the frame header.
 0409            if (_incomingBuffer.ActiveLength < FrameHeader.Size)
 0410            {
 411                do
 0412                {
 413                    // Issue a zero-byte read to avoid potentially pinning the buffer while waiting for more data.
 0414                    await _stream.ReadAsync(Memory<byte>.Empty).ConfigureAwait(false);
 415
 0416                    _incomingBuffer.EnsureAvailableSpace(FrameHeader.Size);
 417
 0418                    int bytesRead = await _stream.ReadAsync(_incomingBuffer.AvailableMemory).ConfigureAwait(false);
 0419                    _incomingBuffer.Commit(bytesRead);
 0420                    if (bytesRead == 0)
 0421                    {
 0422                        if (_goAwayErrorCode is not null)
 0423                        {
 0424                            ThrowProtocolError(_goAwayErrorCode.Value, SR.net_http_http2_connection_close);
 425                        }
 0426                        else if (_incomingBuffer.ActiveLength == 0)
 0427                        {
 0428                            ThrowMissingFrame();
 0429                        }
 430                        else
 0431                        {
 0432                            ThrowPrematureEOF(FrameHeader.Size);
 0433                        }
 0434                    }
 0435                }
 0436                while (_incomingBuffer.ActiveLength < FrameHeader.Size);
 0437            }
 438
 439            // Parse the frame header from our read buffer and validate it.
 0440            FrameHeader frameHeader = FrameHeader.ReadFrom(_incomingBuffer.ActiveSpan);
 0441            if (frameHeader.PayloadLength > FrameHeader.MaxPayloadLength)
 0442            {
 0443                if (initialFrame && NetEventSource.Log.IsEnabled())
 0444                {
 0445                    string response = Encoding.ASCII.GetString(_incomingBuffer.ActiveSpan.Slice(0, Math.Min(20, _incomin
 0446                    Trace($"HTTP/2 handshake failed. Server returned {response}");
 0447                }
 448
 0449                _incomingBuffer.Discard(FrameHeader.Size);
 0450                ThrowProtocolError(initialFrame ? Http2ProtocolErrorCode.ProtocolError : Http2ProtocolErrorCode.FrameSiz
 451            }
 0452            _incomingBuffer.Discard(FrameHeader.Size);
 453
 454            // Ensure we've read the frame contents into our buffer.
 0455            if (_incomingBuffer.ActiveLength < frameHeader.PayloadLength)
 0456            {
 0457                _incomingBuffer.EnsureAvailableSpace(frameHeader.PayloadLength - _incomingBuffer.ActiveLength);
 458                do
 0459                {
 460                    // Issue a zero-byte read to avoid potentially pinning the buffer while waiting for more data.
 0461                    await _stream.ReadAsync(Memory<byte>.Empty).ConfigureAwait(false);
 462
 0463                    int bytesRead = await _stream.ReadAsync(_incomingBuffer.AvailableMemory).ConfigureAwait(false);
 0464                    _incomingBuffer.Commit(bytesRead);
 0465                    if (bytesRead == 0) ThrowPrematureEOF(frameHeader.PayloadLength);
 0466                }
 0467                while (_incomingBuffer.ActiveLength < frameHeader.PayloadLength);
 0468            }
 469
 470            // Return the read frame header.
 0471            return frameHeader;
 472
 473            void ThrowPrematureEOF(int requiredBytes) =>
 0474                throw new HttpIOException(HttpRequestError.ResponseEnded, SR.Format(SR.net_http_invalid_response_prematu
 475
 476            void ThrowMissingFrame() =>
 0477                throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_missing_frame);
 0478        }
 479
 480        private async Task ProcessIncomingFramesAsync()
 0481        {
 482            try
 0483            {
 484                FrameHeader frameHeader;
 485                try
 0486                {
 487                    // Read the initial settings frame.
 0488                    frameHeader = await ReadFrameAsync(initialFrame: true).ConfigureAwait(false);
 0489                    if (frameHeader.Type != FrameType.Settings || frameHeader.AckFlag)
 0490                    {
 0491                        if (frameHeader.Type == FrameType.GoAway)
 0492                        {
 0493                            var (_, errorCode) = ReadGoAwayFrame(frameHeader);
 0494                            ThrowProtocolError(errorCode, SR.net_http_http2_connection_close);
 495                        }
 496                        else
 0497                        {
 0498                            ThrowProtocolError();
 499                        }
 500                    }
 501
 0502                    if (NetEventSource.Log.IsEnabled()) Trace($"Frame 0: {frameHeader}.");
 503
 504                    // Process the initial SETTINGS frame. This will send an ACK.
 0505                    ProcessSettingsFrame(frameHeader, initialFrame: true);
 506
 0507                    Debug.Assert(InitialSettingsReceived.Task.IsCompleted);
 0508                }
 0509                catch (HttpProtocolException e)
 0510                {
 0511                    InitialSettingsReceived.TrySetException(e);
 0512                    LogExceptions(InitialSettingsReceived.Task);
 0513                    throw;
 514                }
 0515                catch (Exception e)
 0516                {
 0517                    InitialSettingsReceived.TrySetException(new HttpIOException(HttpRequestError.InvalidResponse, SR.net
 0518                    LogExceptions(InitialSettingsReceived.Task);
 0519                    throw new HttpIOException(HttpRequestError.InvalidResponse, SR.net_http_http2_connection_not_establi
 520                }
 521
 522                // Keep processing frames as they arrive.
 0523                for (long frameNum = 1; ; frameNum++)
 0524                {
 525                    // We could just call ReadFrameAsync here, but we add this code
 526                    // to avoid another state machine allocation in the relatively common case where we
 527                    // currently don't have enough data buffered and issuing a read for the frame header
 528                    // completes asynchronously, but that read ends up also reading enough data to fulfill
 529                    // the entire frame's needs (not just the header).
 0530                    if (_incomingBuffer.ActiveLength < FrameHeader.Size)
 0531                    {
 532                        do
 0533                        {
 534                            // Issue a zero-byte read to avoid potentially pinning the buffer while waiting for more dat
 0535                            ValueTask<int> zeroByteReadTask = _stream.ReadAsync(Memory<byte>.Empty);
 0536                            if (!zeroByteReadTask.IsCompletedSuccessfully && _incomingBuffer.ActiveLength == 0)
 0537                            {
 538                                // No data is available yet. Return the receive buffer back to the pool while we wait.
 0539                                _incomingBuffer.ClearAndReturnBuffer();
 0540                            }
 0541                            await zeroByteReadTask.ConfigureAwait(false);
 542
 543                            // While we only need FrameHeader.Size bytes to complete this read, it's better if we rent m
 544                            // to avoid multiple ReadAsync calls and resizes once we start copying the content.
 0545                            _incomingBuffer.EnsureAvailableSpace(InitialConnectionBufferSize);
 546
 0547                            int bytesRead = await _stream.ReadAsync(_incomingBuffer.AvailableMemory).ConfigureAwait(fals
 0548                            Debug.Assert(bytesRead >= 0);
 0549                            _incomingBuffer.Commit(bytesRead);
 0550                            if (bytesRead == 0)
 0551                            {
 552                                // ReadFrameAsync below will detect that the frame is incomplete and throw the appropria
 0553                                break;
 554                            }
 0555                        }
 0556                        while (_incomingBuffer.ActiveLength < FrameHeader.Size);
 0557                    }
 558
 559                    // Read the frame.
 0560                    frameHeader = await ReadFrameAsync().ConfigureAwait(false);
 0561                    if (NetEventSource.Log.IsEnabled()) Trace($"Frame {frameNum}: {frameHeader}.");
 562
 0563                    RefreshPingTimestamp();
 564
 565                    // Process the frame.
 0566                    switch (frameHeader.Type)
 567                    {
 568                        case FrameType.Headers:
 0569                            await ProcessHeadersFrame(frameHeader).ConfigureAwait(false);
 0570                            break;
 571
 572                        case FrameType.Data:
 0573                            ProcessDataFrame(frameHeader);
 0574                            break;
 575
 576                        case FrameType.Settings:
 0577                            ProcessSettingsFrame(frameHeader);
 0578                            break;
 579
 580                        case FrameType.Priority:
 0581                            ProcessPriorityFrame(frameHeader);
 0582                            break;
 583
 584                        case FrameType.Ping:
 0585                            ProcessPingFrame(frameHeader);
 0586                            break;
 587
 588                        case FrameType.WindowUpdate:
 0589                            ProcessWindowUpdateFrame(frameHeader);
 0590                            break;
 591
 592                        case FrameType.RstStream:
 0593                            ProcessRstStreamFrame(frameHeader);
 0594                            break;
 595
 596                        case FrameType.GoAway:
 0597                            ProcessGoAwayFrame(frameHeader);
 0598                            break;
 599
 600                        case FrameType.AltSvc:
 0601                            ProcessAltSvcFrame(frameHeader);
 0602                            break;
 603
 604                        case FrameType.PushPromise:     // Should not happen, since we disable this in our initial SETTI
 605                        case FrameType.Continuation:    // Should only be received while processing headers in ProcessHe
 606                        default:
 0607                            ThrowProtocolError();
 608                            break;
 609                    }
 0610                }
 611            }
 0612            catch (Exception e)
 0613            {
 0614                if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(ProcessIncomingFramesAsync)}: {e.Message}");
 615
 0616                Abort(e);
 0617            }
 618            finally
 0619            {
 0620                _incomingBuffer.Dispose();
 0621            }
 0622        }
 623
 624        // Note, this will return null for a streamId that's no longer in use.
 625        // Callers must check for this and send a RST_STREAM or ignore as appropriate.
 626        // If the streamId is invalid or the stream is idle, calling this function
 627        // will result in a connection level error.
 628        private Http2Stream? GetStream(int streamId)
 0629        {
 0630            if (streamId <= 0 || streamId >= _nextStream)
 0631            {
 0632                ThrowProtocolError();
 633            }
 634
 0635            lock (SyncObject)
 0636            {
 0637                if (!_httpStreams.TryGetValue(streamId, out Http2Stream? http2Stream))
 0638                {
 0639                    return null;
 640                }
 641
 0642                return http2Stream;
 643            }
 0644        }
 645
 646        private async ValueTask ProcessHeadersFrame(FrameHeader frameHeader)
 0647        {
 0648            if (NetEventSource.Log.IsEnabled()) Trace($"{frameHeader}");
 0649            Debug.Assert(frameHeader.Type == FrameType.Headers);
 650
 0651            bool endStream = frameHeader.EndStreamFlag;
 652
 0653            int streamId = frameHeader.StreamId;
 0654            Http2Stream? http2Stream = GetStream(streamId);
 655
 656            IHttpStreamHeadersHandler headersHandler;
 0657            if (http2Stream != null)
 0658            {
 0659                http2Stream.OnHeadersStart();
 0660                _rttEstimator.OnDataOrHeadersReceived(this, sendWindowUpdateBeforePing: true);
 0661                headersHandler = http2Stream;
 0662            }
 663            else
 0664            {
 665                // http2Stream will be null if this is a closed stream. We will still process the headers,
 666                // to ensure the header decoding state is up-to-date, but we will discard the decoded headers.
 0667                headersHandler = NopHeadersHandler.Instance;
 0668            }
 669
 0670            _hpackDecoder.Decode(
 0671                GetFrameData(_incomingBuffer.ActiveSpan.Slice(0, frameHeader.PayloadLength), frameHeader.PaddedFlag, fra
 0672                frameHeader.EndHeadersFlag,
 0673                headersHandler);
 0674            _incomingBuffer.Discard(frameHeader.PayloadLength);
 675
 0676            while (!frameHeader.EndHeadersFlag)
 0677            {
 0678                frameHeader = await ReadFrameAsync().ConfigureAwait(false);
 679
 0680                if (frameHeader.Type != FrameType.Continuation ||
 0681                    frameHeader.StreamId != streamId)
 0682                {
 0683                    ThrowProtocolError();
 684                }
 685
 0686                _hpackDecoder.Decode(
 0687                    _incomingBuffer.ActiveSpan.Slice(0, frameHeader.PayloadLength),
 0688                    frameHeader.EndHeadersFlag,
 0689                    headersHandler);
 0690                _incomingBuffer.Discard(frameHeader.PayloadLength);
 0691            }
 692
 0693            _hpackDecoder.CompleteDecode();
 694
 0695            http2Stream?.OnHeadersComplete(endStream);
 0696        }
 697
 698        /// <summary>Nop implementation of <see cref="IHttpStreamHeadersHandler"/> used by <see cref="ProcessHeadersFram
 699        private sealed class NopHeadersHandler : IHttpStreamHeadersHandler
 700        {
 0701            public static readonly NopHeadersHandler Instance = new NopHeadersHandler();
 0702            void IHttpStreamHeadersHandler.OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value) { }
 0703            void IHttpStreamHeadersHandler.OnHeadersComplete(bool endStream) { }
 0704            void IHttpStreamHeadersHandler.OnStaticIndexedHeader(int index) { }
 0705            void IHttpStreamHeadersHandler.OnStaticIndexedHeader(int index, ReadOnlySpan<byte> value) { }
 0706            void IHttpStreamHeadersHandler.OnDynamicIndexedHeader(int? index, ReadOnlySpan<byte> name, ReadOnlySpan<byte
 707        }
 708
 709        private static ReadOnlySpan<byte> GetFrameData(ReadOnlySpan<byte> frameData, bool hasPad, bool hasPriority)
 0710        {
 0711            if (hasPad)
 0712            {
 0713                if (frameData.Length == 0)
 0714                {
 0715                    ThrowProtocolError();
 716                }
 717
 0718                int padLength = frameData[0];
 0719                frameData = frameData.Slice(1);
 720
 0721                if (frameData.Length < padLength)
 0722                {
 0723                    ThrowProtocolError();
 724                }
 725
 0726                frameData = frameData.Slice(0, frameData.Length - padLength);
 0727            }
 728
 0729            if (hasPriority)
 0730            {
 0731                if (frameData.Length < FrameHeader.PriorityInfoLength)
 0732                {
 0733                    ThrowProtocolError();
 734                }
 735
 736                // We ignore priority info.
 0737                frameData = frameData.Slice(FrameHeader.PriorityInfoLength);
 0738            }
 739
 0740            return frameData;
 0741        }
 742
 743        /// <summary>
 744        /// Parses an ALTSVC frame, defined by RFC 7838 Section 4.
 745        /// </summary>
 746        /// <remarks>
 747        /// The RFC states that any parse errors should result in ignoring the frame.
 748        /// </remarks>
 749        private void ProcessAltSvcFrame(FrameHeader frameHeader)
 0750        {
 0751            if (NetEventSource.Log.IsEnabled()) Trace($"{frameHeader}");
 0752            Debug.Assert(frameHeader.Type == FrameType.AltSvc);
 0753            Debug.Assert(!Monitor.IsEntered(SyncObject));
 754
 0755            ReadOnlySpan<byte> span = _incomingBuffer.ActiveSpan.Slice(0, frameHeader.PayloadLength);
 756
 0757            if (BinaryPrimitives.TryReadUInt16BigEndian(span, out ushort originLength))
 0758            {
 0759                span = span.Slice(2);
 760
 761                // Check that this ALTSVC frame is valid for our pool's origin. ALTSVC frames can come in one of two way
 762                //  - On stream 0, the origin will be specified. HTTP/2 can service multiple origins per connection, and
 763                //  - Otherwise, the origin is implicitly defined by the request stream and must be of length 0.
 764
 0765                if ((frameHeader.StreamId != 0 && originLength == 0) || (frameHeader.StreamId == 0 && span.Length >= ori
 0766                {
 0767                    span = span.Slice(originLength);
 768
 769                    // The span now contains a string with the same format as Alt-Svc headers.
 770
 0771                    string altSvcHeaderValue = Encoding.ASCII.GetString(span);
 0772                    _pool.HandleAltSvc(new[] { altSvcHeaderValue }, null);
 0773                }
 0774            }
 775
 0776            _incomingBuffer.Discard(frameHeader.PayloadLength);
 0777        }
 778
 779        private void ProcessDataFrame(FrameHeader frameHeader)
 0780        {
 0781            Debug.Assert(frameHeader.Type == FrameType.Data);
 782
 0783            Http2Stream? http2Stream = GetStream(frameHeader.StreamId);
 784
 785            // Note, http2Stream will be null if this is a closed stream.
 786            // Just ignore the frame in this case.
 787
 0788            ReadOnlySpan<byte> frameData = GetFrameData(_incomingBuffer.ActiveSpan.Slice(0, frameHeader.PayloadLength), 
 0789            bool endStream = frameHeader.EndStreamFlag;
 790
 0791            if (frameData.Length > 0 || endStream)
 0792            {
 0793                http2Stream?.OnResponseData(frameData, endStream);
 0794            }
 795
 0796            if (frameData.Length > 0)
 0797            {
 0798                bool windowUpdateSent = ExtendWindow(frameData.Length);
 0799                if (http2Stream is not null && !endStream)
 0800                {
 0801                    _rttEstimator.OnDataOrHeadersReceived(this, sendWindowUpdateBeforePing: !windowUpdateSent);
 0802                }
 0803            }
 804
 0805            _incomingBuffer.Discard(frameHeader.PayloadLength);
 0806        }
 807
 808        private void ProcessSettingsFrame(FrameHeader frameHeader, bool initialFrame = false)
 0809        {
 0810            Debug.Assert(frameHeader.Type == FrameType.Settings);
 811
 0812            if (frameHeader.StreamId != 0)
 0813            {
 0814                ThrowProtocolError();
 815            }
 816
 0817            if (frameHeader.AckFlag)
 0818            {
 0819                if (frameHeader.PayloadLength != 0)
 0820                {
 0821                    ThrowProtocolError(Http2ProtocolErrorCode.FrameSizeError);
 822                }
 823
 0824                if (_receivedSettingsAck)
 0825                {
 0826                    ThrowProtocolError();
 827                }
 828
 829                // We only send SETTINGS once initially, so we don't need to do anything in response to the ACK.
 830                // Just remember that we received one and we won't be expecting any more.
 0831                _receivedSettingsAck = true;
 0832                _rttEstimator.OnInitialSettingsAckReceived(this);
 0833            }
 834            else
 0835            {
 0836                if ((frameHeader.PayloadLength % 6) != 0)
 0837                {
 0838                    ThrowProtocolError(Http2ProtocolErrorCode.FrameSizeError);
 839                }
 840
 841                // Parse settings and process the ones we care about.
 0842                ReadOnlySpan<byte> settings = _incomingBuffer.ActiveSpan.Slice(0, frameHeader.PayloadLength);
 0843                bool maxConcurrentStreamsReceived = false;
 0844                while (settings.Length > 0)
 0845                {
 0846                    Debug.Assert((settings.Length % 6) == 0);
 847
 0848                    ushort settingId = BinaryPrimitives.ReadUInt16BigEndian(settings);
 0849                    settings = settings.Slice(2);
 0850                    uint settingValue = BinaryPrimitives.ReadUInt32BigEndian(settings);
 0851                    settings = settings.Slice(4);
 852
 0853                    if (NetEventSource.Log.IsEnabled()) Trace($"Applying setting {(SettingId)settingId}={settingValue}")
 854
 0855                    switch ((SettingId)settingId)
 856                    {
 857                        case SettingId.MaxConcurrentStreams:
 0858                            ChangeMaxConcurrentStreams(settingValue);
 0859                            maxConcurrentStreamsReceived = true;
 0860                            break;
 861
 862                        case SettingId.InitialWindowSize:
 0863                            if (settingValue > 0x7FFFFFFF)
 0864                            {
 0865                                ThrowProtocolError(Http2ProtocolErrorCode.FlowControlError);
 866                            }
 867
 0868                            ChangeInitialWindowSize((int)settingValue);
 0869                            break;
 870
 871                        case SettingId.MaxFrameSize:
 0872                            if (settingValue < 16384 || settingValue > 16777215)
 0873                            {
 0874                                ThrowProtocolError();
 875                            }
 876
 877                            // We don't actually store this value; we always send frames of the minimum size (16K).
 0878                            break;
 879
 880                        case SettingId.EnableConnect:
 0881                            if (settingValue == 1)
 0882                            {
 0883                                IsConnectEnabled = true;
 0884                            }
 0885                            else if (settingValue == 0 && IsConnectEnabled)
 0886                            {
 887                                // Accroding to RFC: a sender MUST NOT send a SETTINGS_ENABLE_CONNECT_PROTOCOL parameter
 888                                // with the value of 0 after previously sending a value of 1.
 889                                // https://datatracker.ietf.org/doc/html/rfc8441#section-3
 0890                                ThrowProtocolError();
 891                            }
 0892                            break;
 893
 894                        case SettingId.MaxHeaderListSize:
 0895                            _maxHeaderListSize = settingValue;
 0896                            _pool._lastSeenHttp2MaxHeaderListSize = _maxHeaderListSize;
 0897                            break;
 898
 899                        default:
 900                            // All others are ignored because we don't care about them.
 901                            // Note, per RFC, unknown settings IDs should be ignored.
 0902                            break;
 903                    }
 0904                }
 905
 0906                if (initialFrame)
 0907                {
 0908                    if (!maxConcurrentStreamsReceived)
 0909                    {
 910                        // Set to 'infinite' because MaxConcurrentStreams was not set on the initial SETTINGS frame.
 0911                        ChangeMaxConcurrentStreams(int.MaxValue);
 0912                    }
 913
 0914                    if (_initialSettingsReceived is null)
 0915                    {
 0916                        Interlocked.CompareExchange(ref _initialSettingsReceived, s_settingsReceivedSingleton, null);
 0917                    }
 918                    // Set result in case if CompareExchange lost the race
 0919                    InitialSettingsReceived.TrySetResult(true);
 0920                }
 921
 0922                _incomingBuffer.Discard(frameHeader.PayloadLength);
 923
 924                // Send acknowledgement
 925                // Don't wait for completion, which could happen asynchronously.
 0926                LogExceptions(SendSettingsAckAsync());
 0927            }
 0928        }
 929
 930        private void ChangeMaxConcurrentStreams(uint newValue)
 0931        {
 0932            lock (SyncObject)
 0933            {
 0934                if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(newValue)}={newValue}, {nameof(_streamsInUse)}={_str
 935
 0936                Debug.Assert(_availableStreamsWaiter is null || _streamsInUse >= _maxConcurrentStreams);
 937
 0938                _maxConcurrentStreams = newValue;
 0939                if (_streamsInUse < _maxConcurrentStreams)
 0940                {
 0941                    SignalAvailableStreamsWaiter(true);
 0942                }
 0943            }
 0944        }
 945
 946        private void ChangeInitialWindowSize(int newSize)
 0947        {
 0948            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(newSize)}={newSize}");
 0949            Debug.Assert(newSize >= 0);
 950
 0951            lock (SyncObject)
 0952            {
 0953                int delta = newSize - _initialServerStreamWindowSize;
 0954                _initialServerStreamWindowSize = newSize;
 955
 956                // Adjust existing streams
 0957                foreach (KeyValuePair<int, Http2Stream> kvp in _httpStreams)
 0958                {
 0959                    kvp.Value.OnWindowUpdate(delta);
 0960                }
 0961            }
 0962        }
 963
 964        private void ProcessPriorityFrame(FrameHeader frameHeader)
 0965        {
 0966            Debug.Assert(frameHeader.Type == FrameType.Priority);
 967
 0968            if (frameHeader.StreamId == 0 || frameHeader.PayloadLength != FrameHeader.PriorityInfoLength)
 0969            {
 0970                ThrowProtocolError();
 971            }
 972
 973            // Ignore priority info.
 974
 0975            _incomingBuffer.Discard(frameHeader.PayloadLength);
 0976        }
 977
 978        private void ProcessPingFrame(FrameHeader frameHeader)
 0979        {
 0980            Debug.Assert(frameHeader.Type == FrameType.Ping);
 981
 0982            if (frameHeader.StreamId != 0)
 0983            {
 0984                ThrowProtocolError();
 985            }
 986
 0987            if (frameHeader.PayloadLength != FrameHeader.PingLength)
 0988            {
 0989                ThrowProtocolError(Http2ProtocolErrorCode.FrameSizeError);
 990            }
 991
 992            // We don't wait for SendPingAckAsync to complete before discarding
 993            // the incoming buffer, so we need to take a copy of the data. Read
 994            // it as a big-endian integer here to avoid allocating an array.
 0995            Debug.Assert(sizeof(long) == FrameHeader.PingLength);
 0996            ReadOnlySpan<byte> pingContent = _incomingBuffer.ActiveSpan.Slice(0, FrameHeader.PingLength);
 0997            long pingContentLong = BinaryPrimitives.ReadInt64BigEndian(pingContent);
 998
 0999            if (NetEventSource.Log.IsEnabled()) Trace($"Received PING frame, content:{pingContentLong} ack: {frameHeader
 1000
 01001            if (frameHeader.AckFlag)
 01002            {
 01003                ProcessPingAck(pingContentLong);
 01004            }
 1005            else
 01006            {
 01007                LogExceptions(SendPingAsync(pingContentLong, isAck: true));
 01008            }
 01009            _incomingBuffer.Discard(frameHeader.PayloadLength);
 01010        }
 1011
 1012        private void ProcessWindowUpdateFrame(FrameHeader frameHeader)
 01013        {
 01014            Debug.Assert(frameHeader.Type == FrameType.WindowUpdate);
 1015
 01016            if (frameHeader.PayloadLength != FrameHeader.WindowUpdateLength)
 01017            {
 01018                ThrowProtocolError(Http2ProtocolErrorCode.FrameSizeError);
 1019            }
 1020
 01021            int amount = BinaryPrimitives.ReadInt32BigEndian(_incomingBuffer.ActiveSpan) & 0x7FFFFFFF;
 01022            if (NetEventSource.Log.IsEnabled()) Trace($"{frameHeader}. {nameof(amount)}={amount}");
 1023
 01024            Debug.Assert(amount >= 0);
 01025            if (amount == 0)
 01026            {
 01027                ThrowProtocolError();
 1028            }
 1029
 01030            _incomingBuffer.Discard(frameHeader.PayloadLength);
 1031
 01032            if (frameHeader.StreamId == 0)
 01033            {
 01034                _connectionWindow.AdjustCredit(amount);
 01035            }
 1036            else
 01037            {
 01038                Http2Stream? http2Stream = GetStream(frameHeader.StreamId);
 01039                if (http2Stream == null)
 01040                {
 1041                    // Ignore invalid stream ID, as per RFC
 01042                    return;
 1043                }
 1044
 01045                http2Stream.OnWindowUpdate(amount);
 01046            }
 01047        }
 1048
 1049        private void ProcessRstStreamFrame(FrameHeader frameHeader)
 01050        {
 01051            Debug.Assert(frameHeader.Type == FrameType.RstStream);
 1052
 01053            if (frameHeader.PayloadLength != FrameHeader.RstStreamLength)
 01054            {
 01055                ThrowProtocolError(Http2ProtocolErrorCode.FrameSizeError);
 1056            }
 1057
 01058            if (frameHeader.StreamId == 0)
 01059            {
 01060                ThrowProtocolError();
 1061            }
 1062
 01063            Http2Stream? http2Stream = GetStream(frameHeader.StreamId);
 01064            if (http2Stream == null)
 01065            {
 1066                // Ignore invalid stream ID, as per RFC
 01067                _incomingBuffer.Discard(frameHeader.PayloadLength);
 01068                return;
 1069            }
 1070
 01071            var protocolError = (Http2ProtocolErrorCode)BinaryPrimitives.ReadInt32BigEndian(_incomingBuffer.ActiveSpan);
 01072            if (NetEventSource.Log.IsEnabled()) Trace(frameHeader.StreamId, $"{nameof(protocolError)}={protocolError}");
 1073
 01074            _incomingBuffer.Discard(frameHeader.PayloadLength);
 1075
 01076            bool canRetry = protocolError == Http2ProtocolErrorCode.RefusedStream;
 01077            http2Stream.OnReset(HttpProtocolException.CreateHttp2StreamException(protocolError), resetStreamErrorCode: p
 01078        }
 1079
 1080        private void ProcessGoAwayFrame(FrameHeader frameHeader)
 01081        {
 01082            var (lastStreamId, errorCode) = ReadGoAwayFrame(frameHeader);
 1083
 01084            Debug.Assert(lastStreamId >= 0);
 01085            Exception resetException = HttpProtocolException.CreateHttp2ConnectionException(errorCode, SR.net_http_http2
 01086            _goAwayErrorCode = errorCode;
 1087
 1088            // There is no point sending more PING frames for RTT estimation:
 01089            _rttEstimator.OnGoAwayReceived();
 1090
 01091            List<Http2Stream> streamsToAbort = new List<Http2Stream>();
 01092            lock (SyncObject)
 01093            {
 01094                Shutdown();
 1095
 01096                foreach (KeyValuePair<int, Http2Stream> kvp in _httpStreams)
 01097                {
 01098                    int streamId = kvp.Key;
 01099                    Debug.Assert(streamId == kvp.Value.StreamId);
 1100
 01101                    if (streamId > lastStreamId)
 01102                    {
 01103                        streamsToAbort.Add(kvp.Value);
 01104                    }
 01105                }
 01106            }
 1107
 1108            // Avoid calling OnReset under the lock, as it may cause the Http2Stream to call back in to RemoveStream
 01109            foreach (Http2Stream s in streamsToAbort)
 01110            {
 01111                s.OnReset(resetException, canRetry: true);
 01112            }
 01113        }
 1114
 1115        private (int lastStreamId, Http2ProtocolErrorCode errorCode) ReadGoAwayFrame(FrameHeader frameHeader)
 01116        {
 01117            Debug.Assert(frameHeader.Type == FrameType.GoAway);
 1118
 01119            if (frameHeader.PayloadLength < FrameHeader.GoAwayMinLength)
 01120            {
 01121                ThrowProtocolError(Http2ProtocolErrorCode.FrameSizeError);
 1122            }
 1123
 01124            if (frameHeader.StreamId != 0)
 01125            {
 01126                ThrowProtocolError();
 1127            }
 1128
 01129            int lastStreamId = (int)(BinaryPrimitives.ReadUInt32BigEndian(_incomingBuffer.ActiveSpan) & 0x7FFFFFFF);
 01130            Http2ProtocolErrorCode errorCode = (Http2ProtocolErrorCode)BinaryPrimitives.ReadInt32BigEndian(_incomingBuff
 01131            if (NetEventSource.Log.IsEnabled()) Trace(frameHeader.StreamId, $"{nameof(lastStreamId)}={lastStreamId}, {na
 1132
 01133            _incomingBuffer.Discard(frameHeader.PayloadLength);
 1134
 01135            return (lastStreamId, errorCode);
 01136        }
 1137
 1138        internal Task FlushAsync(CancellationToken cancellationToken) =>
 01139            PerformWriteAsync(0, 0, static (_, __) => true, cancellationToken);
 1140
 1141        private abstract class WriteQueueEntry : TaskCompletionSource
 1142        {
 1143            private readonly CancellationTokenRegistration _cancellationRegistration;
 1144
 1145            public WriteQueueEntry(int writeBytes, CancellationToken cancellationToken)
 01146                : base(TaskCreationOptions.RunContinuationsAsynchronously)
 01147            {
 01148                WriteBytes = writeBytes;
 1149
 01150                _cancellationRegistration = cancellationToken.UnsafeRegister(static (s, cancellationToken) =>
 01151                {
 01152                    bool canceled = ((WriteQueueEntry)s!).TrySetCanceled(cancellationToken);
 01153                    Debug.Assert(canceled, "Callback should have been unregistered if the operation was completing succe
 01154                }, this);
 01155            }
 1156
 01157            public int WriteBytes { get; }
 1158
 1159            public bool TryDisableCancellation()
 01160            {
 01161                _cancellationRegistration.Dispose();
 01162                return !Task.IsCanceled;
 01163            }
 1164
 1165            public abstract bool InvokeWriteAction(Memory<byte> writeBuffer);
 1166        }
 1167
 1168        private sealed class WriteQueueEntry<T> : WriteQueueEntry
 1169        {
 1170            private readonly T _state;
 1171            private readonly Func<T, Memory<byte>, bool> _writeAction;
 1172
 1173            public WriteQueueEntry(int writeBytes, T state, Func<T, Memory<byte>, bool> writeAction, CancellationToken c
 01174                : base(writeBytes, cancellationToken)
 01175            {
 01176                _state = state;
 01177                _writeAction = writeAction;
 01178            }
 1179
 1180            public override bool InvokeWriteAction(Memory<byte> writeBuffer)
 01181            {
 01182                return _writeAction(_state, writeBuffer);
 01183            }
 1184        }
 1185
 1186        private Task PerformWriteAsync<T>(int writeBytes, T state, Func<T, Memory<byte>, bool> writeAction, Cancellation
 01187        {
 01188            WriteQueueEntry writeEntry = new WriteQueueEntry<T>(writeBytes, state, writeAction, cancellationToken);
 1189
 01190            if (!_writeChannel.Writer.TryWrite(writeEntry))
 01191            {
 01192                if (_abortException is not null)
 01193                {
 01194                    return Task.FromException(GetRequestAbortedException(_abortException));
 1195                }
 1196
 1197                // We must be trying to send something asynchronously (like RST_STREAM or a PING or a SETTINGS ACK) and 
 1198                // As such, it should not matter that we were not able to actually send the frame.
 1199                // But just in case, throw ObjectDisposedException. Asynchronous callers will ignore the failure.
 01200                Debug.Assert(_shutdown && _streamsInUse == 0);
 01201                return Task.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(new ObjectDisposedException(nameof(
 1202            }
 1203
 01204            return writeEntry.Task;
 01205        }
 1206
 1207        private async Task ProcessOutgoingFramesAsync()
 01208        {
 1209            try
 01210            {
 01211                while (await _writeChannel.Reader.WaitToReadAsync().ConfigureAwait(false))
 01212                {
 01213                    while (_writeChannel.Reader.TryRead(out WriteQueueEntry? writeEntry))
 01214                    {
 01215                        if (_abortException is not null)
 01216                        {
 01217                            if (writeEntry.TryDisableCancellation())
 01218                            {
 01219                                writeEntry.SetException(_abortException);
 01220                            }
 01221                        }
 1222                        else
 01223                        {
 01224                            int writeBytes = writeEntry.WriteBytes;
 1225
 1226                            // If the buffer has already grown to 32k, does not have room for the next request,
 1227                            // and is non-empty, flush the current contents to the wire.
 01228                            int totalBufferLength = _outgoingBuffer.Capacity;
 01229                            if (totalBufferLength >= UnflushedOutgoingBufferSize)
 01230                            {
 01231                                int activeBufferLength = _outgoingBuffer.ActiveLength;
 01232                                if (writeBytes >= totalBufferLength - activeBufferLength)
 01233                                {
 01234                                    await FlushOutgoingBytesAsync().ConfigureAwait(false);
 01235                                }
 01236                            }
 1237
 1238                            // We are ready to process the write, so disable write cancellation now.
 01239                            if (writeEntry.TryDisableCancellation())
 01240                            {
 01241                                _outgoingBuffer.EnsureAvailableSpace(writeBytes);
 1242
 1243                                try
 01244                                {
 01245                                    if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(writeBytes)}={writeBytes}");
 1246
 1247                                    // Invoke the callback with the supplied state and the target write buffer.
 01248                                    bool flush = writeEntry.InvokeWriteAction(_outgoingBuffer.AvailableMemorySliced(writ
 1249
 01250                                    writeEntry.SetResult();
 1251
 01252                                    _outgoingBuffer.Commit(writeBytes);
 01253                                    _lastPendingWriterShouldFlush |= flush;
 01254                                }
 01255                                catch (Exception e)
 01256                                {
 01257                                    writeEntry.SetException(e);
 01258                                }
 01259                            }
 01260                        }
 01261                    }
 1262
 1263                    // Nothing left in the queue to process.
 1264                    // Flush the write buffer if we need to.
 01265                    if (_lastPendingWriterShouldFlush)
 01266                    {
 01267                        await FlushOutgoingBytesAsync().ConfigureAwait(false);
 01268                    }
 1269
 01270                    if (_outgoingBuffer.ActiveLength == 0)
 01271                    {
 01272                        _outgoingBuffer.ClearAndReturnBuffer();
 01273                    }
 01274                }
 01275            }
 01276            catch (Exception e)
 01277            {
 01278                if (NetEventSource.Log.IsEnabled()) Trace($"Unexpected exception in {nameof(ProcessOutgoingFramesAsync)}
 1279
 01280                Debug.Fail($"Unexpected exception in {nameof(ProcessOutgoingFramesAsync)}: {e}");
 1281            }
 1282            finally
 01283            {
 01284                _outgoingBuffer.Dispose();
 01285            }
 01286        }
 1287
 1288        private Task SendSettingsAckAsync() =>
 01289            PerformWriteAsync(FrameHeader.Size, this, static (thisRef, writeBuffer) =>
 01290            {
 01291                if (NetEventSource.Log.IsEnabled()) thisRef.Trace("Started writing.");
 01292
 01293                FrameHeader.WriteTo(writeBuffer.Span, 0, FrameType.Settings, FrameFlags.Ack, streamId: 0);
 01294
 01295                return true;
 01296            });
 1297
 1298        /// <param name="pingContent">The 8-byte ping content to send, read as a big-endian integer.</param>
 1299        /// <param name="isAck">Determine whether the frame is ping or ping ack.</param>
 1300        private Task SendPingAsync(long pingContent, bool isAck = false) =>
 01301            PerformWriteAsync(FrameHeader.Size + FrameHeader.PingLength, (thisRef: this, pingContent, isAck), static (st
 01302            {
 01303                if (NetEventSource.Log.IsEnabled()) state.thisRef.Trace($"Started writing. {nameof(pingContent)}={state.
 01304
 01305                Debug.Assert(sizeof(long) == FrameHeader.PingLength);
 01306
 01307                Span<byte> span = writeBuffer.Span;
 01308                FrameHeader.WriteTo(span, FrameHeader.PingLength, FrameType.Ping, state.isAck ? FrameFlags.Ack : FrameFl
 01309                BinaryPrimitives.WriteInt64BigEndian(span.Slice(FrameHeader.Size), state.pingContent);
 01310
 01311                return true;
 01312            });
 1313
 1314        private Task SendRstStreamAsync(int streamId, Http2ProtocolErrorCode errorCode) =>
 01315            PerformWriteAsync(FrameHeader.Size + FrameHeader.RstStreamLength, (thisRef: this, streamId, errorCode), stat
 01316            {
 01317                if (NetEventSource.Log.IsEnabled()) s.thisRef.Trace(s.streamId, $"Started writing. {nameof(s.errorCode)}
 01318
 01319                Span<byte> span = writeBuffer.Span;
 01320                FrameHeader.WriteTo(span, FrameHeader.RstStreamLength, FrameType.RstStream, FrameFlags.None, s.streamId)
 01321                BinaryPrimitives.WriteInt32BigEndian(span.Slice(FrameHeader.Size), (int)s.errorCode);
 01322
 01323                return true;
 01324            });
 1325
 1326
 1327        internal void HeartBeat()
 01328        {
 01329            Debug.Assert(!_pool.HasSyncObjLock);
 1330
 01331            if (_shutdown)
 01332                return;
 1333
 1334            try
 01335            {
 01336                VerifyKeepAlive();
 01337            }
 01338            catch (Exception e)
 01339            {
 01340                if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(HeartBeat)}: {e.Message}");
 1341
 01342                Abort(e);
 01343            }
 01344        }
 1345
 1346        private static (ReadOnlyMemory<byte> first, ReadOnlyMemory<byte> rest) SplitBuffer(ReadOnlyMemory<byte> buffer, 
 01347            buffer.Length > maxSize ?
 01348                (buffer.Slice(0, maxSize), buffer.Slice(maxSize)) :
 01349                (buffer, Memory<byte>.Empty);
 1350
 1351        private void WriteIndexedHeader(int index, ref ArrayBuffer headerBuffer)
 01352        {
 01353            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(index)}={index}");
 1354
 1355            int bytesWritten;
 01356            while (!HPackEncoder.EncodeIndexedHeaderField(index, headerBuffer.AvailableSpan, out bytesWritten))
 01357            {
 01358                headerBuffer.Grow();
 01359            }
 1360
 01361            headerBuffer.Commit(bytesWritten);
 01362        }
 1363
 1364        private void WriteIndexedHeader(int index, string value, ref ArrayBuffer headerBuffer)
 01365        {
 01366            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(index)}={index}, {nameof(value)}={value}");
 1367
 1368            int bytesWritten;
 01369            while (!HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexing(index, value, valueEncoding: null, headerBuffer
 01370            {
 01371                headerBuffer.Grow();
 01372            }
 1373
 01374            headerBuffer.Commit(bytesWritten);
 01375        }
 1376
 1377        private void WriteLiteralHeader(string name, ReadOnlySpan<string> values, Encoding? valueEncoding, ref ArrayBuff
 01378        {
 01379            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(name)}={name}, {nameof(values)}={string.Join(", ", value
 1380
 1381            int bytesWritten;
 01382            while (!HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingNewName(name, values, HttpHeaderParser.DefaultSe
 01383            {
 01384                headerBuffer.Grow();
 01385            }
 1386
 01387            headerBuffer.Commit(bytesWritten);
 01388        }
 1389
 1390        private void WriteLiteralHeaderValues(ReadOnlySpan<string> values, byte[]? separator, Encoding? valueEncoding, r
 01391        {
 01392            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(values)}={string.Join(Encoding.ASCII.GetString(separator
 1393
 1394            int bytesWritten;
 01395            while (!HPackEncoder.EncodeStringLiterals(values, separator, valueEncoding, headerBuffer.AvailableSpan, out 
 01396            {
 01397                headerBuffer.Grow();
 01398            }
 1399
 01400            headerBuffer.Commit(bytesWritten);
 01401        }
 1402
 1403        private void WriteLiteralHeaderValue(string value, Encoding? valueEncoding, ref ArrayBuffer headerBuffer)
 01404        {
 01405            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(value)}={value}");
 1406
 1407            int bytesWritten;
 01408            while (!HPackEncoder.EncodeStringLiteral(value, valueEncoding, headerBuffer.AvailableSpan, out bytesWritten)
 01409            {
 01410                headerBuffer.Grow();
 01411            }
 1412
 01413            headerBuffer.Commit(bytesWritten);
 01414        }
 1415
 1416        private void WriteBytes(ReadOnlySpan<byte> bytes, ref ArrayBuffer headerBuffer)
 01417        {
 01418            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(bytes.Length)}={bytes.Length}");
 1419
 01420            headerBuffer.EnsureAvailableSpace(bytes.Length);
 01421            bytes.CopyTo(headerBuffer.AvailableSpan);
 01422            headerBuffer.Commit(bytes.Length);
 01423        }
 1424
 1425        private int WriteHeaderCollection(HttpRequestMessage request, HttpHeaders headers, ref ArrayBuffer headerBuffer)
 01426        {
 01427            if (NetEventSource.Log.IsEnabled()) Trace("");
 1428
 01429            HeaderEncodingSelector<HttpRequestMessage>? encodingSelector = _pool.Settings._requestHeaderEncodingSelector
 1430
 01431            ref string[]? tmpHeaderValuesArray = ref t_headerValues;
 1432
 01433            ReadOnlySpan<HeaderEntry> entries = headers.GetEntries();
 01434            int headerListSize = entries.Length * HeaderField.RfcOverhead;
 1435
 01436            foreach (HeaderEntry header in entries)
 01437            {
 01438                int headerValuesCount = HttpHeaders.GetStoreValuesIntoStringArray(header.Key, header.Value, ref tmpHeade
 01439                Debug.Assert(headerValuesCount > 0, "No values for header??");
 01440                ReadOnlySpan<string> headerValues = tmpHeaderValuesArray.AsSpan(0, headerValuesCount);
 1441
 01442                Encoding? valueEncoding = encodingSelector?.Invoke(header.Key.Name, request);
 1443
 01444                KnownHeader? knownHeader = header.Key.KnownHeader;
 01445                if (knownHeader != null)
 01446                {
 1447                    // The Host header is not sent for HTTP2 because we send the ":authority" pseudo-header instead
 1448                    // (see pseudo-header handling below in WriteHeaders).
 1449                    // The Connection, Upgrade and ProxyConnection headers are also not supported in HTTP2.
 01450                    if (knownHeader != KnownHeaders.Host && knownHeader != KnownHeaders.Connection && knownHeader != Kno
 01451                    {
 1452                        // The length of the encoded name may be shorter than the actual name.
 1453                        // Ensure that headerListSize is always >= of the actual size.
 01454                        headerListSize += knownHeader.Name.Length;
 1455
 01456                        if (knownHeader == KnownHeaders.TE)
 01457                        {
 1458                            // HTTP/2 allows only 'trailers' TE header. rfc7540 8.1.2.2
 01459                            foreach (string value in headerValues)
 01460                            {
 01461                                if (string.Equals(value, "trailers", StringComparison.OrdinalIgnoreCase))
 01462                                {
 01463                                    WriteBytes(knownHeader.Http2EncodedName, ref headerBuffer);
 01464                                    WriteLiteralHeaderValue(value, valueEncoding, ref headerBuffer);
 01465                                    break;
 1466                                }
 01467                            }
 01468                            continue;
 1469                        }
 1470
 1471                        // Extended connect requests will use the response content stream for bidirectional communicatio
 1472                        // We will ignore any content set for such requests in Http2Stream.SendRequestBodyAsync, as it h
 1473                        // Drop the Content-Length header as well in the unlikely case it was set.
 01474                        if (knownHeader == KnownHeaders.ContentLength && request.IsExtendedConnectRequest)
 01475                        {
 01476                            continue;
 1477                        }
 1478
 1479                        // For all other known headers, send them via their pre-encoded name and the associated value.
 01480                        WriteBytes(knownHeader.Http2EncodedName, ref headerBuffer);
 1481
 01482                        byte[]? separator = headerValues.Length > 1 ? header.Key.SeparatorBytes : null;
 1483
 01484                        WriteLiteralHeaderValues(headerValues, separator, valueEncoding, ref headerBuffer);
 01485                    }
 01486                }
 1487                else
 01488                {
 1489                    // The header is not known: fall back to just encoding the header name and value(s).
 01490                    WriteLiteralHeader(header.Key.Name, headerValues, valueEncoding, ref headerBuffer);
 01491                }
 01492            }
 1493
 01494            return headerListSize;
 01495        }
 1496
 1497        private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuffer)
 01498        {
 01499            if (NetEventSource.Log.IsEnabled()) Trace("");
 1500
 01501            WriteBytes(request.Method.Http2EncodedBytes, ref headerBuffer);
 1502
 01503            WriteIndexedHeader(_pool.IsSecure ? H2StaticTable.SchemeHttps : H2StaticTable.SchemeHttp, ref headerBuffer);
 1504
 01505            if (request.HasHeaders && request.Headers.Host is string host)
 01506            {
 01507                WriteIndexedHeader(H2StaticTable.Authority, host, ref headerBuffer);
 01508            }
 1509            else
 01510            {
 01511                WriteBytes(_pool._http2EncodedAuthorityHostHeader, ref headerBuffer);
 01512            }
 1513
 01514            Debug.Assert(request.RequestUri != null);
 01515            string pathAndQuery = request.RequestUri.PathAndQuery;
 01516            if (pathAndQuery == "/")
 01517            {
 01518                WriteIndexedHeader(H2StaticTable.PathSlash, ref headerBuffer);
 01519            }
 1520            else
 01521            {
 01522                WriteIndexedHeader(H2StaticTable.PathSlash, pathAndQuery, ref headerBuffer);
 01523            }
 1524
 01525            int headerListSize = 3 * HeaderField.RfcOverhead; // Method, Authority, Path
 1526
 01527            if (request.HasHeaders)
 01528            {
 1529                // HTTP2 does not support Transfer-Encoding: chunked, so disable this on the request.
 01530                if (request.Headers.TransferEncodingChunked == true)
 01531                {
 01532                    request.Headers.TransferEncodingChunked = false;
 01533                }
 1534
 01535                if (request.Headers.Protocol is string protocol)
 01536                {
 01537                    WriteBytes(ProtocolLiteralHeaderBytes, ref headerBuffer);
 01538                    Encoding? protocolEncoding = _pool.Settings._requestHeaderEncodingSelector?.Invoke(":protocol", requ
 01539                    WriteLiteralHeaderValue(protocol, protocolEncoding, ref headerBuffer);
 01540                    headerListSize += HeaderField.RfcOverhead;
 01541                }
 1542
 01543                headerListSize += WriteHeaderCollection(request, request.Headers, ref headerBuffer);
 01544            }
 1545
 1546            // Determine cookies to send.
 01547            if (_pool.Settings._useCookies)
 01548            {
 01549                string cookiesFromContainer = _pool.Settings._cookieContainer!.GetCookieHeader(request.RequestUri);
 01550                if (cookiesFromContainer != string.Empty)
 01551                {
 01552                    WriteBytes(KnownHeaders.Cookie.Http2EncodedName, ref headerBuffer);
 01553                    Encoding? cookieEncoding = _pool.Settings._requestHeaderEncodingSelector?.Invoke(KnownHeaders.Cookie
 01554                    WriteLiteralHeaderValue(cookiesFromContainer, cookieEncoding, ref headerBuffer);
 01555                    headerListSize += HttpKnownHeaderNames.Cookie.Length + HeaderField.RfcOverhead;
 01556                }
 01557            }
 1558
 01559            if (request.Content == null)
 01560            {
 1561                // Write out Content-Length: 0 header to indicate no body,
 1562                // unless this is a method that never has a body.
 01563                if (request.Method.MustHaveRequestBody)
 01564                {
 01565                    WriteBytes(KnownHeaders.ContentLength.Http2EncodedName, ref headerBuffer);
 01566                    WriteLiteralHeaderValue("0", valueEncoding: null, ref headerBuffer);
 01567                    headerListSize += HttpKnownHeaderNames.ContentLength.Length + HeaderField.RfcOverhead;
 01568                }
 01569            }
 1570            else
 01571            {
 01572                headerListSize += WriteHeaderCollection(request, request.Content.Headers, ref headerBuffer);
 01573            }
 1574
 1575            // The headerListSize is an approximation of the total header length.
 1576            // This is acceptable as long as the value is always >= the actual length.
 1577            // We must avoid ever sending more than the server allowed.
 1578            // This approach must be revisited if we ever support the dynamic table or compression when sending requests
 01579            headerListSize += headerBuffer.ActiveLength;
 1580
 01581            uint maxHeaderListSize = _maxHeaderListSize;
 01582            if ((uint)headerListSize > maxHeaderListSize)
 01583            {
 01584                throw new HttpRequestException(SR.Format(SR.net_http_request_headers_exceeded_length, maxHeaderListSize)
 1585            }
 01586        }
 1587
 1588        private void AddStream(Http2Stream http2Stream)
 01589        {
 01590            lock (SyncObject)
 01591            {
 01592                if (_nextStream == MaxStreamId)
 01593                {
 1594                    // We have exhausted StreamIds. Shut down the connection.
 01595                    Shutdown();
 01596                }
 1597
 01598                if (_abortException is not null)
 01599                {
 01600                    throw GetRequestAbortedException(_abortException);
 1601                }
 1602
 01603                if (_shutdown)
 01604                {
 1605                    // The connection has shut down. Throw a retryable exception so that this request will be handled on
 01606                    ThrowRetry(SR.net_http_server_shutdown);
 1607                }
 1608
 01609                if (_streamsInUse > _maxConcurrentStreams)
 01610                {
 1611                    // The server must have sent a downward adjustment to SETTINGS_MAX_CONCURRENT_STREAMS, so our previo
 1612                    // We might want a better exception message here, but in general the user shouldn't see this anyway 
 01613                    ThrowRetry(SR.net_http_request_aborted);
 1614                }
 1615
 01616                if (_httpStreams.Count == 0)
 01617                {
 01618                    MarkConnectionAsNotIdle();
 01619                }
 1620
 1621                // Now that we're holding the lock, configure the stream.  The lock must be held while
 1622                // assigning the stream ID to ensure only one stream gets an ID, and it must be held
 1623                // across setting the initial window size (available credit) and storing the stream into
 1624                // collection such that window size updates are able to atomically affect all known streams.
 01625                http2Stream.Initialize(_nextStream, _initialServerStreamWindowSize);
 1626
 1627                // Client-initiated streams are always odd-numbered, so increase by 2.
 01628                _nextStream += 2;
 1629
 01630                _httpStreams.Add(http2Stream.StreamId, http2Stream);
 01631            }
 01632        }
 1633
 1634        private async ValueTask<Http2Stream> SendHeadersAsync(HttpRequestMessage request, CancellationToken cancellation
 01635        {
 01636            ArrayBuffer headerBuffer = default;
 1637            try
 01638            {
 01639                if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestHeadersStart(Id);
 1640
 1641                // Serialize headers to a temporary buffer, and do as much work to prepare to send the headers as we can
 1642                // before taking the write lock.
 01643                headerBuffer = new ArrayBuffer(InitialConnectionBufferSize, usePool: true);
 01644                WriteHeaders(request, ref headerBuffer);
 01645                ReadOnlyMemory<byte> headerBytes = headerBuffer.ActiveMemory;
 01646                Debug.Assert(headerBytes.Length > 0);
 1647
 1648                // Calculate the total number of bytes we're going to use (content + headers).
 01649                int frameCount = ((headerBytes.Length - 1) / FrameHeader.MaxPayloadLength) + 1;
 01650                int totalSize = headerBytes.Length + (frameCount * FrameHeader.Size);
 1651
 1652                // Construct and initialize the new Http2Stream instance.  It's stream ID must be set below
 1653                // before the instance is used and stored into the dictionary.  However, we construct it here
 1654                // so as to avoid the allocation and initialization expense while holding multiple locks.
 01655                var http2Stream = new Http2Stream(request, this);
 1656
 1657                // Start the write.  This serializes access to write to the connection, and ensures that HEADERS
 1658                // and CONTINUATION frames stay together, as they must do. We use the lock as well to ensure new
 1659                // streams are created and started in order.
 01660                await PerformWriteAsync(totalSize, (thisRef: this, http2Stream, headerBytes, endStream: (request.Content
 1661                {
 1662                    s.thisRef.AddStream(s.http2Stream);
 01663
 1664                    if (NetEventSource.Log.IsEnabled()) s.thisRef.Trace(s.http2Stream.StreamId, $"Started writing. Total
 01665
 1666                    Span<byte> span = writeBuffer.Span;
 01667
 01668                    // Copy the HEADERS frame.
 01669                    ReadOnlyMemory<byte> current, remaining;
 1670                    (current, remaining) = SplitBuffer(s.headerBytes, FrameHeader.MaxPayloadLength);
 1671                    FrameFlags flags = (remaining.Length == 0 ? FrameFlags.EndHeaders : FrameFlags.None);
 1672                    flags |= (s.endStream ? FrameFlags.EndStream : FrameFlags.None);
 1673                    FrameHeader.WriteTo(span, current.Length, FrameType.Headers, flags, s.http2Stream.StreamId);
 1674                    span = span.Slice(FrameHeader.Size);
 1675                    current.Span.CopyTo(span);
 1676                    span = span.Slice(current.Length);
 1677                    if (NetEventSource.Log.IsEnabled()) s.thisRef.Trace(s.http2Stream.StreamId, $"Wrote HEADERS frame. L
 01678
 01679                    // Copy CONTINUATION frames, if any.
 1680                    while (remaining.Length > 0)
 1681                    {
 1682                        (current, remaining) = SplitBuffer(remaining, FrameHeader.MaxPayloadLength);
 1683                        flags = remaining.Length == 0 ? FrameFlags.EndHeaders : FrameFlags.None;
 01684
 1685                        FrameHeader.WriteTo(span, current.Length, FrameType.Continuation, flags, s.http2Stream.StreamId)
 1686                        span = span.Slice(FrameHeader.Size);
 1687                        current.Span.CopyTo(span);
 1688                        span = span.Slice(current.Length);
 1689                        if (NetEventSource.Log.IsEnabled()) s.thisRef.Trace(s.http2Stream.StreamId, $"Wrote CONTINUATION
 1690                    }
 01691
 1692                    Debug.Assert(span.Length == 0);
 01693
 1694                    return s.mustFlush || s.endStream;
 1695                }, cancellationToken).ConfigureAwait(false);
 1696
 01697                if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestHeadersStop();
 1698
 01699                return http2Stream;
 1700            }
 01701            catch
 01702            {
 01703                ReleaseStream();
 01704                throw;
 1705            }
 1706            finally
 01707            {
 01708                headerBuffer.Dispose();
 01709            }
 01710        }
 1711
 1712        private async Task SendStreamDataAsync(int streamId, ReadOnlyMemory<byte> buffer, bool finalFlush, CancellationT
 01713        {
 01714            ReadOnlyMemory<byte> remaining = buffer;
 1715
 01716            while (remaining.Length > 0)
 01717            {
 1718                // Once credit had been granted, we want to actually consume those bytes.
 01719                int frameSize = Math.Min(remaining.Length, FrameHeader.MaxPayloadLength);
 01720                frameSize = await _connectionWindow.RequestCreditAsync(frameSize, cancellationToken).ConfigureAwait(fals
 1721
 1722                ReadOnlyMemory<byte> current;
 01723                (current, remaining) = SplitBuffer(remaining, frameSize);
 1724
 01725                bool flush = false;
 01726                if (finalFlush && remaining.Length == 0)
 01727                {
 01728                    flush = true;
 01729                }
 1730
 1731                // Force a flush if we are out of credit, because we don't know that we will be sending more data any ti
 01732                if (!_connectionWindow.IsCreditAvailable)
 01733                {
 01734                    flush = true;
 01735                }
 1736
 1737                try
 01738                {
 01739                    await PerformWriteAsync(FrameHeader.Size + current.Length, (thisRef: this, streamId, current, flush)
 1740                    {
 01741                        // Invoked while holding the lock:
 1742                        if (NetEventSource.Log.IsEnabled()) s.thisRef.Trace(s.streamId, $"Started writing. {nameof(write
 01743
 1744                        FrameHeader.WriteTo(writeBuffer.Span, s.current.Length, FrameType.Data, FrameFlags.None, s.strea
 1745                        s.current.CopyTo(writeBuffer.Slice(FrameHeader.Size));
 01746
 1747                        return s.flush;
 1748                    }, cancellationToken).ConfigureAwait(false);
 01749                }
 01750                catch
 01751                {
 1752                    // Invoked if waiting for the lock is canceled (in that case, we need to return the credit that we h
 01753                    _connectionWindow.AdjustCredit(frameSize);
 01754                    throw;
 1755                }
 01756            }
 01757        }
 1758
 1759        private Task SendEndStreamAsync(int streamId) =>
 01760            PerformWriteAsync(FrameHeader.Size, (thisRef: this, streamId), static (s, writeBuffer) =>
 01761            {
 01762                if (NetEventSource.Log.IsEnabled()) s.thisRef.Trace(s.streamId, "Started writing.");
 01763
 01764                FrameHeader.WriteTo(writeBuffer.Span, 0, FrameType.Data, FrameFlags.EndStream, s.streamId);
 01765
 01766                return true; // finished sending request body, so flush soon (but ok to wait for pending packets)
 01767            });
 1768
 1769        private Task SendWindowUpdateAsync(int streamId, int amount)
 01770        {
 1771            // We update both the connection-level and stream-level windows at the same time
 01772            Debug.Assert(amount > 0);
 01773            return PerformWriteAsync(FrameHeader.Size + FrameHeader.WindowUpdateLength, (thisRef: this, streamId, amount
 01774            {
 01775                if (NetEventSource.Log.IsEnabled()) s.thisRef.Trace(s.streamId, $"Started writing. {nameof(s.amount)}={s
 01776
 01777                Span<byte> span = writeBuffer.Span;
 01778                FrameHeader.WriteTo(span, FrameHeader.WindowUpdateLength, FrameType.WindowUpdate, FrameFlags.None, s.str
 01779                BinaryPrimitives.WriteInt32BigEndian(span.Slice(FrameHeader.Size), s.amount);
 01780
 01781                return true;
 01782            });
 01783        }
 1784
 1785        private bool ExtendWindow(int amount)
 01786        {
 01787            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(amount)}={amount}");
 01788            Debug.Assert(amount > 0);
 01789            Debug.Assert(_pendingWindowUpdate < ConnectionWindowThreshold);
 1790
 01791            _pendingWindowUpdate += amount;
 01792            if (_pendingWindowUpdate < ConnectionWindowThreshold)
 01793            {
 01794                if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(_pendingWindowUpdate)} {_pendingWindowUpdate} < {Con
 01795                return false;
 1796            }
 1797
 01798            int windowUpdateSize = _pendingWindowUpdate;
 01799            _pendingWindowUpdate = 0;
 1800
 01801            LogExceptions(SendWindowUpdateAsync(0, windowUpdateSize));
 01802            return true;
 01803        }
 1804
 1805        private bool ForceSendConnectionWindowUpdate()
 01806        {
 01807            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(_pendingWindowUpdate)}={_pendingWindowUpdate}");
 01808            if (_pendingWindowUpdate == 0) return false;
 1809
 01810            LogExceptions(SendWindowUpdateAsync(0, _pendingWindowUpdate));
 01811            _pendingWindowUpdate = 0;
 01812            return true;
 01813        }
 1814
 1815        /// <summary>Abort all streams and cause further processing to fail.</summary>
 1816        /// <param name="abortException">Exception causing Abort to be called.</param>
 1817        private void Abort(Exception abortException)
 01818        {
 01819            if (NetEventSource.Log.IsEnabled()) Trace($"{nameof(abortException)}=={abortException}");
 1820
 1821            // The connection has failed, e.g. failed IO or a connection-level protocol error.
 01822            List<Http2Stream> streamsToAbort = new List<Http2Stream>();
 01823            lock (SyncObject)
 01824            {
 01825                if (_abortException is not null)
 01826                {
 01827                    if (NetEventSource.Log.IsEnabled()) Trace($"Abort called while already aborting. {nameof(abortExcept
 01828                    return;
 1829                }
 1830
 01831                _abortException = abortException;
 1832
 01833                Shutdown();
 1834
 01835                foreach (KeyValuePair<int, Http2Stream> kvp in _httpStreams)
 01836                {
 01837                    int streamId = kvp.Key;
 01838                    Debug.Assert(streamId == kvp.Value.StreamId);
 1839
 01840                    streamsToAbort.Add(kvp.Value);
 01841                }
 01842            }
 1843
 1844            // Avoid calling OnReset under the lock, as it may cause the Http2Stream to call back in to RemoveStream
 01845            foreach (Http2Stream s in streamsToAbort)
 01846            {
 01847                s.OnReset(_abortException);
 01848            }
 01849        }
 1850
 1851        private void FinalTeardown()
 01852        {
 01853            if (NetEventSource.Log.IsEnabled()) Trace("");
 1854
 01855            Debug.Assert(_shutdown);
 01856            Debug.Assert(_streamsInUse == 0);
 1857
 01858            GC.SuppressFinalize(this);
 01859            _stream.Dispose();
 1860
 01861            _connectionWindow.Dispose();
 01862            bool completed = _writeChannel.Writer.TryComplete();
 01863            Debug.Assert(completed, "FinalTeardown was called twice");
 1864
 1865            // We're not disposing the _incomingBuffer and _outgoingBuffer here as they may still be in use by
 1866            // ProcessIncomingFramesAsync and ProcessOutgoingFramesAsync respectively, and those methods are
 1867            // responsible for returning the buffers.
 1868
 01869            MarkConnectionAsClosed();
 01870        }
 1871
 1872        public override void Dispose()
 01873        {
 01874            lock (SyncObject)
 01875            {
 01876                Shutdown();
 01877            }
 01878        }
 1879
 1880        private enum FrameType : byte
 1881        {
 1882            Data = 0,
 1883            Headers = 1,
 1884            Priority = 2,
 1885            RstStream = 3,
 1886            Settings = 4,
 1887            PushPromise = 5,
 1888            Ping = 6,
 1889            GoAway = 7,
 1890            WindowUpdate = 8,
 1891            Continuation = 9,
 1892            AltSvc = 10,
 1893
 1894            Last = 10
 1895        }
 1896
 1897        private readonly struct FrameHeader
 1898        {
 1899            public readonly int PayloadLength;
 1900            public readonly FrameType Type;
 1901            public readonly FrameFlags Flags;
 1902            public readonly int StreamId;
 1903
 1904            public const int Size = 9;
 1905            public const int MaxPayloadLength = 16384;
 1906
 1907            public const int SettingLength = 6;            // per setting (total SETTINGS length must be a multiple of t
 1908            public const int PriorityInfoLength = 5;       // for both PRIORITY frame and priority info within HEADERS
 1909            public const int PingLength = 8;
 1910            public const int WindowUpdateLength = 4;
 1911            public const int RstStreamLength = 4;
 1912            public const int GoAwayMinLength = 8;
 1913
 1914            public FrameHeader(int payloadLength, FrameType type, FrameFlags flags, int streamId)
 01915            {
 01916                Debug.Assert(streamId >= 0);
 1917
 01918                PayloadLength = payloadLength;
 01919                Type = type;
 01920                Flags = flags;
 01921                StreamId = streamId;
 01922            }
 1923
 01924            public bool PaddedFlag => (Flags & FrameFlags.Padded) != 0;
 01925            public bool AckFlag => (Flags & FrameFlags.Ack) != 0;
 01926            public bool EndHeadersFlag => (Flags & FrameFlags.EndHeaders) != 0;
 01927            public bool EndStreamFlag => (Flags & FrameFlags.EndStream) != 0;
 01928            public bool PriorityFlag => (Flags & FrameFlags.Priority) != 0;
 1929
 1930            public static FrameHeader ReadFrom(ReadOnlySpan<byte> buffer)
 01931            {
 01932                Debug.Assert(buffer.Length >= Size);
 1933
 01934                FrameFlags flags = (FrameFlags)buffer[4]; // do first to avoid some bounds checks
 01935                int payloadLength = (buffer[0] << 16) | (buffer[1] << 8) | buffer[2];
 01936                FrameType type = (FrameType)buffer[3];
 01937                int streamId = (int)(BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(5)) & 0x7FFFFFFF);
 1938
 01939                return new FrameHeader(payloadLength, type, flags, streamId);
 01940            }
 1941
 1942            public static void WriteTo(Span<byte> destination, int payloadLength, FrameType type, FrameFlags flags, int 
 01943            {
 01944                Debug.Assert(destination.Length >= Size);
 01945                Debug.Assert(type <= FrameType.Last);
 01946                Debug.Assert((flags & FrameFlags.ValidBits) == flags);
 01947                Debug.Assert((uint)payloadLength <= MaxPayloadLength);
 1948
 1949                // This ordering helps eliminate bounds checks.
 01950                BinaryPrimitives.WriteInt32BigEndian(destination.Slice(5), streamId);
 01951                destination[4] = (byte)flags;
 01952                destination[0] = (byte)((payloadLength & 0x00FF0000) >> 16);
 01953                destination[1] = (byte)((payloadLength & 0x0000FF00) >> 8);
 01954                destination[2] = (byte)(payloadLength & 0x000000FF);
 01955                destination[3] = (byte)type;
 01956            }
 1957
 01958            public override string ToString() => $"StreamId={StreamId}; Type={Type}; Flags={Flags}; PayloadLength={Paylo
 1959        }
 1960
 1961        [Flags]
 1962        private enum FrameFlags : byte
 1963        {
 1964            None = 0,
 1965
 1966            // Some frame types define bits differently.  Define them all here for simplicity.
 1967
 1968            EndStream =     0b00000001,
 1969            Ack =           0b00000001,
 1970            EndHeaders =    0b00000100,
 1971            Padded =        0b00001000,
 1972            Priority =      0b00100000,
 1973
 1974            ValidBits =     0b00101101
 1975        }
 1976
 1977        private enum SettingId : ushort
 1978        {
 1979            HeaderTableSize = 0x1,
 1980            EnablePush = 0x2,
 1981            MaxConcurrentStreams = 0x3,
 1982            InitialWindowSize = 0x4,
 1983            MaxFrameSize = 0x5,
 1984            MaxHeaderListSize = 0x6,
 1985            EnableConnect = 0x8
 1986        }
 1987
 1988        private static TaskCompletionSourceWithCancellation<bool> CreateSuccessfullyCompletedTcs()
 01989        {
 01990            var tcs = new TaskCompletionSourceWithCancellation<bool>();
 01991            tcs.TrySetResult(true);
 01992            return tcs;
 01993        }
 1994
 1995        // Note that this is safe to be called concurrently by multiple threads.
 1996
 1997        public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool async, CancellationToken cance
 01998        {
 01999            Debug.Assert(async);
 02000            Debug.Assert(!_pool.HasSyncObjLock);
 02001            if (NetEventSource.Log.IsEnabled()) Trace($"Sending request: {request}");
 02002            if (ConnectionSetupActivity is not null) ConnectionSetupDistributedTracing.AddConnectionLinkToRequestActivit
 2003
 2004            try
 02005            {
 2006                // Send request headers
 02007                bool shouldExpectContinue = (request.Content != null && request.HasHeaders && request.Headers.ExpectCont
 02008                Http2Stream http2Stream = await SendHeadersAsync(request, cancellationToken, mustFlush: shouldExpectCont
 2009
 02010                bool duplex = request.Content != null && request.Content.AllowDuplex;
 2011
 2012                // If we have duplex content, then don't propagate the cancellation to the request body task.
 2013                // If cancellation occurs before we receive the response headers, then we will cancel the request body a
 02014                CancellationToken requestBodyCancellationToken = duplex ? CancellationToken.None : cancellationToken;
 2015
 2016                // Start sending request body, if any.
 02017                Task requestBodyTask = http2Stream.SendRequestBodyAsync(requestBodyCancellationToken);
 2018
 2019                // Start receiving the response headers.
 02020                Task responseHeadersTask = http2Stream.ReadResponseHeadersAsync(cancellationToken);
 2021
 2022                // Wait for either task to complete.  The best and most common case is when the request body completes
 2023                // before the response headers, in which case we can fully process the sending of the request and then
 2024                // fully process the sending of the response.  WhenAny is not free, so we do a fast-path check to see
 2025                // if the request body completed synchronously, only progressing to do the WhenAny if it didn't. Then
 2026                // if the WhenAny completes and either the WhenAny indicated that the request body completed or
 2027                // both tasks completed, we can proceed to handle the request body as if it completed first.  We also
 2028                // check whether the request content even allows for duplex communication; if it doesn't (none of
 2029                // our built-in content types do), then we can just proceed to wait for the request body content to
 2030                // complete before worrying about response headers completing.
 02031                if (requestBodyTask.IsCompleted ||
 02032                    !duplex ||
 02033                    await Task.WhenAny(requestBodyTask, responseHeadersTask).ConfigureAwait(false) == requestBodyTask ||
 02034                    requestBodyTask.IsCompleted ||
 02035                    http2Stream.SendRequestFinished)
 02036                {
 2037                    // The sending of the request body completed before receiving all of the request headers (or we're
 2038                    // ok waiting for the request body even if it hasn't completed, e.g. because we're not doing duplex)
 2039                    // This is the common and desirable case.
 2040                    try
 02041                    {
 02042                        await requestBodyTask.ConfigureAwait(false);
 02043                    }
 02044                    catch (Exception e)
 02045                    {
 02046                        if (NetEventSource.Log.IsEnabled()) Trace($"Sending request content failed: {e}");
 02047                        LogExceptions(responseHeadersTask); // Observe exception (if any) on responseHeadersTask.
 02048                        throw;
 2049                    }
 02050                }
 2051                else
 02052                {
 2053                    // We received the response headers but the request body hasn't yet finished; this most commonly hap
 2054                    // when the protocol is being used to enable duplex communication. If the connection is aborted or i
 2055                    // get RST or GOAWAY from server, exception will be stored in stream._abortException and propagated 
 2056                    // to caller if possible while processing response, but make sure that we log any exceptions from th
 2057                    // completing asynchronously).
 02058                    LogExceptions(requestBodyTask);
 02059                }
 2060
 2061                // Wait for the response headers to complete if they haven't already, propagating any exceptions.
 02062                await responseHeadersTask.ConfigureAwait(false);
 2063
 02064                return http2Stream.GetAndClearResponse();
 2065            }
 02066            catch (HttpIOException e)
 02067            {
 02068                throw new HttpRequestException(e.HttpRequestError, e.Message, e);
 2069            }
 02070            catch (Exception e) when (e is IOException || e is ObjectDisposedException || e is InvalidOperationException
 02071            {
 02072                throw new HttpRequestException(HttpRequestError.Unknown, SR.net_http_client_execution_error, e);
 2073            }
 02074        }
 2075
 2076        private void RemoveStream(Http2Stream http2Stream)
 02077        {
 02078            if (NetEventSource.Log.IsEnabled()) Trace(http2Stream.StreamId, "");
 2079
 02080            lock (SyncObject)
 02081            {
 02082                if (!_httpStreams.Remove(http2Stream.StreamId))
 02083                {
 02084                    Debug.Fail($"Stream {http2Stream.StreamId} not found in dictionary during RemoveStream???");
 2085                    return;
 2086                }
 2087
 02088                if (_httpStreams.Count == 0)
 02089                {
 02090                    MarkConnectionAsIdle();
 02091                }
 02092            }
 2093
 02094            ReleaseStream();
 02095        }
 2096
 2097        private void RefreshPingTimestamp()
 02098        {
 02099            _nextPingRequestTimestamp = Environment.TickCount64 + _keepAlivePingDelay;
 02100        }
 2101
 2102        private void ProcessPingAck(long payload)
 02103        {
 2104            // RttEstimator is using negative values in PING payloads.
 2105            // _keepAlivePingPayload is always non-negative.
 02106            if (payload < 0) // RTT ping
 02107            {
 02108                _rttEstimator.OnPingAckReceived(payload, this);
 02109            }
 2110            else // Keepalive ping
 02111            {
 02112                if (_keepAliveState != KeepAliveState.PingSent)
 02113                    ThrowProtocolError();
 02114                if (Interlocked.Read(ref _keepAlivePingPayload) != payload)
 02115                    ThrowProtocolError();
 02116                _keepAliveState = KeepAliveState.None;
 02117            }
 02118        }
 2119
 2120        private void VerifyKeepAlive()
 02121        {
 02122            if (_keepAlivePingPolicy == HttpKeepAlivePingPolicy.WithActiveRequests)
 02123            {
 02124                lock (SyncObject)
 02125                {
 02126                    if (_streamsInUse == 0)
 02127                    {
 02128                        return;
 2129                    }
 02130                }
 02131            }
 2132
 02133            long now = Environment.TickCount64;
 02134            switch (_keepAliveState)
 2135            {
 2136                case KeepAliveState.None:
 2137                    // Check whether keep alive delay has passed since last frame received
 02138                    if (now > _nextPingRequestTimestamp)
 02139                    {
 2140                        // Set the status directly to ping sent and set the timestamp
 02141                        _keepAliveState = KeepAliveState.PingSent;
 02142                        _keepAlivePingTimeoutTimestamp = now + _keepAlivePingTimeout;
 2143
 02144                        long pingPayload = Interlocked.Increment(ref _keepAlivePingPayload);
 02145                        LogExceptions(SendPingAsync(pingPayload));
 02146                        return;
 2147                    }
 02148                    break;
 2149                case KeepAliveState.PingSent:
 02150                    if (now > _keepAlivePingTimeoutTimestamp)
 02151                        ThrowProtocolError(Http2ProtocolErrorCode.ProtocolError, SR.net_ping_request_timed_out);
 02152                    break;
 2153                default:
 02154                    Debug.Fail($"Unexpected keep alive state ({_keepAliveState})");
 2155                    break;
 2156            }
 02157        }
 2158
 02159        public sealed override string ToString() => $"{nameof(Http2Connection)}({_pool})"; // Description for diagnostic
 2160
 2161        public override void Trace(string message, [CallerMemberName] string? memberName = null) =>
 02162            Trace(0, message, memberName);
 2163
 2164        internal void Trace(int streamId, string message, [CallerMemberName] string? memberName = null) =>
 02165            NetEventSource.Log.HandlerMessage(
 02166                _pool?.GetHashCode() ?? 0,    // pool ID
 02167                GetHashCode(),                // connection ID
 02168                streamId,                     // stream ID
 02169                memberName,                   // method name
 02170                message);                     // message
 2171
 2172        [DoesNotReturn]
 2173        private static void ThrowRetry(string message, Exception? innerException = null) =>
 02174            throw new HttpRequestException((innerException as HttpIOException)?.HttpRequestError ?? HttpRequestError.Unk
 2175
 2176        private static Exception GetRequestAbortedException(Exception? innerException = null) =>
 02177            innerException as HttpIOException ?? ExceptionDispatchInfo.SetCurrentStackTrace(new IOException(SR.net_http_
 2178
 2179        [DoesNotReturn]
 2180        private static void ThrowRequestAborted(Exception? innerException = null) =>
 02181            throw GetRequestAbortedException(innerException);
 2182
 2183        [DoesNotReturn]
 2184        private static void ThrowProtocolError() =>
 02185            ThrowProtocolError(Http2ProtocolErrorCode.ProtocolError);
 2186
 2187        [DoesNotReturn]
 2188        private static void ThrowProtocolError(Http2ProtocolErrorCode errorCode, string? message = null) =>
 02189            throw HttpProtocolException.CreateHttp2ConnectionException(errorCode, message);
 2190    }
 2191}

D:\runner\runtime\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!;
 347                using (cancellationToken.UnsafeRegister(static s => ((TaskCompletionSource<bool>)s!).TrySetResult(false)
 0348                await using (new Timer(static s =>
 349                {
 350                    var thisRef = (Http2Stream)s!;
 351                    if (NetEventSource.Log.IsEnabled()) thisRef.Trace($"100-Continue timer expired.");
 352                    thisRef._expect100ContinueWaiter?.TrySetResult(true);
 353                }, 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}

D:\runner\runtime\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.LogExceptions(connection.SendPingAsync(_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}

Methods/Properties

ProtocolLiteralHeaderBytes()
.cctor()
.ctor(System.Net.Http.HttpConnectionPool,System.IO.Stream,System.Diagnostics.Activity,System.Net.IPEndPoint)
Http2ConnectionPreface()
TimeSpanToMs(System.TimeSpan)
Finalize()
SyncObject()
InitialSettingsReceived()
IsConnectEnabled()
SetupAsync()
Shutdown()
TryReserveStream()
ReleaseStream()
WaitForAvailableStreamsAsync()
SignalAvailableStreamsWaiter(System.Boolean)
FlushOutgoingBytesAsync()
ReadFrameAsync()
ThrowPrematureEOF(System.Int32)
ThrowMissingFrame()
ProcessIncomingFramesAsync()
GetStream(System.Int32)
ProcessHeadersFrame()
.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()
SendSettingsAckAsync()
SendPingAsync(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()
SendStreamDataAsync()
SendEndStreamAsync(System.Int32)
SendWindowUpdateAsync(System.Int32,System.Int32)
ExtendWindow(System.Int32)
ForceSendConnectionWindowUpdate()
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()
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()
WaitFor100ContinueAsync()
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()
TryReadFromBuffer(System.Span`1<System.Byte>,System.Boolean)
ReadData(System.Span`1<System.Byte>,System.Net.Http.HttpResponseMessage)
ReadDataAsync()
CopyTo(System.Net.Http.HttpResponseMessage,System.IO.Stream,System.Int32)
CopyToAsync()
MoveTrailersToResponseMessage(System.Net.Http.HttpResponseMessage)
SendDataAsync()
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)