public async Task Read_StreamAborted_Throws() { const int ExpectedErrorCode = 0xfffffff; await Task.Run(async() => { using QuicListener listener = CreateQuicListener(); ValueTask <QuicConnection> serverConnectionTask = listener.AcceptConnectionAsync(); using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint); await clientConnection.ConnectAsync(); using QuicConnection serverConnection = await serverConnectionTask; await using QuicStream clientStream = clientConnection.OpenBidirectionalStream(); await clientStream.WriteAsync(new byte[1]); await using QuicStream serverStream = await serverConnection.AcceptStreamAsync(); await serverStream.ReadAsync(new byte[1]); clientStream.AbortWrite(ExpectedErrorCode); byte[] buffer = new byte[100]; QuicStreamAbortedException ex = await Assert.ThrowsAsync <QuicStreamAbortedException>(() => serverStream.ReadAsync(buffer).AsTask()); Assert.Equal(ExpectedErrorCode, ex.ErrorCode); }).TimeoutAfter(millisecondsTimeout: 5_000); }
public override void Abort(ConnectionAbortedException abortReason) { // Don't call _stream.Shutdown and _stream.Abort at the same time. lock (_shutdownLock) { _stream.AbortRead(_context.Options.AbortErrorCode); _stream.AbortWrite(_context.Options.AbortErrorCode); } // Cancel ProcessSends loop after calling shutdown to ensure the correct _shutdownReason gets set. Output.CancelPendingRead(); }
public override void Abort(ConnectionAbortedException abortReason) { // This abort is called twice, make sure that doesn't happen. // Don't call _stream.Shutdown and _stream.Abort at the same time. if (_aborted) { return; } _aborted = true; _log.StreamAbort(ConnectionId, abortReason); lock (_shutdownLock) { _stream.AbortRead(Error); _stream.AbortWrite(Error); } // Cancel ProcessSends loop after calling shutdown to ensure the correct _shutdownReason gets set. Output.CancelPendingRead(); }
/// <summary> /// Routes a stream to an appropriate stream-type-specific processor /// </summary> private async Task ProcessServerStreamAsync(QuicStream stream) { try { await using (stream.ConfigureAwait(false)) using (var buffer = new ArrayBuffer(initialSize: 32, usePool: true)) { if (stream.CanWrite) { // Server initiated bidirectional streams are either push streams or extensions, and we support neither. throw new Http3ConnectionException(Http3ErrorCode.StreamCreationError); } int bytesRead; try { bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); } catch (QuicStreamAbortedException) { // Treat identical to receiving 0. See below comment. bytesRead = 0; } if (bytesRead == 0) { // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-unidirectional-streams // A sender can close or reset a unidirectional stream unless otherwise specified. A receiver MUST // tolerate unidirectional streams being closed or reset prior to the reception of the unidirectional // stream header. return; } buffer.Commit(bytesRead); // Stream type is a variable-length integer, but we only check the first byte. There is no known type requiring more than 1 byte. switch (buffer.ActiveSpan[0]) { case (byte)Http3StreamType.Control: if (Interlocked.Exchange(ref _haveServerControlStream, 1) != 0) { // A second control stream has been received. throw new Http3ConnectionException(Http3ErrorCode.StreamCreationError); } // Discard the stream type header. buffer.Discard(1); await ProcessServerControlStreamAsync(stream, buffer).ConfigureAwait(false); return; case (byte)Http3StreamType.QPackDecoder: if (Interlocked.Exchange(ref _haveServerQpackDecodeStream, 1) != 0) { // A second QPack decode stream has been received. throw new Http3ConnectionException(Http3ErrorCode.StreamCreationError); } // The stream must not be closed, but we aren't using QPACK right now -- ignore. buffer.Dispose(); await stream.CopyToAsync(Stream.Null).ConfigureAwait(false); return; case (byte)Http3StreamType.QPackEncoder: if (Interlocked.Exchange(ref _haveServerQpackEncodeStream, 1) != 0) { // A second QPack encode stream has been received. throw new Http3ConnectionException(Http3ErrorCode.StreamCreationError); } // The stream must not be closed, but we aren't using QPACK right now -- ignore. buffer.Dispose(); await stream.CopyToAsync(Stream.Null).ConfigureAwait(false); return; case (byte)Http3StreamType.Push: // We don't support push streams. // Because no maximum push stream ID was negotiated via a MAX_PUSH_ID frame, server should not have sent this. Abort the connection with H3_ID_ERROR. throw new Http3ConnectionException(Http3ErrorCode.IdError); default: // Unknown stream type. Per spec, these must be ignored and aborted but not be considered a connection-level error. if (NetEventSource.Log.IsEnabled()) { // Read the rest of the integer, which might be more than 1 byte, so we can log it. long unknownStreamType; while (!VariableLengthIntegerHelper.TryRead(buffer.ActiveSpan, out unknownStreamType, out _)) { buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength); bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead == 0) { unknownStreamType = -1; break; } buffer.Commit(bytesRead); } NetEventSource.Info(this, $"Ignoring server-initiated stream of unknown type {unknownStreamType}."); } stream.AbortWrite((long)Http3ErrorCode.StreamCreationError); return; } } } catch (Exception ex) { Abort(ex); } }