< Summary

Information
Class: System.Net.Http.Http3RequestStream
Assembly: System.Net.Http
File(s): D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\Http3RequestStream.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 897
Coverable lines: 897
Total lines: 1603
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 362
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%44440%
ReadResponseAsync()0%12120%
SendContentAsync()0%20200%
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%10100%
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%59590%
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)

D:\runner\runtime\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.Discard(_sendBuffer.ActiveLength);
 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.Discard(_sendBuffer.ActiveLength);
 0581            }
 0582        }
 583
 584        private ValueTask FlushSendBufferAsync(bool endStream, CancellationToken cancellationToken)
 0585        {
 0586            ReadOnlyMemory<byte> toSend = _sendBuffer.ActiveMemory;
 0587            _sendBuffer.Discard(toSend.Length);
 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        {
 961            // TODO: this header budget is sent as SETTINGS_MAX_HEADER_LIST_SIZE, so it should not use frame payload but
 962            // https://tools.ietf.org/html/draft-ietf-quic-http-24#section-4.1.1
 0963            if (headersLength > _headerBudgetRemaining)
 0964            {
 0965                _stream.Abort(QuicAbortDirection.Read, (long)Http3ErrorCode.ExcessiveLoad);
 0966                throw new HttpRequestException(HttpRequestError.ConfigurationLimitExceeded, SR.Format(SR.net_http_respon
 967            }
 968
 0969            _headerBudgetRemaining -= (int)headersLength;
 970
 0971            while (headersLength != 0)
 0972            {
 0973                if (_recvBuffer.ActiveLength == 0)
 0974                {
 0975                    _recvBuffer.EnsureAvailableSpace(1);
 976
 0977                    int bytesRead = await _stream.ReadAsync(_recvBuffer.AvailableMemory, cancellationToken).ConfigureAwa
 0978                    if (bytesRead != 0)
 0979                    {
 0980                        _recvBuffer.Commit(bytesRead);
 0981                    }
 982                    else
 0983                    {
 0984                        if (NetEventSource.Log.IsEnabled()) Trace($"Server closed response stream before entire header p
 0985                        throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature
 986                    }
 0987                }
 988
 0989                int processLength = (int)Math.Min(headersLength, _recvBuffer.ActiveLength);
 0990                bool endHeaders = headersLength == processLength;
 991
 0992                _headerDecoder.Decode(_recvBuffer.ActiveSpan.Slice(0, processLength), endHeaders, this);
 0993                _recvBuffer.Discard(processLength);
 0994                headersLength -= processLength;
 0995            }
 996
 997            // Reset decoder state. Require because one decoder instance is reused to decode headers and trailers.
 0998            _headerDecoder.Reset();
 0999        }
 1000
 1001        void IHttpStreamHeadersHandler.OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
 01002        {
 01003            Debug.Assert(name.Length > 0);
 01004            if (!HeaderDescriptor.TryGet(name, out HeaderDescriptor descriptor))
 01005            {
 1006                // Invalid header name
 01007                throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invalid_response_
 1008            }
 01009            OnHeader(staticIndex: null, descriptor, staticValue: default, literalValue: value);
 01010        }
 1011
 1012        void IHttpStreamHeadersHandler.OnStaticIndexedHeader(int index)
 01013        {
 01014            GetStaticQPackHeader(index, out HeaderDescriptor descriptor, out string? knownValue);
 01015            OnHeader(index, descriptor, knownValue, literalValue: default);
 01016        }
 1017
 1018        void IHttpStreamHeadersHandler.OnStaticIndexedHeader(int index, ReadOnlySpan<byte> value)
 01019        {
 01020            GetStaticQPackHeader(index, out HeaderDescriptor descriptor, knownValue: out _);
 01021            OnHeader(index, descriptor, staticValue: null, literalValue: value);
 01022        }
 1023
 1024        void IHttpStreamHeadersHandler.OnDynamicIndexedHeader(int? index, ReadOnlySpan<byte> name, ReadOnlySpan<byte> va
 01025        {
 01026            ((IHttpStreamHeadersHandler)this).OnHeader(name, value);
 01027        }
 1028
 1029        private void GetStaticQPackHeader(int index, out HeaderDescriptor descriptor, out string? knownValue)
 01030        {
 01031            if (!HeaderDescriptor.TryGetStaticQPackHeader(index, out descriptor, out knownValue))
 01032            {
 01033                if (NetEventSource.Log.IsEnabled()) Trace($"Response contains invalid static header index '{index}'.");
 01034                throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ProtocolError);
 1035            }
 01036        }
 1037
 1038        /// <param name="staticIndex">The static index of the header, if any.</param>
 1039        /// <param name="descriptor">A descriptor for either a known header or unknown header.</param>
 1040        /// <param name="staticValue">The static indexed value, if any.</param>
 1041        /// <param name="literalValue">The literal ASCII value, if any.</param>
 1042        /// <remarks>One of <paramref name="staticValue"/> or <paramref name="literalValue"/> will be set.</remarks>
 1043        private void OnHeader(int? staticIndex, HeaderDescriptor descriptor, string? staticValue, ReadOnlySpan<byte> lit
 01044        {
 01045            if (descriptor.Name[0] == ':')
 01046            {
 01047                if (!descriptor.Equals(KnownHeaders.PseudoStatus))
 01048                {
 01049                    if (NetEventSource.Log.IsEnabled()) Trace($"Received unknown pseudo-header '{descriptor.Name}'.");
 01050                    throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ProtocolError);
 1051                }
 1052
 01053                if (_headerState != HeaderState.StatusHeader)
 01054                {
 01055                    if (NetEventSource.Log.IsEnabled()) Trace("Received extra status header.");
 01056                    throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ProtocolError);
 1057                }
 1058
 1059                int statusCode;
 01060                if (staticValue != null) // Indexed Header Field -- both name and value are taken from the table
 01061                {
 01062                    statusCode = staticIndex switch
 01063                    {
 01064                        H3StaticTable.Status103 => 103,
 01065                        H3StaticTable.Status200 => 200,
 01066                        H3StaticTable.Status304 => 304,
 01067                        H3StaticTable.Status404 => 404,
 01068                        H3StaticTable.Status503 => 503,
 01069                        H3StaticTable.Status100 => 100,
 01070                        H3StaticTable.Status204 => 204,
 01071                        H3StaticTable.Status206 => 206,
 01072                        H3StaticTable.Status302 => 302,
 01073                        H3StaticTable.Status400 => 400,
 01074                        H3StaticTable.Status403 => 403,
 01075                        H3StaticTable.Status421 => 421,
 01076                        H3StaticTable.Status425 => 425,
 01077                        H3StaticTable.Status500 => 500,
 01078                        // We should never get here, at least while we only use static table. But we can still parse sta
 01079                        _ => ParseStatusCode(staticIndex, staticValue)
 01080                    };
 1081
 1082                    int ParseStatusCode(int? index, string value)
 01083                    {
 01084                        string message = $"Unexpected QPACK table reference for Status code: index={index} value=\'{valu
 01085                        Debug.Fail(message);
 1086                        if (NetEventSource.Log.IsEnabled()) Trace(message);
 1087
 1088                        // TODO: The parsing is not optimal, but I don't expect this line to be executed at all for now.
 1089                        return HttpConnectionBase.ParseStatusCode(Encoding.ASCII.GetBytes(value));
 1090                    }
 01091                }
 1092                else // Literal Header Field With Name Reference -- only name is taken from the table
 01093                {
 01094                    statusCode = HttpConnectionBase.ParseStatusCode(literalValue);
 01095                }
 1096
 01097                _response = new HttpResponseMessage()
 01098                {
 01099                    Version = HttpVersion.Version30,
 01100                    RequestMessage = _request,
 01101                    Content = new HttpConnectionResponseContent(),
 01102                    StatusCode = (HttpStatusCode)statusCode
 01103                };
 1104
 01105                if (statusCode < 200)
 01106                {
 1107                    // Informational responses should not contain headers -- skip them.
 01108                    _headerState = HeaderState.SkipExpect100Headers;
 1109
 01110                    if (_response.StatusCode == HttpStatusCode.Continue && _expect100ContinueCompletionSource != null)
 01111                    {
 01112                        _expect100ContinueCompletionSource.TrySetResult(true);
 01113                    }
 01114                }
 1115                else
 01116                {
 01117                    _headerState = HeaderState.ResponseHeaders;
 01118                    if (_expect100ContinueCompletionSource != null)
 01119                    {
 1120                        // If the final status code is >= 300, skip sending the body.
 01121                        bool shouldSendBody = (statusCode < 300);
 1122
 01123                        if (NetEventSource.Log.IsEnabled()) Trace($"Expecting 100 Continue but received final status {st
 01124                        _expect100ContinueCompletionSource.TrySetResult(shouldSendBody);
 01125                    }
 01126                }
 01127            }
 01128            else if (_headerState == HeaderState.SkipExpect100Headers)
 01129            {
 1130                // Ignore any headers that came as part of an informational (i.e. 100 Continue) response.
 01131                return;
 1132            }
 1133            else
 01134            {
 01135                string? headerValue = staticValue;
 1136
 01137                if (headerValue is null)
 01138                {
 01139                    Encoding? encoding = _connection.Pool.Settings._responseHeaderEncodingSelector?.Invoke(descriptor.Na
 01140                    headerValue = _connection.GetResponseHeaderValueWithCaching(descriptor, literalValue, encoding);
 01141                }
 1142
 01143                switch (_headerState)
 1144                {
 1145                    case HeaderState.StatusHeader:
 01146                        if (NetEventSource.Log.IsEnabled()) Trace($"Received headers without :status.");
 01147                        throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ProtocolError);
 01148                    case HeaderState.ResponseHeaders when descriptor.HeaderType.HasFlag(HttpHeaderType.Content):
 01149                        _response!.Content!.Headers.TryAddWithoutValidation(descriptor, headerValue);
 01150                        break;
 1151                    case HeaderState.ResponseHeaders:
 01152                        _response!.Headers.TryAddWithoutValidation(descriptor.HeaderType.HasFlag(HttpHeaderType.Request)
 01153                        break;
 1154                    case HeaderState.TrailingHeaders:
 01155                        _trailingHeaders!.TryAddWithoutValidation(descriptor.HeaderType.HasFlag(HttpHeaderType.Request) 
 01156                        break;
 1157                    default:
 01158                        Debug.Fail($"Unexpected {nameof(Http3RequestStream)}.{nameof(_headerState)} '{_headerState}'.");
 1159                        break;
 1160                }
 01161            }
 01162        }
 1163
 1164        void IHttpStreamHeadersHandler.OnHeadersComplete(bool endStream)
 01165        {
 01166            Debug.Fail($"This has no use in HTTP/3 and should never be called by {nameof(QPackDecoder)}.");
 1167        }
 1168
 1169        private async ValueTask SkipUnknownPayloadAsync(long payloadLength, CancellationToken cancellationToken)
 01170        {
 01171            while (payloadLength != 0)
 01172            {
 01173                if (_recvBuffer.ActiveLength == 0)
 01174                {
 01175                    _recvBuffer.EnsureAvailableSpace(1);
 01176                    int bytesRead = await _stream.ReadAsync(_recvBuffer.AvailableMemory, cancellationToken).ConfigureAwa
 1177
 01178                    if (bytesRead != 0)
 01179                    {
 01180                        _recvBuffer.Commit(bytesRead);
 01181                    }
 1182                    else
 01183                    {
 1184                        // Our buffer has partial frame data in it but not enough to complete the read: bail out.
 01185                        throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.FrameError);
 1186                    }
 01187                }
 1188
 01189                long readLength = Math.Min(payloadLength, _recvBuffer.ActiveLength);
 01190                _recvBuffer.Discard((int)readLength);
 01191                payloadLength -= readLength;
 01192            }
 01193        }
 1194
 1195        private int ReadResponseContent(HttpResponseMessage response, Span<byte> buffer)
 01196        {
 1197            // Response headers should be done reading by the time this is called. _response is nulled out as part of th
 1198            // Verify that this is being called in correct order.
 01199            Debug.Assert(_response == null);
 1200
 1201            try
 01202            {
 01203                int totalBytesRead = 0;
 1204
 1205                do
 01206                {
 1207                    // Sync over async here -- QUIC implementation does it per-I/O already; this is at least more coarse
 01208                    if (_responseDataPayloadRemaining <= 0 && !ReadNextDataFrameAsync(response, CancellationToken.None).
 01209                    {
 1210                        // End of stream.
 01211                        _responseRecvCompleted = true;
 01212                        RemoveFromConnectionIfDone();
 01213                        break;
 1214                    }
 1215
 01216                    if (_recvBuffer.ActiveLength != 0)
 01217                    {
 1218                        // Some of the payload is in our receive buffer, so copy it.
 1219
 01220                        int copyLen = (int)Math.Min(buffer.Length, Math.Min(_responseDataPayloadRemaining, _recvBuffer.A
 01221                        _recvBuffer.ActiveSpan.Slice(0, copyLen).CopyTo(buffer);
 1222
 01223                        totalBytesRead += copyLen;
 01224                        _responseDataPayloadRemaining -= copyLen;
 01225                        _recvBuffer.Discard(copyLen);
 01226                        buffer = buffer.Slice(copyLen);
 1227
 1228                        // Stop, if we've reached the end of a data frame and start of the next data frame is not buffer
 1229                        // Waiting for the next data frame may cause a hang, e.g. in echo scenario
 1230                        // TODO: this is inefficient if data is already available in transport
 01231                        if (_responseDataPayloadRemaining == 0 && _recvBuffer.ActiveLength == 0)
 01232                        {
 01233                            break;
 1234                        }
 01235                    }
 1236                    else
 01237                    {
 1238                        // Receive buffer is empty -- bypass it and read directly into user's buffer.
 1239
 01240                        int copyLen = (int)Math.Min(buffer.Length, _responseDataPayloadRemaining);
 01241                        int bytesRead = _stream.Read(buffer.Slice(0, copyLen));
 1242
 01243                        if (bytesRead == 0 && buffer.Length != 0)
 01244                        {
 01245                            throw new HttpIOException(HttpRequestError.ResponseEnded, SR.Format(SR.net_http_invalid_resp
 1246                        }
 1247
 01248                        totalBytesRead += bytesRead;
 01249                        _responseDataPayloadRemaining -= bytesRead;
 01250                        buffer = buffer.Slice(bytesRead);
 1251
 1252                        // Stop, even if we are in the middle of a data frame. Waiting for the next data may cause a han
 1253                        // TODO: this is inefficient if data is already available in transport
 01254                        break;
 1255                    }
 01256                }
 01257                while (buffer.Length != 0);
 1258
 01259                return totalBytesRead;
 1260            }
 01261            catch (Exception ex)
 01262            {
 01263                HandleReadResponseContentException(ex, CancellationToken.None);
 1264                return 0; // never reached.
 1265            }
 01266        }
 1267
 1268        private async ValueTask<int> ReadResponseContentAsync(HttpResponseMessage response, Memory<byte> buffer, Cancell
 01269        {
 1270            // Response headers should be done reading by the time this is called. _response is nulled out as part of th
 1271            // Verify that this is being called in correct order.
 01272            Debug.Assert(_response == null);
 1273
 1274            try
 01275            {
 01276                int totalBytesRead = 0;
 1277
 1278                do
 01279                {
 01280                    if (_responseDataPayloadRemaining <= 0 && !await ReadNextDataFrameAsync(response, cancellationToken)
 01281                    {
 1282                        // End of stream.
 01283                        _responseRecvCompleted = true;
 01284                        RemoveFromConnectionIfDone();
 01285                        break;
 1286                    }
 1287
 01288                    if (_recvBuffer.ActiveLength != 0)
 01289                    {
 1290                        // Some of the payload is in our receive buffer, so copy it.
 1291
 01292                        int copyLen = (int)Math.Min(buffer.Length, Math.Min(_responseDataPayloadRemaining, _recvBuffer.A
 01293                        _recvBuffer.ActiveSpan.Slice(0, copyLen).CopyTo(buffer.Span);
 1294
 01295                        totalBytesRead += copyLen;
 01296                        _responseDataPayloadRemaining -= copyLen;
 01297                        _recvBuffer.Discard(copyLen);
 01298                        buffer = buffer.Slice(copyLen);
 1299
 1300                        // Stop, if we've reached the end of a data frame and start of the next data frame is not buffer
 1301                        // Waiting for the next data frame may cause a hang, e.g. in echo scenario
 1302                        // TODO: this is inefficient if data is already available in transport
 01303                        if (_responseDataPayloadRemaining == 0 && _recvBuffer.ActiveLength == 0)
 01304                        {
 01305                            break;
 1306                        }
 01307                    }
 1308                    else
 01309                    {
 1310                        // Receive buffer is empty -- bypass it and read directly into user's buffer.
 1311
 01312                        int copyLen = (int)Math.Min(buffer.Length, _responseDataPayloadRemaining);
 01313                        int bytesRead = await _stream.ReadAsync(buffer.Slice(0, copyLen), cancellationToken).ConfigureAw
 1314
 01315                        if (bytesRead == 0 && buffer.Length != 0)
 01316                        {
 01317                            throw new HttpIOException(HttpRequestError.ResponseEnded, SR.Format(SR.net_http_invalid_resp
 1318                        }
 1319
 01320                        totalBytesRead += bytesRead;
 01321                        _responseDataPayloadRemaining -= bytesRead;
 01322                        buffer = buffer.Slice(bytesRead);
 1323
 1324                        // Stop, even if we are in the middle of a data frame. Waiting for the next data may cause a han
 1325                        // TODO: this is inefficient if data is already available in transport
 01326                        break;
 1327                    }
 01328                }
 01329                while (buffer.Length != 0);
 1330
 01331                return totalBytesRead;
 1332            }
 01333            catch (Exception ex)
 01334            {
 01335                HandleReadResponseContentException(ex, cancellationToken);
 1336                return 0; // never reached.
 1337            }
 01338        }
 1339
 1340        [DoesNotReturn]
 1341        private void HandleReadResponseContentException(Exception ex, CancellationToken cancellationToken)
 01342        {
 1343            // The stream is, or is going to be aborted
 01344            _responseRecvCompleted = true;
 01345            RemoveFromConnectionIfDone();
 1346
 01347            switch (ex)
 1348            {
 01349                case QuicException e when (e.QuicError == QuicError.StreamAborted):
 1350                    // Peer aborted the stream
 01351                    Debug.Assert(e.ApplicationErrorCode.HasValue);
 01352                    throw HttpProtocolException.CreateHttp3StreamException((Http3ErrorCode)e.ApplicationErrorCode.Value,
 1353
 01354                case QuicException e when (e.QuicError == QuicError.ConnectionAborted):
 1355                    // Our connection was reset. Start aborting the connection.
 01356                    Debug.Assert(e.ApplicationErrorCode.HasValue);
 01357                    HttpProtocolException exception = HttpProtocolException.CreateHttp3ConnectionException((Http3ErrorCo
 01358                    _connection.Abort(exception);
 01359                    throw exception;
 1360
 01361                case QuicException e when (e.QuicError == QuicError.OperationAborted && _connection.AbortException != nu
 1362                    // we closed the connection already, propagate the AbortException
 01363                    HttpRequestError httpRequestError = _connection.AbortException is HttpProtocolException
 01364                        ? HttpRequestError.HttpProtocolError
 01365                        : HttpRequestError.Unknown;
 01366                    throw new HttpRequestException(httpRequestError, SR.net_http_client_execution_error, _connection.Abo
 1367
 01368                case QuicException e when (e.QuicError == QuicError.OperationAborted && cancellationToken.IsCancellation
 1369                    // It is possible for QuicStream's code to throw an
 1370                    // OperationAborted QuicException when cancellation is requested.
 01371                    throw new TaskCanceledException(e.Message, e, cancellationToken);
 1372
 1373                case HttpIOException:
 01374                    _connection.Abort(ex);
 01375                    ExceptionDispatchInfo.Throw(ex); // Rethrow.
 1376                    return; // Never reached.
 1377
 01378                case OperationCanceledException oce when oce.CancellationToken == cancellationToken:
 01379                    _stream.Abort(QuicAbortDirection.Read, (long)Http3ErrorCode.RequestCancelled);
 01380                    ExceptionDispatchInfo.Throw(ex); // Rethrow.
 1381                    return; // Never reached.
 1382            }
 1383
 01384            _stream.Abort(QuicAbortDirection.Read, (long)Http3ErrorCode.InternalError);
 01385            throw new HttpIOException(HttpRequestError.Unknown, SR.net_http_client_execution_error, new HttpRequestExcep
 01386        }
 1387
 1388        private async ValueTask<bool> ReadNextDataFrameAsync(HttpResponseMessage response, CancellationToken cancellatio
 01389        {
 01390            if (_responseDataPayloadRemaining == -1)
 01391            {
 1392                // EOS -- this branch will only be taken if user calls Read again after EOS.
 01393                return false;
 1394            }
 1395
 1396            Http3FrameType? frameType;
 1397            long payloadLength;
 1398
 01399            while (true)
 01400            {
 01401                (frameType, payloadLength) = await ReadFrameEnvelopeAsync(cancellationToken).ConfigureAwait(false);
 1402
 01403                switch (frameType)
 1404                {
 1405                    case Http3FrameType.Data:
 1406                        // Ignore DATA frames with 0 length.
 01407                        if (payloadLength == 0)
 01408                        {
 01409                            continue;
 1410                        }
 01411                        _responseDataPayloadRemaining = payloadLength;
 01412                        return true;
 1413                    case Http3FrameType.Headers:
 1414                        // Pick up any trailing headers and stop processing.
 01415                        await ProcessTrailersAsync(payloadLength, cancellationToken).ConfigureAwait(false);
 1416
 01417                        goto case null;
 1418                    case null:
 1419                        // End of stream.
 01420                        CopyTrailersToResponseMessage(response);
 1421
 01422                        _responseDataPayloadRemaining = -1; // Set to -1 to indicate EOS.
 01423                        return false;
 1424                }
 01425            }
 01426        }
 1427
 1428        public void Trace(string message, [CallerMemberName] string? memberName = null) =>
 01429            _connection.Trace(StreamId, message, memberName);
 1430
 1431        private void AbortStream()
 01432        {
 1433            // If the request body isn't completed, cancel it now.
 01434            if (_requestContentLengthRemaining != 0) // 0 is used for the end of content writing, -1 is used for unknown
 01435            {
 01436                _stream.Abort(QuicAbortDirection.Write, (long)Http3ErrorCode.RequestCancelled);
 01437            }
 1438            // If the response body isn't completed, cancel it now.
 01439            if (_responseDataPayloadRemaining != -1) // -1 is used for EOF, 0 for consumed DATA frame payload before the
 01440            {
 01441                _stream.Abort(QuicAbortDirection.Read, (long)Http3ErrorCode.RequestCancelled);
 01442            }
 01443        }
 1444
 1445        // TODO: it may be possible for Http3RequestStream to implement Stream directly and avoid this allocation.
 1446        private sealed class Http3ReadStream : HttpBaseStream
 1447        {
 1448            private Http3RequestStream? _stream;
 1449            private HttpResponseMessage? _response;
 1450
 01451            public override bool CanRead => _stream != null;
 1452
 01453            public override bool CanWrite => false;
 1454
 01455            public Http3ReadStream(Http3RequestStream stream)
 01456            {
 01457                _stream = stream;
 01458                _response = stream._response;
 01459            }
 1460
 1461            ~Http3ReadStream()
 01462            {
 01463                Dispose(false);
 01464            }
 1465
 1466            protected override void Dispose(bool disposing)
 01467            {
 01468                Http3RequestStream? stream = Interlocked.Exchange(ref _stream, null);
 01469                if (stream is null)
 01470                {
 01471                    return;
 1472                }
 1473
 01474                if (disposing)
 01475                {
 1476                    // This will remove the stream from the connection properly.
 01477                    stream.Dispose();
 01478                }
 1479                else
 01480                {
 1481                    // We shouldn't be using a managed instance here, but don't have much choice -- we
 1482                    // need to remove the stream from the connection's GOAWAY collection and properly abort.
 01483                    stream.AbortStream();
 01484                    stream._connection.RemoveStream(stream._stream);
 01485                    stream._connection = null!;
 01486                }
 1487
 01488                _response = null;
 1489
 01490                base.Dispose(disposing);
 01491            }
 1492
 1493            public override async ValueTask DisposeAsync()
 01494            {
 01495                Http3RequestStream? stream = Interlocked.Exchange(ref _stream, null);
 01496                if (stream is null)
 01497                {
 01498                    return;
 1499                }
 1500
 01501                await stream.DisposeAsync().ConfigureAwait(false);
 1502
 01503                _response = null;
 1504
 01505                await base.DisposeAsync().ConfigureAwait(false);
 01506            }
 1507
 1508            public override int Read(Span<byte> buffer)
 01509            {
 01510                Http3RequestStream? stream = _stream;
 01511                ObjectDisposedException.ThrowIf(stream is null, this);
 1512
 01513                Debug.Assert(_response != null);
 01514                return stream.ReadResponseContent(_response, buffer);
 01515            }
 1516
 1517            public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken)
 01518            {
 01519                Http3RequestStream? stream = _stream;
 1520
 01521                if (stream is null)
 01522                {
 01523                    return ValueTask.FromException<int>(ExceptionDispatchInfo.SetCurrentStackTrace(new ObjectDisposedExc
 1524                }
 1525
 01526                Debug.Assert(_response != null);
 01527                return stream.ReadResponseContentAsync(_response, buffer, cancellationToken);
 01528            }
 1529
 1530            public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
 01531            {
 01532                throw new NotSupportedException();
 1533            }
 1534        }
 1535
 1536        // TODO: it may be possible for Http3RequestStream to implement Stream directly and avoid this allocation.
 1537        private sealed class Http3WriteStream : HttpBaseStream
 1538        {
 1539            private Http3RequestStream? _stream;
 1540
 01541            public long BytesWritten { get; private set; }
 1542
 01543            public override bool CanRead => false;
 1544
 01545            public override bool CanWrite => _stream != null;
 1546
 01547            public Http3WriteStream(Http3RequestStream stream)
 01548            {
 01549                _stream = stream;
 01550            }
 1551
 1552            protected override void Dispose(bool disposing)
 01553            {
 01554                _stream = null;
 01555                base.Dispose(disposing);
 01556            }
 1557
 1558            public override int Read(Span<byte> buffer)
 01559            {
 01560                throw new NotSupportedException();
 1561            }
 1562
 1563            public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken)
 01564            {
 01565                throw new NotSupportedException();
 1566            }
 1567
 1568            public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
 01569            {
 01570                BytesWritten += buffer.Length;
 1571
 01572                Http3RequestStream? stream = _stream;
 1573
 01574                if (stream is null)
 01575                {
 01576                    return ValueTask.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(new ObjectDisposedExceptio
 1577                }
 1578
 01579                return stream.WriteRequestContentAsync(buffer, cancellationToken);
 01580            }
 1581
 1582            public override Task FlushAsync(CancellationToken cancellationToken)
 01583            {
 01584                Http3RequestStream? stream = _stream;
 1585
 01586                if (stream is null)
 01587                {
 01588                    return Task.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(new ObjectDisposedException(nam
 1589                }
 1590
 01591                return stream.FlushSendBufferAsync(endStream: false, cancellationToken).AsTask();
 01592            }
 1593        }
 1594
 1595        private enum HeaderState
 1596        {
 1597            StatusHeader,
 1598            SkipExpect100Headers,
 1599            ResponseHeaders,
 1600            TrailingHeaders
 1601        }
 1602    }
 1603}

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()
ReadResponseAsync()
SendContentAsync()
WriteRequestContentAsync()
FlushSendBufferAsync(System.Boolean,System.Threading.CancellationToken)
DrainContentLength0Frames()
ProcessTrailersAsync()
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()
ReadHeadersAsync()
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()
ReadResponseContent(System.Net.Http.HttpResponseMessage,System.Span`1<System.Byte>)
ReadResponseContentAsync()
HandleReadResponseContentException(System.Exception,System.Threading.CancellationToken)
ReadNextDataFrameAsync()
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)