Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,7 @@ private async Task ProcessServerStreamAsync(QuicStream stream)
{
await using (stream.ConfigureAwait(false))
{
// Check if this is a bidirectional stream (which we don't support from the server).
if (stream.CanWrite)
{
// Server initiated bidirectional streams are either push streams or extensions, and we support neither.
Expand All @@ -591,49 +592,63 @@ private async Task ProcessServerStreamAsync(QuicStream stream)

buffer = new ArrayBuffer(initialSize: 32, usePool: true);

int bytesRead;

try
{
bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false);
}
catch (QuicException ex) when (ex.QuicError == QuicError.StreamAborted)
// Read the stream type, which is a variable-length integer.
// This may require multiple reads if the integer is encoded in multiple bytes.
long streamType;
while (true)
{
// Treat identical to receiving 0. See below comment.
bytesRead = 0;
int bytesRead;
try
{
bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false);
}
catch (QuicException ex) when (ex.QuicError == QuicError.StreamAborted)
{
// Treat identical to receiving 0. See below comment.
bytesRead = 0;
}

if (bytesRead == 0)
{
// https://www.rfc-editor.org/rfc/rfc9114.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);

if (VariableLengthIntegerHelper.TryRead(buffer.ActiveSpan, out streamType, out int streamTypeLength))
{
// Successfully read the stream type.
buffer.Discard(streamTypeLength);
break;
}
}

if (bytesRead == 0)
if (NetEventSource.Log.IsEnabled())
{
// https://www.rfc-editor.org/rfc/rfc9114.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;
NetEventSource.Info(this, $"Received server-initiated unidirectional stream of type {streamType}");
}

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])
// Process the stream based on its type.
switch ((Http3StreamType)streamType)
{
case (byte)Http3StreamType.Control:
case Http3StreamType.Control:
if (Interlocked.Exchange(ref _haveServerControlStream, true))
{
// A second control stream has been received.
throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.StreamCreationError);
}

// Discard the stream type header.
buffer.Discard(1);

// Ownership of buffer is transferred to ProcessServerControlStreamAsync.
ArrayBuffer bufferCopy = buffer;
buffer = default;

await ProcessServerControlStreamAsync(stream, bufferCopy).ConfigureAwait(false);
return;
case (byte)Http3StreamType.QPackDecoder:
case Http3StreamType.QPackDecoder:
if (Interlocked.Exchange(ref _haveServerQpackDecodeStream, true))
{
// A second QPack decode stream has been received.
Expand All @@ -644,7 +659,7 @@ private async Task ProcessServerStreamAsync(QuicStream stream)
buffer.Dispose();
await stream.CopyToAsync(Stream.Null).ConfigureAwait(false);
return;
case (byte)Http3StreamType.QPackEncoder:
case Http3StreamType.QPackEncoder:
if (Interlocked.Exchange(ref _haveServerQpackEncodeStream, true))
{
// A second QPack encode stream has been received.
Expand All @@ -656,35 +671,12 @@ private async Task ProcessServerStreamAsync(QuicStream stream)
buffer.Dispose();
await stream.CopyToAsync(Stream.Null).ConfigureAwait(false);
return;
case (byte)Http3StreamType.Push:
case 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 HttpProtocolException.CreateHttp3ConnectionException(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.Abort(QuicAbortDirection.Read, (long)Http3ErrorCode.StreamCreationError);
return;
}
Expand Down
Loading