< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 897
Coverable lines: 897
Total lines: 1601
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 372
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%110%
Dispose()0%440%
RemoveFromConnectionIfDone()0%440%
DisposeAsync()0%440%
DisposeSyncHelper()0%440%
GoAway()100%110%
SendAsync(...)0%48480%
ReadResponseAsync(...)0%12120%
SendContentAsync(...)0%24240%
WriteRequestContentAsync(...)0%880%
FlushSendBufferAsync(...)100%110%
DrainContentLength0Frames(...)0%10100%
ProcessTrailersAsync(...)0%220%
CopyTrailersToResponseMessage(...)0%220%
BufferHeaders(...)0%28280%
BufferHeaderCollection(...)0%22220%
BufferIndexedHeader(...)0%220%
BufferLiteralHeaderWithStaticNameReference(...)0%220%
BufferLiteralHeaderWithoutNameReference(...)0%220%
BufferLiteralHeaderWithoutNameReference(...)0%220%
BufferLiteralHeaderValues(...)0%220%
BufferFrameEnvelope(...)0%220%
BufferBytes(...)100%110%
ReadFrameEnvelopeAsync(...)0%25250%
ReadHeadersAsync(...)0%880%
System.Net.Http.IHttpStreamHeadersHandler.OnHeader(...)0%220%
System.Net.Http.IHttpStreamHeadersHandler.OnStaticIndexedHeader(...)100%110%
System.Net.Http.IHttpStreamHeadersHandler.OnStaticIndexedHeader(...)100%110%
System.Net.Http.IHttpStreamHeadersHandler.OnDynamicIndexedHeader(...)100%110%
GetStaticQPackHeader(...)0%440%
OnHeader(...)0%63630%
ParseStatusCode(System.Nullable`1<System.Int32>,System.String)100%110%
System.Net.Http.IHttpStreamHeadersHandler.OnHeadersComplete(...)100%110%
SkipUnknownPayloadAsync(...)0%660%
ReadResponseContent(...)0%16160%
ReadResponseContentAsync(...)0%16160%
HandleReadResponseContentException(...)0%22220%
ReadNextDataFrameAsync(...)0%10100%
Trace(...)100%110%
AbortStream()0%440%
.ctor(...)100%110%
Finalize()100%110%
Dispose(...)0%440%
DisposeAsync()0%220%
Read(...)100%110%
ReadAsync(...)0%220%
WriteAsync(...)100%110%
.ctor(...)100%110%
Dispose(...)100%110%
Read(...)100%110%
ReadAsync(...)100%110%
WriteAsync(...)0%220%
FlushAsync(...)0%220%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6using System.Diagnostics.CodeAnalysis;
 7using System.IO;
 8using System.Net.Http.Headers;
 9using System.Net.Http.QPack;
 10using System.Net.Quic;
 11using System.Runtime.CompilerServices;
 12using System.Runtime.ExceptionServices;
 13using System.Runtime.Versioning;
 14using System.Text;
 15using System.Threading;
 16using System.Threading.Tasks;
 17
 18namespace System.Net.Http
 19{
 20    [SupportedOSPlatform("linux")]
 21    [SupportedOSPlatform("macos")]
 22    [SupportedOSPlatform("windows")]
 23    internal sealed class Http3RequestStream : IHttpStreamHeadersHandler, IAsyncDisposable, IDisposable
 24    {
 25        private readonly HttpRequestMessage _request;
 26        private Http3Connection _connection;
 027        private long _streamId = -1; // A stream does not have an ID until the first I/O against it. This gets set almos
 28        private readonly QuicStream _stream;
 29        private volatile bool _finishingBackgroundWrite;
 30        private ArrayBuffer _sendBuffer;
 31        private volatile bool _finishingBackgroundRead;
 32        private ArrayBuffer _recvBuffer;
 33        private TaskCompletionSource<bool>? _expect100ContinueCompletionSource; // True indicates we should send content
 34        private bool _disposed;
 35        private readonly CancellationTokenSource _requestBodyCancellationSource;
 36
 37        // Allocated when we receive a :status header.
 38        private HttpResponseMessage? _response;
 39
 40        // Header decoding.
 41        private readonly QPackDecoder _headerDecoder;
 42        private HeaderState _headerState;
 43        private int _headerBudgetRemaining;
 44
 45        /// <summary>Reusable array used to get the values for each header being written to the wire.</summary>
 046        private string[] _headerValues = Array.Empty<string>();
 47
 48        /// <summary>Any trailing headers.</summary>
 49        private HttpResponseHeaders? _trailingHeaders;
 50
 51        // When reading response content, keep track of the number of bytes left in the current data frame.
 52        private long _responseDataPayloadRemaining;
 53
 54        // When our request content has a precomputed length, it is sent over a single DATA frame.
 55        // Keep track of how much is remaining in that frame.
 56        private long _requestContentLengthRemaining;
 57
 58        // For the precomputed length case, we need to add the DATA framing for the first write only.
 59        private bool _singleDataFrameWritten;
 60
 61        private bool _requestSendCompleted;
 62        private bool _responseRecvCompleted;
 63
 64        public long StreamId
 65        {
 066            get => Volatile.Read(ref _streamId);
 067            set => Volatile.Write(ref _streamId, value);
 68        }
 69
 070        public Http3RequestStream(HttpRequestMessage request, Http3Connection connection, QuicStream stream)
 071        {
 072            _request = request;
 073            _connection = connection;
 074            _stream = stream;
 075            _sendBuffer = new ArrayBuffer(initialSize: 64, usePool: true);
 076            _recvBuffer = new ArrayBuffer(initialSize: 64, usePool: true);
 77
 078            _headerBudgetRemaining = connection.Pool.Settings.MaxResponseHeadersByteLength;
 079            _headerDecoder = new QPackDecoder(maxHeadersLength: (int)Math.Min(int.MaxValue, _headerBudgetRemaining));
 80
 081            _requestBodyCancellationSource = new CancellationTokenSource();
 82
 083            _requestSendCompleted = _request.Content == null;
 084            _responseRecvCompleted = false;
 085        }
 86
 87        public void Dispose()
 088        {
 089            if (!_disposed)
 090            {
 091                _disposed = true;
 092                AbortStream();
 093                if (_stream.WritesClosed.IsCompleted)
 094                {
 095                    _connection.LogExceptions(_stream.DisposeAsync().AsTask());
 096                }
 97                else
 098                {
 099                    _stream.Dispose();
 0100                }
 0101                DisposeSyncHelper();
 0102            }
 0103        }
 104
 105        private void RemoveFromConnectionIfDone()
 0106        {
 0107            if (_responseRecvCompleted && _requestSendCompleted)
 0108            {
 0109                _connection.RemoveStream(_stream);
 0110            }
 0111        }
 112
 113        public async ValueTask DisposeAsync()
 0114        {
 0115            if (!_disposed)
 0116            {
 0117                _disposed = true;
 0118                AbortStream();
 0119                if (_stream.WritesClosed.IsCompleted)
 0120                {
 0121                    _connection.LogExceptions(_stream.DisposeAsync().AsTask());
 0122                }
 123                else
 0124                {
 0125                    await _stream.DisposeAsync().ConfigureAwait(false);
 0126                }
 0127                DisposeSyncHelper();
 0128            }
 0129        }
 130
 131        private void DisposeSyncHelper()
 0132        {
 0133            _connection.RemoveStream(_stream);
 134
 135            // If the request sending was offloaded to be done concurrently and not awaited within SendAsync (by calling
 136            // the _sendBuffer disposal is the responsibility of that offloaded task to prevent returning the buffer to 
 0137            if (!_finishingBackgroundWrite)
 0138            {
 0139                _sendBuffer.Dispose();
 0140            }
 141            // If the response receiving was offloaded to be done concurrently and not awaited within SendAsync (by call
 142            // the _recvBuffer disposal is the responsibility of that offloaded task to prevent returning the buffer to 
 0143            if (!_finishingBackgroundRead)
 0144            {
 0145                _recvBuffer.Dispose();
 0146            }
 0147        }
 148
 149        public void GoAway()
 0150        {
 0151            _requestBodyCancellationSource.Cancel();
 0152        }
 153
 154        public async Task<HttpResponseMessage> SendAsync(CancellationToken cancellationToken)
 0155        {
 156            // If true, dispose the stream upon return. Will be set to false if we're duplex or returning content.
 0157            bool disposeSelf = true;
 158
 0159            bool duplex = _request.Content != null && _request.Content.AllowDuplex;
 160
 161            // Link the input token with _requestBodyCancellationSource, so cancellation will trigger on GoAway() or Abo
 0162            CancellationTokenRegistration linkedTokenRegistration = cancellationToken.UnsafeRegister(cts => ((Cancellati
 163
 164            // upon failure, we should cancel the _requestBodyCancellationSource
 0165            bool shouldCancelBody = true;
 166            try
 0167            {
 0168                BufferHeaders(_request);
 169
 170                // If using Expect 100 Continue, setup a TCS to wait to send content until we get a response.
 0171                if (_request.HasHeaders && _request.Headers.ExpectContinue == true)
 0172                {
 0173                    _expect100ContinueCompletionSource = new TaskCompletionSource<bool>();
 0174                }
 175
 0176                if (_expect100ContinueCompletionSource != null || _request.Content == null)
 0177                {
 178                    // Ideally, headers will be sent out in a gathered write inside of SendContentAsync().
 179                    // If we don't have content, or we are doing Expect 100 Continue, then we can't rely on
 180                    // this and must send our headers immediately.
 181
 182                    // End the stream writing if there's no content to send, do it as part of the write so that the FIN 
 183                    // Note that there's no need to call Shutdown separately since the FIN flag in the last write is the
 0184                    await FlushSendBufferAsync(endStream: _request.Content == null, _requestBodyCancellationSource.Token
 0185                }
 186
 0187                Task sendRequestTask = _request.Content != null
 0188                    ? SendContentAsync(_request.Content, _requestBodyCancellationSource.Token)
 0189                    : Task.CompletedTask;
 190
 191                // In parallel, send content and read response.
 192                // Depending on Expect 100 Continue usage, one will depend on the other making progress.
 0193                Task readResponseTask = ReadResponseAsync(_requestBodyCancellationSource.Token);
 0194                bool sendContentObserved = false;
 195
 196                // If we're not doing duplex, wait for content to finish sending here.
 197                // If we are doing duplex and have the unlikely event that it completes here, observe the result.
 198                // See Http2Connection.SendAsync for a full comment on this logic -- it is identical behavior.
 0199                if (sendRequestTask.IsCompleted ||
 0200                    _request.Content?.AllowDuplex != true ||
 0201                    await Task.WhenAny(sendRequestTask, readResponseTask).ConfigureAwait(false) == sendRequestTask ||
 0202                    sendRequestTask.IsCompleted)
 0203                {
 204                    try
 0205                    {
 0206                        await sendRequestTask.ConfigureAwait(false);
 0207                        sendContentObserved = true;
 0208                    }
 0209                    catch
 0210                    {
 211                        // This is a best effort attempt to transfer the responsibility of disposing _recvBuffer to Read
 212                        // The task might be past checking the variable or already finished, in which case the buffer wo
 213                        // Not returning the buffer to the pool is an acceptable trade-off for making sure that the buff
 0214                        _finishingBackgroundRead = true;
 215
 216                        // Exceptions will be bubbled up from sendRequestTask here,
 217                        // which means the result of readResponseTask won't be observed directly:
 218                        // Do a background await to log any exceptions.
 0219                        _connection.LogExceptions(readResponseTask);
 0220                        throw;
 221                    }
 0222                }
 223                else
 0224                {
 225                    // This is a best effort attempt to transfer the responsibility of disposing _sendBuffer to SendCont
 226                    // The task might be past checking the variable or already finished, in which case the buffer won't 
 227                    // Not returning the buffer to the pool is an acceptable trade-off for making sure that the buffer i
 0228                    _finishingBackgroundWrite = true;
 229
 230                    // Duplex is being used, so we can't wait for content to finish sending.
 231                    // Do a background await to log any exceptions.
 0232                    _connection.LogExceptions(sendRequestTask);
 0233                }
 234
 235                // Wait for the response headers to be read.
 0236                await readResponseTask.ConfigureAwait(false);
 237
 238                // If we've sent a body, wait for the writes to be closed (most likely already done).
 239                // If sendRequestTask hasn't completed yet, we're doing duplex content transfers and can't wait for writ
 0240                if (sendRequestTask.IsCompletedSuccessfully &&
 0241                    _stream.WritesClosed is { IsCompletedSuccessfully: false } writesClosed)
 0242                {
 243                    try
 0244                    {
 0245                        await writesClosed.WaitAsync(_requestBodyCancellationSource.Token).ConfigureAwait(false);
 0246                    }
 0247                    catch (QuicException qex) when (qex.QuicError == QuicError.StreamAborted && qex.ApplicationErrorCode
 0248                    {
 249                        // The server doesn't need the whole request to respond so it's aborting its reading side gracef
 0250                    }
 0251                }
 252
 0253                Debug.Assert(_response != null && _response.Content != null);
 254                // Set our content stream.
 0255                var responseContent = (HttpConnectionResponseContent)_response.Content;
 256
 257                // If we have received Content-Length: 0 and have completed sending content (which may not be the case i
 258                // we can close our Http3RequestStream immediately and return a singleton empty content stream. Otherwis
 259                // need to return a Http3ReadStream which will be responsible for disposing the Http3RequestStream.
 0260                bool useEmptyResponseContent = responseContent.Headers.ContentLength == 0 && sendContentObserved;
 0261                if (useEmptyResponseContent)
 0262                {
 263                    // Drain the response frames to read any trailing headers.
 0264                    await DrainContentLength0Frames(_requestBodyCancellationSource.Token).ConfigureAwait(false);
 0265                    responseContent.SetStream(EmptyReadStream.Instance);
 0266                }
 267                else
 0268                {
 269                    // A read stream is required to finish up the request.
 0270                    responseContent.SetStream(new Http3ReadStream(this));
 0271                }
 0272                if (NetEventSource.Log.IsEnabled()) Trace($"Received response: {_response}");
 273
 274                // Process any Set-Cookie headers.
 0275                if (_connection.Pool.Settings._useCookies)
 0276                {
 0277                    CookieHelper.ProcessReceivedCookies(_response, _connection.Pool.Settings._cookieContainer!);
 0278                }
 279
 280                // To avoid a circular reference (stream->response->content->stream), null out the stream's response.
 0281                HttpResponseMessage response = _response;
 0282                _response = null;
 283
 284                // If we're 100% done with the stream, dispose.
 0285                disposeSelf = useEmptyResponseContent;
 286
 287                // Success, don't cancel the body.
 0288                shouldCancelBody = false;
 0289                return response;
 290            }
 0291            catch (QuicException ex) when (ex.QuicError == QuicError.StreamAborted)
 292            {
 293                Debug.Assert(ex.ApplicationErrorCode.HasValue);
 294                Http3ErrorCode code = (Http3ErrorCode)ex.ApplicationErrorCode.Value;
 295
 296                switch (code)
 297                {
 298                    case Http3ErrorCode.VersionFallback:
 299                        // The server is requesting us fall back to an older HTTP version.
 300                        throw new HttpRequestException(HttpRequestError.Unknown, SR.net_http_retry_on_older_version, ex,
 301
 302                    case Http3ErrorCode.RequestRejected:
 303                        // The server is rejecting the request without processing it, retry it on a different connection
 304                        HttpProtocolException rejectedException = HttpProtocolException.CreateHttp3StreamException(code,
 305                        throw new HttpRequestException(HttpRequestError.HttpProtocolError, SR.net_http_request_aborted, 
 306
 307                    default:
 308                        // Our stream was reset.
 309                        Exception innerException = _connection.AbortException ?? HttpProtocolException.CreateHttp3Stream
 310                        HttpRequestError httpRequestError = innerException is HttpProtocolException ? HttpRequestError.H
 311                        throw new HttpRequestException(httpRequestError, SR.net_http_client_execution_error, innerExcept
 312                }
 313            }
 0314            catch (QuicException ex) when (ex.QuicError == QuicError.ConnectionAborted)
 315            {
 316                // Our connection was reset. Start shutting down the connection.
 317                Debug.Assert(ex.ApplicationErrorCode.HasValue);
 318                Http3ErrorCode code = (Http3ErrorCode)ex.ApplicationErrorCode.Value;
 319
 320                Exception abortException = _connection.Abort(HttpProtocolException.CreateHttp3ConnectionException(code, 
 321                throw new HttpRequestException(HttpRequestError.HttpProtocolError, SR.net_http_client_execution_error, a
 322            }
 0323            catch (QuicException ex) when (ex.QuicError == QuicError.OperationAborted && cancellationToken.IsCancellatio
 324            {
 325                // It is possible for QuicStream's code to throw an
 326                // OperationAborted QuicException when cancellation is requested.
 327                throw new TaskCanceledException(ex.Message, ex, cancellationToken);
 328            }
 0329            catch (QuicException ex) when (ex.QuicError == QuicError.OperationAborted && _connection.AbortException != n
 330            {
 331                // we closed the connection already, propagate the AbortException
 332                HttpRequestError httpRequestError = _connection.AbortException is HttpProtocolException
 333                    ? HttpRequestError.HttpProtocolError
 334                    : HttpRequestError.Unknown;
 335
 336                throw new HttpRequestException(httpRequestError, SR.net_http_client_execution_error, _connection.AbortEx
 337            }
 0338            catch (QuicException ex)
 0339            {
 340                // Any other QuicException means transport error, and should be treated as a connection failure.
 0341                _connection.Abort(ex);
 0342                throw new HttpRequestException(HttpRequestError.Unknown, SR.net_http_http3_connection_quic_error, ex);
 343            }
 344            // It is possible for user's Content code to throw an unexpected OperationCanceledException.
 0345            catch (OperationCanceledException ex) when (ex.CancellationToken == _requestBodyCancellationSource.Token || 
 346            {
 347                // We're either observing GOAWAY, or the cancellationToken parameter has been canceled.
 348                if (cancellationToken.IsCancellationRequested)
 349                {
 350                    _stream.Abort(QuicAbortDirection.Write, (long)Http3ErrorCode.RequestCancelled);
 351                    throw new TaskCanceledException(ex.Message, ex, cancellationToken);
 352                }
 353                else
 354                {
 355                    Debug.Assert(_requestBodyCancellationSource.IsCancellationRequested);
 356                    throw new HttpRequestException(HttpRequestError.Unknown, SR.net_http_request_aborted, ex, RequestRet
 357                }
 358            }
 0359            catch (HttpIOException ex)
 0360            {
 0361                _connection.Abort(ex);
 0362                throw new HttpRequestException(ex.HttpRequestError, SR.net_http_client_execution_error, ex);
 363            }
 0364            catch (QPackDecodingException ex)
 0365            {
 0366                Exception abortException = _connection.Abort(HttpProtocolException.CreateHttp3ConnectionException(Http3E
 0367                throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.net_http_invalid_response, ex);
 368            }
 0369            catch (QPackEncodingException ex)
 0370            {
 0371                _stream.Abort(QuicAbortDirection.Write, (long)Http3ErrorCode.InternalError);
 0372                throw new HttpRequestException(HttpRequestError.Unknown, SR.net_http_client_execution_error, ex);
 373            }
 0374            catch (Exception ex)
 0375            {
 0376                _stream.Abort(QuicAbortDirection.Write, (long)Http3ErrorCode.InternalError);
 0377                if (ex is HttpRequestException)
 0378                {
 0379                    throw;
 380                }
 381
 382                // all exceptions should be already handled above
 0383                Debug.Fail($"Unexpected exception type in Http3RequestStream.SendAsync: {ex}");
 384                throw new HttpRequestException(HttpRequestError.Unknown, SR.net_http_client_execution_error, ex);
 385            }
 386            finally
 0387            {
 0388                if (shouldCancelBody)
 0389                {
 0390                    _requestBodyCancellationSource.Cancel();
 0391                }
 392
 0393                linkedTokenRegistration.Dispose();
 0394                if (disposeSelf)
 0395                {
 0396                    await DisposeAsync().ConfigureAwait(false);
 0397                }
 0398            }
 0399        }
 400
 401        /// <summary>
 402        /// Waits for the response headers to be read, and handles (Expect 100 etc.) informational statuses.
 403        /// </summary>
 404        private async Task ReadResponseAsync(CancellationToken cancellationToken)
 0405        {
 406            try
 0407            {
 0408                if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersStart();
 409
 0410                Debug.Assert(_response == null);
 411                do
 0412                {
 0413                    _headerState = HeaderState.StatusHeader;
 414
 0415                    (Http3FrameType? frameType, long payloadLength) = await ReadFrameEnvelopeAsync(cancellationToken).Co
 416
 0417                    if (frameType != Http3FrameType.Headers)
 0418                    {
 0419                        if (NetEventSource.Log.IsEnabled())
 0420                        {
 0421                            Trace($"Expected HEADERS as first response frame; received {frameType}.");
 0422                        }
 0423                        throw new HttpIOException(HttpRequestError.InvalidResponse, SR.net_http_invalid_response);
 424                    }
 425
 0426                    await ReadHeadersAsync(payloadLength, cancellationToken).ConfigureAwait(false);
 0427                    Debug.Assert(_response != null);
 0428                }
 0429                while ((int)_response.StatusCode < 200);
 430
 0431                _headerState = HeaderState.TrailingHeaders;
 432
 0433                if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersStop((int)_response.StatusCode);
 0434            }
 435            finally
 0436            {
 437                // Note that we might still observe false here even if we're responsible for the _recvBuffer disposal.
 438                // But in that case, we just don't return the rented buffer to the pool, which is lesser evil than writi
 0439                if (_finishingBackgroundRead)
 0440                {
 0441                    _recvBuffer.Dispose();
 0442                }
 0443            }
 0444        }
 445
 446        private async Task SendContentAsync(HttpContent content, CancellationToken cancellationToken)
 0447        {
 448            try
 0449            {
 450                // If we're using Expect 100 Continue, wait to send content
 451                // until we get a response back or until our timeout elapses.
 0452                if (_expect100ContinueCompletionSource != null)
 0453                {
 0454                    Timer? timer = null;
 455
 456                    try
 0457                    {
 0458                        if (_connection.Pool.Settings._expect100ContinueTimeout != Timeout.InfiniteTimeSpan)
 0459                        {
 0460                            timer = new Timer(static o => ((Http3RequestStream)o!)._expect100ContinueCompletionSource!.T
 0461                                this, _connection.Pool.Settings._expect100ContinueTimeout, Timeout.InfiniteTimeSpan);
 0462                        }
 463
 0464                        if (!await _expect100ContinueCompletionSource.Task.ConfigureAwait(false))
 0465                        {
 466                            // We received an error response code, so the body should not be sent.
 0467                            return;
 468                        }
 0469                    }
 470                    finally
 0471                    {
 0472                        if (timer != null)
 0473                        {
 0474                            await timer.DisposeAsync().ConfigureAwait(false);
 0475                        }
 0476                    }
 0477                }
 478
 0479                if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestContentStart();
 480
 481                // If we have a Content-Length, keep track of it so we don't over-send and so we can send in a single DA
 0482                _requestContentLengthRemaining = content.Headers.ContentLength ?? -1;
 483
 484                long bytesWritten;
 0485                using (var writeStream = new Http3WriteStream(this))
 0486                {
 0487                    await content.CopyToAsync(writeStream, null, cancellationToken).ConfigureAwait(false);
 0488                    bytesWritten = writeStream.BytesWritten;
 0489                }
 490
 0491                if (_requestContentLengthRemaining > 0)
 0492                {
 493                    // The number of bytes we actually sent doesn't match the advertised Content-Length
 0494                    long contentLength = content.Headers.ContentLength.GetValueOrDefault();
 0495                    long sent = contentLength - _requestContentLengthRemaining;
 0496                    throw new HttpRequestException(SR.Format(SR.net_http_request_content_length_mismatch, sent, contentL
 497                }
 498
 499                // Set to 0 to recognize that the whole request body has been sent and therefore there's no need to abor
 0500                _requestContentLengthRemaining = 0;
 501
 0502                if (_sendBuffer.ActiveLength != 0)
 0503                {
 504                    // Our initial send buffer, which has our headers, is normally sent out on the first write to the Ht
 505                    // If we get here, it means the content didn't actually do any writing. Send out the headers now.
 506                    // Also send the FIN flag, since this is the last write. No need to call Shutdown separately.
 0507                    await FlushSendBufferAsync(endStream: true, cancellationToken).ConfigureAwait(false);
 0508                }
 509                else
 0510                {
 0511                    _stream.CompleteWrites();
 0512                }
 513
 0514                if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestContentStop(bytesWritten);
 0515            }
 0516            catch (HttpRequestException hex) when (hex.InnerException is QuicException qex && qex.QuicError == QuicError
 0517            {
 518                // The server doesn't need the whole request to respond so it's aborting its reading side gracefully, se
 0519            }
 520            finally
 0521            {
 522                // Note that we might still observe false here even if we're responsible for the _sendBuffer disposal.
 523                // But in that case, we just don't return the rented buffer to the pool, which is lesser evil than writi
 0524                if (_finishingBackgroundWrite)
 0525                {
 0526                    _sendBuffer.Dispose();
 0527                }
 0528                _requestSendCompleted = true;
 0529                RemoveFromConnectionIfDone();
 0530            }
 0531        }
 532
 533        private async ValueTask WriteRequestContentAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToke
 0534        {
 0535            if (buffer.Length == 0)
 0536            {
 0537                return;
 538            }
 539
 0540            long remaining = _requestContentLengthRemaining;
 0541            if (remaining != -1)
 0542            {
 543                // This HttpContent had a precomputed length, and a DATA frame was written as part of the headers. We ca
 544
 0545                if (buffer.Length > _requestContentLengthRemaining)
 0546                {
 0547                    throw new HttpRequestException(SR.net_http_content_write_larger_than_content_length);
 548                }
 0549                _requestContentLengthRemaining -= buffer.Length;
 550
 0551                if (!_singleDataFrameWritten)
 0552                {
 553                    // Note we may not have sent headers yet; if so, _sendBuffer.ActiveLength will be > 0, and we will w
 554
 555                    // Because we have a Content-Length, we can write it in a single DATA frame.
 0556                    BufferFrameEnvelope(Http3FrameType.Data, remaining);
 557
 0558                    await _stream.WriteAsync(_sendBuffer.ActiveMemory, cancellationToken).ConfigureAwait(false);
 0559                    await _stream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
 560
 0561                    _sendBuffer.DiscardAll();
 562
 0563                    _singleDataFrameWritten = true;
 0564                }
 565                else
 0566                {
 567                    // DATA frame already sent, send just the content buffer directly.
 0568                    await _stream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
 0569                }
 0570            }
 571            else
 0572            {
 573                // Variable-length content: write both a DATA frame and buffer. (and headers, which will still be in _se
 574                // It's up to the HttpContent to give us sufficiently large writes to avoid excessively small DATA frame
 0575                BufferFrameEnvelope(Http3FrameType.Data, buffer.Length);
 576
 0577                await _stream.WriteAsync(_sendBuffer.ActiveMemory, cancellationToken).ConfigureAwait(false);
 0578                await _stream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
 579
 0580                _sendBuffer.DiscardAll();
 0581            }
 0582        }
 583
 584        private ValueTask FlushSendBufferAsync(bool endStream, CancellationToken cancellationToken)
 0585        {
 0586            ReadOnlyMemory<byte> toSend = _sendBuffer.ActiveMemory;
 0587            _sendBuffer.DiscardAll();
 0588            return _stream.WriteAsync(toSend, endStream, cancellationToken);
 0589        }
 590
 591        private async ValueTask DrainContentLength0Frames(CancellationToken cancellationToken)
 0592        {
 593            Http3FrameType? frameType;
 594            long payloadLength;
 595
 0596            while (true)
 0597            {
 0598                (frameType, payloadLength) = await ReadFrameEnvelopeAsync(cancellationToken).ConfigureAwait(false);
 599
 0600                switch (frameType)
 601                {
 602                    case Http3FrameType.Headers:
 603                        // Pick up any trailing headers and stop processing.
 0604                        await ProcessTrailersAsync(payloadLength, cancellationToken).ConfigureAwait(false);
 605
 0606                        goto case null;
 607                    case null:
 608                        // Done receiving: copy over trailing headers.
 0609                        CopyTrailersToResponseMessage(_response!);
 610
 0611                        _responseDataPayloadRemaining = -1; // Set to -1 to indicate EOS.
 0612                        return;
 613                    case Http3FrameType.Data:
 614                        // The sum of data frames must equal content length. Because this method is only
 615                        // called for a Content-Length of 0, anything other than 0 here would be an error.
 616                        // Per spec, 0-length payload is allowed.
 0617                        if (payloadLength != 0)
 0618                        {
 0619                            if (NetEventSource.Log.IsEnabled())
 0620                            {
 0621                                Trace("Response content exceeded Content-Length.");
 0622                            }
 0623                            throw new HttpIOException(HttpRequestError.InvalidResponse, SR.net_http_invalid_response);
 624                        }
 0625                        break;
 626                    default:
 0627                        Debug.Fail($"Received unexpected frame type {frameType}.");
 628                        return;
 629                }
 0630            }
 0631        }
 632
 633        private async ValueTask ProcessTrailersAsync(long payloadLength, CancellationToken cancellationToken)
 0634        {
 0635            _trailingHeaders = new HttpResponseHeaders(containsTrailingHeaders: true);
 0636            await ReadHeadersAsync(payloadLength, cancellationToken).ConfigureAwait(false);
 637
 638            // In typical cases, there should be no more frames. Make sure to read the EOS.
 0639            _recvBuffer.EnsureAvailableSpace(1);
 0640            int bytesRead = await _stream.ReadAsync(_recvBuffer.AvailableMemory, cancellationToken).ConfigureAwait(false
 0641            if (bytesRead > 0)
 0642            {
 643                // The server may send us frames of unknown types after the trailer. Ideally we should drain the respons
 644                // but this is a rare case so we just stop reading and let Dispose() send an ABORT_RECEIVE.
 645                // Note: if a server sends additional HEADERS or DATA frames at this point, it
 646                // would be a connection error -- not draining the stream also means we won't catch this.
 0647                _recvBuffer.Commit(bytesRead);
 0648                _recvBuffer.Discard(bytesRead);
 0649            }
 0650        }
 651
 652        private void CopyTrailersToResponseMessage(HttpResponseMessage responseMessage)
 0653        {
 0654            if (_trailingHeaders is not null)
 0655            {
 0656                responseMessage.StoreReceivedTrailingHeaders(_trailingHeaders);
 0657            }
 0658        }
 659
 660        private void BufferHeaders(HttpRequestMessage request)
 0661        {
 0662            if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestHeadersStart(_connection.Id);
 663
 664            // Reserve space for the header frame envelope.
 665            // The envelope needs to be written after headers are serialized, as we need to know the payload length firs
 666            const int PreHeadersReserveSpace = Http3Frame.MaximumEncodedFrameEnvelopeLength;
 667
 668            // This should be the first write to our buffer. The trick of reserving space won't otherwise work.
 0669            Debug.Assert(_sendBuffer.ActiveLength == 0);
 670
 671            // Reserve space for header frame envelope.
 0672            _sendBuffer.Commit(PreHeadersReserveSpace);
 673
 674            // Add header block prefix. We aren't using dynamic table, so these are simple zeroes.
 675            // https://tools.ietf.org/html/draft-ietf-quic-qpack-11#section-4.5.1
 0676            _sendBuffer.EnsureAvailableSpace(2);
 0677            _sendBuffer.AvailableSpan[0] = 0x00; // required insert count.
 0678            _sendBuffer.AvailableSpan[1] = 0x00; // s + delta base.
 0679            _sendBuffer.Commit(2);
 680
 0681            BufferBytes(request.Method.Http3EncodedBytes);
 0682            BufferIndexedHeader(H3StaticTable.SchemeHttps);
 683
 0684            if (request.HasHeaders && request.Headers.Host is string host)
 0685            {
 0686                BufferLiteralHeaderWithStaticNameReference(H3StaticTable.Authority, host);
 0687            }
 688            else
 0689            {
 0690                BufferBytes(_connection.Pool._http3EncodedAuthorityHostHeader);
 0691            }
 692
 0693            Debug.Assert(request.RequestUri != null);
 0694            string pathAndQuery = request.RequestUri.PathAndQuery;
 0695            if (pathAndQuery == "/")
 0696            {
 0697                BufferIndexedHeader(H3StaticTable.PathSlash);
 0698            }
 699            else
 0700            {
 0701                BufferLiteralHeaderWithStaticNameReference(H3StaticTable.PathSlash, pathAndQuery);
 0702            }
 703
 704            // The only way to reach H3 is to upgrade via an Alt-Svc header, so we can encode Alt-Used for every connect
 0705            BufferBytes(_connection.AltUsedEncodedHeaderBytes);
 706
 0707            int headerListSize = 4 * HeaderField.RfcOverhead; // Scheme, Method, Authority, Path
 708
 0709            if (request.HasHeaders)
 0710            {
 711                // H3 does not support Transfer-Encoding: chunked.
 0712                if (request.HasHeaders && request.Headers.TransferEncodingChunked == true)
 0713                {
 0714                    request.Headers.TransferEncodingChunked = false;
 0715                }
 716
 0717                headerListSize += BufferHeaderCollection(request.Headers);
 0718            }
 719
 0720            if (_connection.Pool.Settings._useCookies)
 0721            {
 0722                string cookiesFromContainer = _connection.Pool.Settings._cookieContainer!.GetCookieHeader(request.Reques
 0723                if (cookiesFromContainer != string.Empty)
 0724                {
 0725                    Encoding? valueEncoding = _connection.Pool.Settings._requestHeaderEncodingSelector?.Invoke(HttpKnown
 0726                    BufferLiteralHeaderWithStaticNameReference(H3StaticTable.Cookie, cookiesFromContainer, valueEncoding
 0727                    headerListSize += HttpKnownHeaderNames.Cookie.Length + HeaderField.RfcOverhead;
 0728                }
 0729            }
 730
 0731            if (request.Content == null)
 0732            {
 0733                if (request.Method.MustHaveRequestBody)
 0734                {
 0735                    BufferIndexedHeader(H3StaticTable.ContentLength0);
 0736                    headerListSize += HttpKnownHeaderNames.ContentLength.Length + HeaderField.RfcOverhead;
 0737                }
 0738            }
 739            else
 0740            {
 0741                headerListSize += BufferHeaderCollection(request.Content.Headers);
 0742            }
 743
 744            // Determine our header envelope size.
 745            // The reserved space was the maximum required; discard what wasn't used.
 0746            int headersLength = _sendBuffer.ActiveLength - PreHeadersReserveSpace;
 0747            int headersLengthEncodedSize = VariableLengthIntegerHelper.GetByteCount(headersLength);
 0748            _sendBuffer.Discard(PreHeadersReserveSpace - headersLengthEncodedSize - 1);
 749
 750            // Encode header type in first byte, and payload length in subsequent bytes.
 0751            _sendBuffer.ActiveSpan[0] = (byte)Http3FrameType.Headers;
 0752            int actualHeadersLengthEncodedSize = VariableLengthIntegerHelper.WriteInteger(_sendBuffer.ActiveSpan.Slice(1
 0753            Debug.Assert(actualHeadersLengthEncodedSize == headersLengthEncodedSize);
 754
 755            // The headerListSize is an approximation of the total header length.
 756            // This is acceptable as long as the value is always >= the actual length.
 757            // We must avoid ever sending more than the server allowed.
 758            // This approach must be revisted if we ever support the dynamic table or compression when sending requests.
 0759            headerListSize += headersLength;
 760
 0761            uint maxHeaderListSize = _connection.MaxHeaderListSize;
 0762            if ((uint)headerListSize > maxHeaderListSize)
 0763            {
 0764                throw new HttpRequestException(SR.Format(SR.net_http_request_headers_exceeded_length, maxHeaderListSize)
 765            }
 766
 0767            if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestHeadersStop();
 0768        }
 769
 770        // TODO: special-case Content-Type for static table values values?
 771        private int BufferHeaderCollection(HttpHeaders headers)
 0772        {
 0773            HeaderEncodingSelector<HttpRequestMessage>? encodingSelector = _connection.Pool.Settings._requestHeaderEncod
 774
 0775            ReadOnlySpan<HeaderEntry> entries = headers.GetEntries();
 0776            int headerListSize = entries.Length * HeaderField.RfcOverhead;
 777
 0778            foreach (HeaderEntry header in entries)
 0779            {
 0780                int headerValuesCount = HttpHeaders.GetStoreValuesIntoStringArray(header.Key, header.Value, ref _headerV
 0781                Debug.Assert(headerValuesCount > 0, "No values for header??");
 0782                ReadOnlySpan<string> headerValues = _headerValues.AsSpan(0, headerValuesCount);
 783
 0784                Encoding? valueEncoding = encodingSelector?.Invoke(header.Key.Name, _request);
 785
 0786                KnownHeader? knownHeader = header.Key.KnownHeader;
 0787                if (knownHeader != null)
 0788                {
 789                    // The Host header is not sent for HTTP/3 because we send the ":authority" pseudo-header instead
 790                    // (see pseudo-header handling below in WriteHeaders).
 791                    // The Connection, Upgrade and ProxyConnection headers are also not supported in HTTP/3.
 0792                    if (knownHeader != KnownHeaders.Host && knownHeader != KnownHeaders.Connection && knownHeader != Kno
 0793                    {
 794                        // The length of the encoded name may be shorter than the actual name.
 795                        // Ensure that headerListSize is always >= of the actual size.
 0796                        headerListSize += knownHeader.Name.Length;
 797
 0798                        if (knownHeader == KnownHeaders.TE)
 0799                        {
 800                            // HTTP/2 allows only 'trailers' TE header. rfc7540 8.1.2.2
 801                            // HTTP/3 does not mention this one way or another; assume it has the same rule.
 0802                            foreach (string value in headerValues)
 0803                            {
 0804                                if (string.Equals(value, "trailers", StringComparison.OrdinalIgnoreCase))
 0805                                {
 0806                                    BufferLiteralHeaderWithoutNameReference("TE", value, valueEncoding);
 0807                                    break;
 808                                }
 0809                            }
 0810                            continue;
 811                        }
 812
 813                        // For all other known headers, send them via their pre-encoded name and the associated value.
 0814                        BufferBytes(knownHeader.Http3EncodedName);
 815
 0816                        byte[]? separator = headerValues.Length > 1 ? header.Key.SeparatorBytes : null;
 817
 0818                        BufferLiteralHeaderValues(headerValues, separator, valueEncoding);
 0819                    }
 0820                }
 821                else
 0822                {
 823                    // The header is not known: fall back to just encoding the header name and value(s).
 0824                    BufferLiteralHeaderWithoutNameReference(header.Key.Name, headerValues, HttpHeaderParser.DefaultSepar
 0825                }
 0826            }
 827
 0828            return headerListSize;
 0829        }
 830
 831        private void BufferIndexedHeader(int index)
 0832        {
 833            int bytesWritten;
 0834            while (!QPackEncoder.EncodeStaticIndexedHeaderField(index, _sendBuffer.AvailableSpan, out bytesWritten))
 0835            {
 0836                _sendBuffer.Grow();
 0837            }
 0838            _sendBuffer.Commit(bytesWritten);
 0839        }
 840
 841        private void BufferLiteralHeaderWithStaticNameReference(int nameIndex, string value, Encoding? valueEncoding = n
 0842        {
 843            int bytesWritten;
 0844            while (!QPackEncoder.EncodeLiteralHeaderFieldWithStaticNameReference(nameIndex, value, valueEncoding, _sendB
 0845            {
 0846                _sendBuffer.Grow();
 0847            }
 0848            _sendBuffer.Commit(bytesWritten);
 0849        }
 850
 851        private void BufferLiteralHeaderWithoutNameReference(string name, ReadOnlySpan<string> values, byte[] separator,
 0852        {
 853            int bytesWritten;
 0854            while (!QPackEncoder.EncodeLiteralHeaderFieldWithoutNameReference(name, values, separator, valueEncoding, _s
 0855            {
 0856                _sendBuffer.Grow();
 0857            }
 0858            _sendBuffer.Commit(bytesWritten);
 0859        }
 860
 861        private void BufferLiteralHeaderWithoutNameReference(string name, string value, Encoding? valueEncoding)
 0862        {
 863            int bytesWritten;
 0864            while (!QPackEncoder.EncodeLiteralHeaderFieldWithoutNameReference(name, value, valueEncoding, _sendBuffer.Av
 0865            {
 0866                _sendBuffer.Grow();
 0867            }
 0868            _sendBuffer.Commit(bytesWritten);
 0869        }
 870
 871        private void BufferLiteralHeaderValues(ReadOnlySpan<string> values, byte[]? separator, Encoding? valueEncoding)
 0872        {
 873            int bytesWritten;
 0874            while (!QPackEncoder.EncodeValueString(values, separator, valueEncoding, _sendBuffer.AvailableSpan, out byte
 0875            {
 0876                _sendBuffer.Grow();
 0877            }
 0878            _sendBuffer.Commit(bytesWritten);
 0879        }
 880
 881        private void BufferFrameEnvelope(Http3FrameType frameType, long payloadLength)
 0882        {
 883            int bytesWritten;
 0884            while (!Http3Frame.TryWriteFrameEnvelope(frameType, payloadLength, _sendBuffer.AvailableSpan, out bytesWritt
 0885            {
 0886                _sendBuffer.Grow();
 0887            }
 0888            _sendBuffer.Commit(bytesWritten);
 0889        }
 890
 891        private void BufferBytes(ReadOnlySpan<byte> span)
 0892        {
 0893            _sendBuffer.EnsureAvailableSpace(span.Length);
 0894            span.CopyTo(_sendBuffer.AvailableSpan);
 0895            _sendBuffer.Commit(span.Length);
 0896        }
 897
 898        private async ValueTask<(Http3FrameType? frameType, long payloadLength)> ReadFrameEnvelopeAsync(CancellationToke
 0899        {
 900            long frameType, payloadLength;
 901            int bytesRead;
 902
 0903            while (true)
 0904            {
 0905                while (!Http3Frame.TryReadIntegerPair(_recvBuffer.ActiveSpan, out frameType, out payloadLength, out byte
 0906                {
 0907                    _recvBuffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength * 2);
 0908                    bytesRead = await _stream.ReadAsync(_recvBuffer.AvailableMemory, cancellationToken).ConfigureAwait(f
 909
 0910                    if (bytesRead != 0)
 0911                    {
 0912                        _recvBuffer.Commit(bytesRead);
 0913                    }
 0914                    else if (_recvBuffer.ActiveLength == 0)
 0915                    {
 916                        // End of stream.
 0917                        return (null, 0);
 918                    }
 919                    else
 0920                    {
 921                        // Our buffer has partial frame data in it but not enough to complete the read: bail out.
 0922                        throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature
 923                    }
 0924                }
 925
 0926                _recvBuffer.Discard(bytesRead);
 927
 0928                if (NetEventSource.Log.IsEnabled())
 0929                {
 0930                    Trace($"Received frame {frameType} of length {payloadLength}.");
 0931                }
 932
 0933                switch ((Http3FrameType)frameType)
 934                {
 935                    case Http3FrameType.Headers:
 936                    case Http3FrameType.Data:
 0937                        return ((Http3FrameType)frameType, payloadLength);
 938                    case Http3FrameType.Settings: // These frames should only be received on a control stream, not a res
 939                    case Http3FrameType.GoAway:
 940                    case Http3FrameType.MaxPushId:
 941                    case Http3FrameType.ReservedHttp2Priority: // These frames are explicitly reserved and must never be
 942                    case Http3FrameType.ReservedHttp2Ping:
 943                    case Http3FrameType.ReservedHttp2WindowUpdate:
 944                    case Http3FrameType.ReservedHttp2Continuation:
 0945                        throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.UnexpectedFrame);
 946                    case Http3FrameType.PushPromise:
 947                    case Http3FrameType.CancelPush:
 948                        // Because we haven't sent any MAX_PUSH_ID frames, any of these push-related
 949                        // frames that the server sends will have an out-of-range push ID.
 0950                        throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.IdError);
 951                    default:
 952                        // Unknown frame types should be skipped.
 0953                        await SkipUnknownPayloadAsync(payloadLength, cancellationToken).ConfigureAwait(false);
 0954                        break;
 955                }
 0956            }
 0957        }
 958
 959        private async ValueTask ReadHeadersAsync(long headersLength, CancellationToken cancellationToken)
 0960        {
 0961            while (headersLength != 0)
 0962            {
 0963                if (_recvBuffer.ActiveLength == 0)
 0964                {
 0965                    _recvBuffer.EnsureAvailableSpace(1);
 966
 0967                    int bytesRead = await _stream.ReadAsync(_recvBuffer.AvailableMemory, cancellationToken).ConfigureAwa
 0968                    if (bytesRead != 0)
 0969                    {
 0970                        _recvBuffer.Commit(bytesRead);
 0971                    }
 972                    else
 0973                    {
 0974                        if (NetEventSource.Log.IsEnabled()) Trace($"Server closed response stream before entire header p
 0975                        throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature
 976                    }
 0977                }
 978
 0979                int processLength = (int)Math.Min(headersLength, _recvBuffer.ActiveLength);
 0980                bool endHeaders = headersLength == processLength;
 981
 0982                _headerDecoder.Decode(_recvBuffer.ActiveSpan.Slice(0, processLength), endHeaders, this);
 0983                _recvBuffer.Discard(processLength);
 0984                headersLength -= processLength;
 0985            }
 986
 987            // Reset decoder state. Require because one decoder instance is reused to decode headers and trailers.
 0988            _headerDecoder.Reset();
 0989        }
 990
 991        void IHttpStreamHeadersHandler.OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
 0992        {
 0993            Debug.Assert(name.Length > 0);
 0994            if (!HeaderDescriptor.TryGet(name, out HeaderDescriptor descriptor))
 0995            {
 996                // Invalid header name
 0997                throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invalid_response_
 998            }
 0999            OnHeader(staticIndex: null, descriptor, staticValue: default, literalValue: value);
 01000        }
 1001
 1002        void IHttpStreamHeadersHandler.OnStaticIndexedHeader(int index)
 01003        {
 01004            GetStaticQPackHeader(index, out HeaderDescriptor descriptor, out string? knownValue);
 01005            OnHeader(index, descriptor, knownValue, literalValue: default);
 01006        }
 1007
 1008        void IHttpStreamHeadersHandler.OnStaticIndexedHeader(int index, ReadOnlySpan<byte> value)
 01009        {
 01010            GetStaticQPackHeader(index, out HeaderDescriptor descriptor, knownValue: out _);
 01011            OnHeader(index, descriptor, staticValue: null, literalValue: value);
 01012        }
 1013
 1014        void IHttpStreamHeadersHandler.OnDynamicIndexedHeader(int? index, ReadOnlySpan<byte> name, ReadOnlySpan<byte> va
 01015        {
 01016            ((IHttpStreamHeadersHandler)this).OnHeader(name, value);
 01017        }
 1018
 1019        private void GetStaticQPackHeader(int index, out HeaderDescriptor descriptor, out string? knownValue)
 01020        {
 01021            if (!HeaderDescriptor.TryGetStaticQPackHeader(index, out descriptor, out knownValue))
 01022            {
 01023                if (NetEventSource.Log.IsEnabled()) Trace($"Response contains invalid static header index '{index}'.");
 01024                throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ProtocolError);
 1025            }
 01026        }
 1027
 1028        /// <param name="staticIndex">The static index of the header, if any.</param>
 1029        /// <param name="descriptor">A descriptor for either a known header or unknown header.</param>
 1030        /// <param name="staticValue">The static indexed value, if any.</param>
 1031        /// <param name="literalValue">The literal ASCII value, if any.</param>
 1032        /// <remarks>One of <paramref name="staticValue"/> or <paramref name="literalValue"/> will be set.</remarks>
 1033        private void OnHeader(int? staticIndex, HeaderDescriptor descriptor, string? staticValue, ReadOnlySpan<byte> lit
 01034        {
 01035            _headerBudgetRemaining -= descriptor.Name.Length + (staticValue?.Length ?? literalValue.Length);
 01036            if (_headerBudgetRemaining < 0)
 01037            {
 01038                _stream.Abort(QuicAbortDirection.Read, (long)Http3ErrorCode.ExcessiveLoad);
 01039                throw new HttpRequestException(HttpRequestError.ConfigurationLimitExceeded, SR.Format(SR.net_http_respon
 1040            }
 1041
 01042            if (descriptor.Name[0] == ':')
 01043            {
 01044                if (!descriptor.Equals(KnownHeaders.PseudoStatus))
 01045                {
 01046                    if (NetEventSource.Log.IsEnabled()) Trace($"Received unknown pseudo-header '{descriptor.Name}'.");
 01047                    throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ProtocolError);
 1048                }
 1049
 01050                if (_headerState != HeaderState.StatusHeader)
 01051                {
 01052                    if (NetEventSource.Log.IsEnabled()) Trace("Received extra status header.");
 01053                    throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ProtocolError);
 1054                }
 1055
 1056                int statusCode;
 01057                if (staticValue != null) // Indexed Header Field -- both name and value are taken from the table
 01058                {
 01059                    statusCode = staticIndex switch
 01060                    {
 01061                        H3StaticTable.Status103 => 103,
 01062                        H3StaticTable.Status200 => 200,
 01063                        H3StaticTable.Status304 => 304,
 01064                        H3StaticTable.Status404 => 404,
 01065                        H3StaticTable.Status503 => 503,
 01066                        H3StaticTable.Status100 => 100,
 01067                        H3StaticTable.Status204 => 204,
 01068                        H3StaticTable.Status206 => 206,
 01069                        H3StaticTable.Status302 => 302,
 01070                        H3StaticTable.Status400 => 400,
 01071                        H3StaticTable.Status403 => 403,
 01072                        H3StaticTable.Status421 => 421,
 01073                        H3StaticTable.Status425 => 425,
 01074                        H3StaticTable.Status500 => 500,
 01075                        // We should never get here, at least while we only use static table. But we can still parse sta
 01076                        _ => ParseStatusCode(staticIndex, staticValue)
 01077                    };
 1078
 1079                    int ParseStatusCode(int? index, string value)
 01080                    {
 01081                        string message = $"Unexpected QPACK table reference for Status code: index={index} value=\'{valu
 01082                        Debug.Fail(message);
 1083                        if (NetEventSource.Log.IsEnabled()) Trace(message);
 1084
 1085                        // TODO: The parsing is not optimal, but I don't expect this line to be executed at all for now.
 1086                        return HttpConnectionBase.ParseStatusCode(Encoding.ASCII.GetBytes(value));
 1087                    }
 01088                }
 1089                else // Literal Header Field With Name Reference -- only name is taken from the table
 01090                {
 01091                    statusCode = HttpConnectionBase.ParseStatusCode(literalValue);
 01092                }
 1093
 01094                _response = new HttpResponseMessage()
 01095                {
 01096                    Version = HttpVersion.Version30,
 01097                    RequestMessage = _request,
 01098                    Content = new HttpConnectionResponseContent(),
 01099                    StatusCode = (HttpStatusCode)statusCode
 01100                };
 1101
 01102                if (statusCode < 200)
 01103                {
 1104                    // Informational responses should not contain headers -- skip them.
 01105                    _headerState = HeaderState.SkipExpect100Headers;
 1106
 01107                    if (_response.StatusCode == HttpStatusCode.Continue && _expect100ContinueCompletionSource != null)
 01108                    {
 01109                        _expect100ContinueCompletionSource.TrySetResult(true);
 01110                    }
 01111                }
 1112                else
 01113                {
 01114                    _headerState = HeaderState.ResponseHeaders;
 01115                    if (_expect100ContinueCompletionSource != null)
 01116                    {
 1117                        // If the final status code is >= 300, skip sending the body.
 01118                        bool shouldSendBody = (statusCode < 300);
 1119
 01120                        if (NetEventSource.Log.IsEnabled()) Trace($"Expecting 100 Continue but received final status {st
 01121                        _expect100ContinueCompletionSource.TrySetResult(shouldSendBody);
 01122                    }
 01123                }
 01124            }
 01125            else if (_headerState == HeaderState.SkipExpect100Headers)
 01126            {
 1127                // Ignore any headers that came as part of an informational (i.e. 100 Continue) response.
 01128                return;
 1129            }
 1130            else
 01131            {
 01132                string? headerValue = staticValue;
 1133
 01134                if (headerValue is null)
 01135                {
 01136                    Encoding? encoding = _connection.Pool.Settings._responseHeaderEncodingSelector?.Invoke(descriptor.Na
 01137                    headerValue = _connection.GetResponseHeaderValueWithCaching(descriptor, literalValue, encoding);
 01138                }
 1139
 01140                switch (_headerState)
 1141                {
 1142                    case HeaderState.StatusHeader:
 01143                        if (NetEventSource.Log.IsEnabled()) Trace($"Received headers without :status.");
 01144                        throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ProtocolError);
 01145                    case HeaderState.ResponseHeaders when descriptor.HeaderType.HasFlag(HttpHeaderType.Content):
 01146                        _response!.Content!.Headers.TryAddWithoutValidation(descriptor, headerValue);
 01147                        break;
 1148                    case HeaderState.ResponseHeaders:
 01149                        _response!.Headers.TryAddWithoutValidation(descriptor.HeaderType.HasFlag(HttpHeaderType.Request)
 01150                        break;
 1151                    case HeaderState.TrailingHeaders:
 01152                        _trailingHeaders!.TryAddWithoutValidation(descriptor.HeaderType.HasFlag(HttpHeaderType.Request) 
 01153                        break;
 1154                    default:
 01155                        Debug.Fail($"Unexpected {nameof(Http3RequestStream)}.{nameof(_headerState)} '{_headerState}'.");
 1156                        break;
 1157                }
 01158            }
 01159        }
 1160
 1161        void IHttpStreamHeadersHandler.OnHeadersComplete(bool endStream)
 01162        {
 01163            Debug.Fail($"This has no use in HTTP/3 and should never be called by {nameof(QPackDecoder)}.");
 1164        }
 1165
 1166        private async ValueTask SkipUnknownPayloadAsync(long payloadLength, CancellationToken cancellationToken)
 01167        {
 01168            while (payloadLength != 0)
 01169            {
 01170                if (_recvBuffer.ActiveLength == 0)
 01171                {
 01172                    _recvBuffer.EnsureAvailableSpace(1);
 01173                    int bytesRead = await _stream.ReadAsync(_recvBuffer.AvailableMemory, cancellationToken).ConfigureAwa
 1174
 01175                    if (bytesRead != 0)
 01176                    {
 01177                        _recvBuffer.Commit(bytesRead);
 01178                    }
 1179                    else
 01180                    {
 1181                        // Our buffer has partial frame data in it but not enough to complete the read: bail out.
 01182                        throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.FrameError);
 1183                    }
 01184                }
 1185
 01186                long readLength = Math.Min(payloadLength, _recvBuffer.ActiveLength);
 01187                _recvBuffer.Discard((int)readLength);
 01188                payloadLength -= readLength;
 01189            }
 01190        }
 1191
 1192        private int ReadResponseContent(HttpResponseMessage response, Span<byte> buffer)
 01193        {
 1194            // Response headers should be done reading by the time this is called. _response is nulled out as part of th
 1195            // Verify that this is being called in correct order.
 01196            Debug.Assert(_response == null);
 1197
 1198            try
 01199            {
 01200                int totalBytesRead = 0;
 1201
 1202                do
 01203                {
 1204                    // Sync over async here -- QUIC implementation does it per-I/O already; this is at least more coarse
 01205                    if (_responseDataPayloadRemaining <= 0 && !ReadNextDataFrameAsync(response, CancellationToken.None).
 01206                    {
 1207                        // End of stream.
 01208                        _responseRecvCompleted = true;
 01209                        RemoveFromConnectionIfDone();
 01210                        break;
 1211                    }
 1212
 01213                    if (_recvBuffer.ActiveLength != 0)
 01214                    {
 1215                        // Some of the payload is in our receive buffer, so copy it.
 1216
 01217                        int copyLen = (int)Math.Min(buffer.Length, Math.Min(_responseDataPayloadRemaining, _recvBuffer.A
 01218                        _recvBuffer.ActiveSpan.Slice(0, copyLen).CopyTo(buffer);
 1219
 01220                        totalBytesRead += copyLen;
 01221                        _responseDataPayloadRemaining -= copyLen;
 01222                        _recvBuffer.Discard(copyLen);
 01223                        buffer = buffer.Slice(copyLen);
 1224
 1225                        // Stop, if we've reached the end of a data frame and start of the next data frame is not buffer
 1226                        // Waiting for the next data frame may cause a hang, e.g. in echo scenario
 1227                        // TODO: this is inefficient if data is already available in transport
 01228                        if (_responseDataPayloadRemaining == 0 && _recvBuffer.ActiveLength == 0)
 01229                        {
 01230                            break;
 1231                        }
 01232                    }
 1233                    else
 01234                    {
 1235                        // Receive buffer is empty -- bypass it and read directly into user's buffer.
 1236
 01237                        int copyLen = (int)Math.Min(buffer.Length, _responseDataPayloadRemaining);
 01238                        int bytesRead = _stream.Read(buffer.Slice(0, copyLen));
 1239
 01240                        if (bytesRead == 0 && buffer.Length != 0)
 01241                        {
 01242                            throw new HttpIOException(HttpRequestError.ResponseEnded, SR.Format(SR.net_http_invalid_resp
 1243                        }
 1244
 01245                        totalBytesRead += bytesRead;
 01246                        _responseDataPayloadRemaining -= bytesRead;
 01247                        buffer = buffer.Slice(bytesRead);
 1248
 1249                        // Stop, even if we are in the middle of a data frame. Waiting for the next data may cause a han
 1250                        // TODO: this is inefficient if data is already available in transport
 01251                        break;
 1252                    }
 01253                }
 01254                while (buffer.Length != 0);
 1255
 01256                return totalBytesRead;
 1257            }
 01258            catch (Exception ex)
 01259            {
 01260                HandleReadResponseContentException(ex, CancellationToken.None);
 1261                return 0; // never reached.
 1262            }
 01263        }
 1264
 1265        private async ValueTask<int> ReadResponseContentAsync(HttpResponseMessage response, Memory<byte> buffer, Cancell
 01266        {
 1267            // Response headers should be done reading by the time this is called. _response is nulled out as part of th
 1268            // Verify that this is being called in correct order.
 01269            Debug.Assert(_response == null);
 1270
 1271            try
 01272            {
 01273                int totalBytesRead = 0;
 1274
 1275                do
 01276                {
 01277                    if (_responseDataPayloadRemaining <= 0 && !await ReadNextDataFrameAsync(response, cancellationToken)
 01278                    {
 1279                        // End of stream.
 01280                        _responseRecvCompleted = true;
 01281                        RemoveFromConnectionIfDone();
 01282                        break;
 1283                    }
 1284
 01285                    if (_recvBuffer.ActiveLength != 0)
 01286                    {
 1287                        // Some of the payload is in our receive buffer, so copy it.
 1288
 01289                        int copyLen = (int)Math.Min(buffer.Length, Math.Min(_responseDataPayloadRemaining, _recvBuffer.A
 01290                        _recvBuffer.ActiveSpan.Slice(0, copyLen).CopyTo(buffer.Span);
 1291
 01292                        totalBytesRead += copyLen;
 01293                        _responseDataPayloadRemaining -= copyLen;
 01294                        _recvBuffer.Discard(copyLen);
 01295                        buffer = buffer.Slice(copyLen);
 1296
 1297                        // Stop, if we've reached the end of a data frame and start of the next data frame is not buffer
 1298                        // Waiting for the next data frame may cause a hang, e.g. in echo scenario
 1299                        // TODO: this is inefficient if data is already available in transport
 01300                        if (_responseDataPayloadRemaining == 0 && _recvBuffer.ActiveLength == 0)
 01301                        {
 01302                            break;
 1303                        }
 01304                    }
 1305                    else
 01306                    {
 1307                        // Receive buffer is empty -- bypass it and read directly into user's buffer.
 1308
 01309                        int copyLen = (int)Math.Min(buffer.Length, _responseDataPayloadRemaining);
 01310                        int bytesRead = await _stream.ReadAsync(buffer.Slice(0, copyLen), cancellationToken).ConfigureAw
 1311
 01312                        if (bytesRead == 0 && buffer.Length != 0)
 01313                        {
 01314                            throw new HttpIOException(HttpRequestError.ResponseEnded, SR.Format(SR.net_http_invalid_resp
 1315                        }
 1316
 01317                        totalBytesRead += bytesRead;
 01318                        _responseDataPayloadRemaining -= bytesRead;
 01319                        buffer = buffer.Slice(bytesRead);
 1320
 1321                        // Stop, even if we are in the middle of a data frame. Waiting for the next data may cause a han
 1322                        // TODO: this is inefficient if data is already available in transport
 01323                        break;
 1324                    }
 01325                }
 01326                while (buffer.Length != 0);
 1327
 01328                return totalBytesRead;
 1329            }
 01330            catch (Exception ex)
 01331            {
 01332                HandleReadResponseContentException(ex, cancellationToken);
 1333                return 0; // never reached.
 1334            }
 01335        }
 1336
 1337        [DoesNotReturn]
 1338        private void HandleReadResponseContentException(Exception ex, CancellationToken cancellationToken)
 01339        {
 1340            // The stream is, or is going to be aborted
 01341            _responseRecvCompleted = true;
 01342            RemoveFromConnectionIfDone();
 1343
 01344            switch (ex)
 1345            {
 01346                case QuicException e when (e.QuicError == QuicError.StreamAborted):
 1347                    // Peer aborted the stream
 01348                    Debug.Assert(e.ApplicationErrorCode.HasValue);
 01349                    throw HttpProtocolException.CreateHttp3StreamException((Http3ErrorCode)e.ApplicationErrorCode.Value,
 1350
 01351                case QuicException e when (e.QuicError == QuicError.ConnectionAborted):
 1352                    // Our connection was reset. Start aborting the connection.
 01353                    Debug.Assert(e.ApplicationErrorCode.HasValue);
 01354                    HttpProtocolException exception = HttpProtocolException.CreateHttp3ConnectionException((Http3ErrorCo
 01355                    _connection.Abort(exception);
 01356                    throw exception;
 1357
 01358                case QuicException e when (e.QuicError == QuicError.OperationAborted && _connection.AbortException != nu
 1359                    // we closed the connection already, propagate the AbortException
 01360                    HttpRequestError httpRequestError = _connection.AbortException is HttpProtocolException
 01361                        ? HttpRequestError.HttpProtocolError
 01362                        : HttpRequestError.Unknown;
 01363                    throw new HttpRequestException(httpRequestError, SR.net_http_client_execution_error, _connection.Abo
 1364
 01365                case QuicException e when (e.QuicError == QuicError.OperationAborted && cancellationToken.IsCancellation
 1366                    // It is possible for QuicStream's code to throw an
 1367                    // OperationAborted QuicException when cancellation is requested.
 01368                    throw new TaskCanceledException(e.Message, e, cancellationToken);
 1369
 1370                case HttpIOException:
 01371                    _connection.Abort(ex);
 01372                    ExceptionDispatchInfo.Throw(ex); // Rethrow.
 1373                    return; // Never reached.
 1374
 01375                case OperationCanceledException oce when oce.CancellationToken == cancellationToken:
 01376                    _stream.Abort(QuicAbortDirection.Read, (long)Http3ErrorCode.RequestCancelled);
 01377                    ExceptionDispatchInfo.Throw(ex); // Rethrow.
 1378                    return; // Never reached.
 1379            }
 1380
 01381            _stream.Abort(QuicAbortDirection.Read, (long)Http3ErrorCode.InternalError);
 01382            throw new HttpIOException(HttpRequestError.Unknown, SR.net_http_client_execution_error, new HttpRequestExcep
 01383        }
 1384
 1385        private async ValueTask<bool> ReadNextDataFrameAsync(HttpResponseMessage response, CancellationToken cancellatio
 01386        {
 01387            if (_responseDataPayloadRemaining == -1)
 01388            {
 1389                // EOS -- this branch will only be taken if user calls Read again after EOS.
 01390                return false;
 1391            }
 1392
 1393            Http3FrameType? frameType;
 1394            long payloadLength;
 1395
 01396            while (true)
 01397            {
 01398                (frameType, payloadLength) = await ReadFrameEnvelopeAsync(cancellationToken).ConfigureAwait(false);
 1399
 01400                switch (frameType)
 1401                {
 1402                    case Http3FrameType.Data:
 1403                        // Ignore DATA frames with 0 length.
 01404                        if (payloadLength == 0)
 01405                        {
 01406                            continue;
 1407                        }
 01408                        _responseDataPayloadRemaining = payloadLength;
 01409                        return true;
 1410                    case Http3FrameType.Headers:
 1411                        // Pick up any trailing headers and stop processing.
 01412                        await ProcessTrailersAsync(payloadLength, cancellationToken).ConfigureAwait(false);
 1413
 01414                        goto case null;
 1415                    case null:
 1416                        // End of stream.
 01417                        CopyTrailersToResponseMessage(response);
 1418
 01419                        _responseDataPayloadRemaining = -1; // Set to -1 to indicate EOS.
 01420                        return false;
 1421                }
 01422            }
 01423        }
 1424
 1425        public void Trace(string message, [CallerMemberName] string? memberName = null) =>
 01426            _connection.Trace(StreamId, message, memberName);
 1427
 1428        private void AbortStream()
 01429        {
 1430            // If the request body isn't completed, cancel it now.
 01431            if (_requestContentLengthRemaining != 0) // 0 is used for the end of content writing, -1 is used for unknown
 01432            {
 01433                _stream.Abort(QuicAbortDirection.Write, (long)Http3ErrorCode.RequestCancelled);
 01434            }
 1435            // If the response body isn't completed, cancel it now.
 01436            if (_responseDataPayloadRemaining != -1) // -1 is used for EOF, 0 for consumed DATA frame payload before the
 01437            {
 01438                _stream.Abort(QuicAbortDirection.Read, (long)Http3ErrorCode.RequestCancelled);
 01439            }
 01440        }
 1441
 1442        // TODO: it may be possible for Http3RequestStream to implement Stream directly and avoid this allocation.
 1443        private sealed class Http3ReadStream : HttpBaseStream
 1444        {
 1445            private Http3RequestStream? _stream;
 1446            private HttpResponseMessage? _response;
 1447
 01448            public override bool CanRead => _stream != null;
 1449
 01450            public override bool CanWrite => false;
 1451
 01452            public Http3ReadStream(Http3RequestStream stream)
 01453            {
 01454                _stream = stream;
 01455                _response = stream._response;
 01456            }
 1457
 1458            ~Http3ReadStream()
 01459            {
 01460                Dispose(false);
 01461            }
 1462
 1463            protected override void Dispose(bool disposing)
 01464            {
 01465                Http3RequestStream? stream = Interlocked.Exchange(ref _stream, null);
 01466                if (stream is null)
 01467                {
 01468                    return;
 1469                }
 1470
 01471                if (disposing)
 01472                {
 1473                    // This will remove the stream from the connection properly.
 01474                    stream.Dispose();
 01475                }
 1476                else
 01477                {
 1478                    // We shouldn't be using a managed instance here, but don't have much choice -- we
 1479                    // need to remove the stream from the connection's GOAWAY collection and properly abort.
 01480                    stream.AbortStream();
 01481                    stream._connection.RemoveStream(stream._stream);
 01482                    stream._connection = null!;
 01483                }
 1484
 01485                _response = null;
 1486
 01487                base.Dispose(disposing);
 01488            }
 1489
 1490            public override async ValueTask DisposeAsync()
 01491            {
 01492                Http3RequestStream? stream = Interlocked.Exchange(ref _stream, null);
 01493                if (stream is null)
 01494                {
 01495                    return;
 1496                }
 1497
 01498                await stream.DisposeAsync().ConfigureAwait(false);
 1499
 01500                _response = null;
 1501
 01502                await base.DisposeAsync().ConfigureAwait(false);
 01503            }
 1504
 1505            public override int Read(Span<byte> buffer)
 01506            {
 01507                Http3RequestStream? stream = _stream;
 01508                ObjectDisposedException.ThrowIf(stream is null, this);
 1509
 01510                Debug.Assert(_response != null);
 01511                return stream.ReadResponseContent(_response, buffer);
 01512            }
 1513
 1514            public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken)
 01515            {
 01516                Http3RequestStream? stream = _stream;
 1517
 01518                if (stream is null)
 01519                {
 01520                    return ValueTask.FromException<int>(ExceptionDispatchInfo.SetCurrentStackTrace(new ObjectDisposedExc
 1521                }
 1522
 01523                Debug.Assert(_response != null);
 01524                return stream.ReadResponseContentAsync(_response, buffer, cancellationToken);
 01525            }
 1526
 1527            public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
 01528            {
 01529                throw new NotSupportedException();
 1530            }
 1531        }
 1532
 1533        // TODO: it may be possible for Http3RequestStream to implement Stream directly and avoid this allocation.
 1534        private sealed class Http3WriteStream : HttpBaseStream
 1535        {
 1536            private Http3RequestStream? _stream;
 1537
 01538            public long BytesWritten { get; private set; }
 1539
 01540            public override bool CanRead => false;
 1541
 01542            public override bool CanWrite => _stream != null;
 1543
 01544            public Http3WriteStream(Http3RequestStream stream)
 01545            {
 01546                _stream = stream;
 01547            }
 1548
 1549            protected override void Dispose(bool disposing)
 01550            {
 01551                _stream = null;
 01552                base.Dispose(disposing);
 01553            }
 1554
 1555            public override int Read(Span<byte> buffer)
 01556            {
 01557                throw new NotSupportedException();
 1558            }
 1559
 1560            public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken)
 01561            {
 01562                throw new NotSupportedException();
 1563            }
 1564
 1565            public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
 01566            {
 01567                BytesWritten += buffer.Length;
 1568
 01569                Http3RequestStream? stream = _stream;
 1570
 01571                if (stream is null)
 01572                {
 01573                    return ValueTask.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(new ObjectDisposedExceptio
 1574                }
 1575
 01576                return stream.WriteRequestContentAsync(buffer, cancellationToken);
 01577            }
 1578
 1579            public override Task FlushAsync(CancellationToken cancellationToken)
 01580            {
 01581                Http3RequestStream? stream = _stream;
 1582
 01583                if (stream is null)
 01584                {
 01585                    return Task.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(new ObjectDisposedException(nam
 1586                }
 1587
 01588                return stream.FlushSendBufferAsync(endStream: false, cancellationToken).AsTask();
 01589            }
 1590        }
 1591
 1592        private enum HeaderState
 1593        {
 1594            StatusHeader,
 1595            SkipExpect100Headers,
 1596            ResponseHeaders,
 1597            TrailingHeaders
 1598        }
 1599    }
 1600}
 1601

Methods/Properties

.ctor(System.Net.Http.HttpRequestMessage,System.Net.Http.Http3Connection,System.Net.Quic.QuicStream)
StreamId()
StreamId(System.Int64)
Dispose()
RemoveFromConnectionIfDone()
DisposeAsync()
DisposeSyncHelper()
GoAway()
SendAsync(System.Threading.CancellationToken)
ReadResponseAsync(System.Threading.CancellationToken)
SendContentAsync(System.Net.Http.HttpContent,System.Threading.CancellationToken)
WriteRequestContentAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
FlushSendBufferAsync(System.Boolean,System.Threading.CancellationToken)
DrainContentLength0Frames(System.Threading.CancellationToken)
ProcessTrailersAsync(System.Int64,System.Threading.CancellationToken)
CopyTrailersToResponseMessage(System.Net.Http.HttpResponseMessage)
BufferHeaders(System.Net.Http.HttpRequestMessage)
BufferHeaderCollection(System.Net.Http.Headers.HttpHeaders)
BufferIndexedHeader(System.Int32)
BufferLiteralHeaderWithStaticNameReference(System.Int32,System.String,System.Text.Encoding)
BufferLiteralHeaderWithoutNameReference(System.String,System.ReadOnlySpan`1<System.String>,System.Byte[],System.Text.Encoding)
BufferLiteralHeaderWithoutNameReference(System.String,System.String,System.Text.Encoding)
BufferLiteralHeaderValues(System.ReadOnlySpan`1<System.String>,System.Byte[],System.Text.Encoding)
BufferFrameEnvelope(System.Net.Http.Http3FrameType,System.Int64)
BufferBytes(System.ReadOnlySpan`1<System.Byte>)
ReadFrameEnvelopeAsync(System.Threading.CancellationToken)
ReadHeadersAsync(System.Int64,System.Threading.CancellationToken)
System.Net.Http.IHttpStreamHeadersHandler.OnHeader(System.ReadOnlySpan`1<System.Byte>,System.ReadOnlySpan`1<System.Byte>)
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>)
GetStaticQPackHeader(System.Int32,System.Net.Http.Headers.HeaderDescriptor&,System.String&)
OnHeader(System.Nullable`1<System.Int32>,System.Net.Http.Headers.HeaderDescriptor,System.String,System.ReadOnlySpan`1<System.Byte>)
ParseStatusCode(System.Nullable`1<System.Int32>,System.String)
System.Net.Http.IHttpStreamHeadersHandler.OnHeadersComplete(System.Boolean)
SkipUnknownPayloadAsync(System.Int64,System.Threading.CancellationToken)
ReadResponseContent(System.Net.Http.HttpResponseMessage,System.Span`1<System.Byte>)
ReadResponseContentAsync(System.Net.Http.HttpResponseMessage,System.Memory`1<System.Byte>,System.Threading.CancellationToken)
HandleReadResponseContentException(System.Exception,System.Threading.CancellationToken)
ReadNextDataFrameAsync(System.Net.Http.HttpResponseMessage,System.Threading.CancellationToken)
Trace(System.String,System.String)
AbortStream()
CanRead()
CanWrite()
.ctor(System.Net.Http.Http3RequestStream)
Finalize()
Dispose(System.Boolean)
DisposeAsync()
Read(System.Span`1<System.Byte>)
ReadAsync(System.Memory`1<System.Byte>,System.Threading.CancellationToken)
WriteAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
BytesWritten()
CanRead()
CanWrite()
.ctor(System.Net.Http.Http3RequestStream)
Dispose(System.Boolean)
Read(System.Span`1<System.Byte>)
ReadAsync(System.Memory`1<System.Byte>,System.Threading.CancellationToken)
WriteAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
FlushAsync(System.Threading.CancellationToken)