/// <summary> /// Reads the server's control stream. /// </summary> private async Task ProcessServerControlStreamAsync(QuicStream stream, ArrayBuffer buffer) { (Http3FrameType? frameType, long payloadLength) = await ReadFrameEnvelopeAsync().ConfigureAwait(false); if (frameType == null) { // Connection closed prematurely, expected SETTINGS frame. throw new Http3ConnectionException(Http3ErrorCode.ClosedCriticalStream); } if (frameType != Http3FrameType.Settings) { // Expected SETTINGS as first frame of control stream. throw new Http3ConnectionException(Http3ErrorCode.MissingSettings); } await ProcessSettingsFrameAsync(payloadLength).ConfigureAwait(false); while (true) { (frameType, payloadLength) = await ReadFrameEnvelopeAsync().ConfigureAwait(false); switch (frameType) { case Http3FrameType.GoAway: await ProcessGoAwayFameAsync(payloadLength).ConfigureAwait(false); break; case Http3FrameType.Settings: // Only a single SETTINGS frame is supported. throw new Http3ConnectionException(Http3ErrorCode.UnexpectedFrame); case Http3FrameType.Headers: case Http3FrameType.Data: case Http3FrameType.MaxPushId: case Http3FrameType.DuplicatePush: // Servers should not send these frames to a control stream. throw new Http3ConnectionException(Http3ErrorCode.UnexpectedFrame); case Http3FrameType.PushPromise: case Http3FrameType.CancelPush: // Because we haven't sent any MAX_PUSH_ID frame, it is invalid to receive any push-related frames as they will all reference a too-large ID. throw new Http3ConnectionException(Http3ErrorCode.IdError); case null: // End of stream reached. If we're shutting down, stop looping. Otherwise, this is an error (this stream should not be closed for life of connection). bool shuttingDown; lock (SyncObj) { shuttingDown = ShuttingDown; } if (!shuttingDown) { throw new Http3ConnectionException(Http3ErrorCode.ClosedCriticalStream); } return; default: await SkipUnknownPayloadAsync(frameType.GetValueOrDefault(), payloadLength).ConfigureAwait(false); break; } } async ValueTask <(Http3FrameType?frameType, long payloadLength)> ReadFrameEnvelopeAsync() { long frameType, payloadLength; int bytesRead; while (!Http3Frame.TryReadIntegerPair(buffer.ActiveSpan, out frameType, out payloadLength, out bytesRead)) { buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength * 2); bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead != 0) { buffer.Commit(bytesRead); } else if (buffer.ActiveLength == 0) { // End of stream. return(null, 0); } else { // Our buffer has partial frame data in it but not enough to complete the read: bail out. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } } buffer.Discard(bytesRead); return((Http3FrameType)frameType, payloadLength); } async ValueTask ProcessSettingsFrameAsync(long settingsPayloadLength) { if (settingsPayloadLength > MaximumSettingsPayloadLength) { if (NetEventSource.Log.IsEnabled()) { Trace($"Received SETTINGS frame with {settingsPayloadLength} byte payload exceeding the {MaximumSettingsPayloadLength} byte maximum."); } throw new Http3ConnectionException(Http3ErrorCode.ExcessiveLoad); } while (settingsPayloadLength != 0) { long settingId, settingValue; int bytesRead; while (!Http3Frame.TryReadIntegerPair(buffer.ActiveSpan, out settingId, out settingValue, out bytesRead)) { buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength * 2); bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead != 0) { buffer.Commit(bytesRead); } else { // Our buffer has partial frame data in it but not enough to complete the read: bail out. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } } settingsPayloadLength -= bytesRead; // Only support this single setting. Skip others. if (settingId == (long)Http3SettingType.MaxHeaderListSize) { _maximumHeadersLength = (int)Math.Min(settingValue, int.MaxValue); } } } async ValueTask ProcessGoAwayFameAsync(long goawayPayloadLength) { long lastStreamId; int bytesRead; while (!VariableLengthIntegerHelper.TryRead(buffer.AvailableSpan, out lastStreamId, out bytesRead)) { buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength); bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead != 0) { buffer.Commit(bytesRead); } else { // Our buffer has partial frame data in it but not enough to complete the read: bail out. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } } buffer.Discard(bytesRead); if (bytesRead != goawayPayloadLength) { // Frame contains unknown extra data after the integer. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } OnServerGoAway(lastStreamId); } async ValueTask SkipUnknownPayloadAsync(Http3FrameType frameType, long payloadLength) { if (payloadLength > MaximumUnknownFramePayloadLength) { Trace($"Received unknown frame type 0x{(long)frameType:x} with {payloadLength} byte payload exceeding the {MaximumUnknownFramePayloadLength} byte maximum."); throw new Http3ConnectionException(Http3ErrorCode.ExcessiveLoad); } while (payloadLength != 0) { if (buffer.ActiveLength == 0) { int bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead != 0) { buffer.Commit(bytesRead); } else { // Our buffer has partial frame data in it but not enough to complete the read: bail out. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } } long readLength = Math.Min(payloadLength, buffer.ActiveLength); buffer.Discard((int)readLength); payloadLength -= readLength; } } }
/// <summary> /// Reads the server's control stream. /// </summary> private async Task ProcessServerControlStreamAsync(QuicStream stream, ArrayBuffer buffer) { using (buffer) { // Read the first frame of the control stream. Per spec: // A SETTINGS frame MUST be sent as the first frame of each control stream. (Http3FrameType? frameType, long payloadLength) = await ReadFrameEnvelopeAsync().ConfigureAwait(false); if (frameType == null) { // Connection closed prematurely, expected SETTINGS frame. throw new Http3ConnectionException(Http3ErrorCode.ClosedCriticalStream); } if (frameType != Http3FrameType.Settings) { throw new Http3ConnectionException(Http3ErrorCode.MissingSettings); } await ProcessSettingsFrameAsync(payloadLength).ConfigureAwait(false); // Read subsequent frames. while (true) { (frameType, payloadLength) = await ReadFrameEnvelopeAsync().ConfigureAwait(false); switch (frameType) { case Http3FrameType.GoAway: await ProcessGoAwayFameAsync(payloadLength).ConfigureAwait(false); break; case Http3FrameType.Settings: // If an endpoint receives a second SETTINGS frame on the control stream, the endpoint MUST respond with a connection error of type H3_FRAME_UNEXPECTED. throw new Http3ConnectionException(Http3ErrorCode.UnexpectedFrame); case Http3FrameType.Headers: case Http3FrameType.Data: case Http3FrameType.MaxPushId: case Http3FrameType.DuplicatePush: // Servers should not send these frames to a control stream. throw new Http3ConnectionException(Http3ErrorCode.UnexpectedFrame); case Http3FrameType.PushPromise: case Http3FrameType.CancelPush: // Because we haven't sent any MAX_PUSH_ID frame, it is invalid to receive any push-related frames as they will all reference a too-large ID. throw new Http3ConnectionException(Http3ErrorCode.IdError); case null: // End of stream reached. If we're shutting down, stop looping. Otherwise, this is an error (this stream should not be closed for life of connection). bool shuttingDown; lock (SyncObj) { shuttingDown = ShuttingDown; } if (!shuttingDown) { throw new Http3ConnectionException(Http3ErrorCode.ClosedCriticalStream); } return; default: await SkipUnknownPayloadAsync(frameType.GetValueOrDefault(), payloadLength).ConfigureAwait(false); break; } } } async ValueTask <(Http3FrameType?frameType, long payloadLength)> ReadFrameEnvelopeAsync() { long frameType, payloadLength; int bytesRead; while (!Http3Frame.TryReadIntegerPair(buffer.ActiveSpan, out frameType, out payloadLength, out bytesRead)) { buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength * 2); bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead != 0) { buffer.Commit(bytesRead); } else if (buffer.ActiveLength == 0) { // End of stream. return(null, 0); } else { // Our buffer has partial frame data in it but not enough to complete the read: bail out. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } } buffer.Discard(bytesRead); return((Http3FrameType)frameType, payloadLength); } async ValueTask ProcessSettingsFrameAsync(long settingsPayloadLength) { while (settingsPayloadLength != 0) { long settingId, settingValue; int bytesRead; while (!Http3Frame.TryReadIntegerPair(buffer.ActiveSpan, out settingId, out settingValue, out bytesRead)) { buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength * 2); bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead != 0) { buffer.Commit(bytesRead); } else { // Our buffer has partial frame data in it but not enough to complete the read: bail out. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } } settingsPayloadLength -= bytesRead; if (settingsPayloadLength < 0) { // An integer was encoded past the payload length. // A frame payload that contains additional bytes after the identified fields or a frame payload that terminates before the end of the identified fields MUST be treated as a connection error of type H3_FRAME_ERROR. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } buffer.Discard(bytesRead); // Only support this single setting. Skip others. if (settingId == (long)Http3SettingType.MaxHeaderListSize) { _maximumHeadersLength = (int)Math.Min(settingValue, int.MaxValue); } } } async ValueTask ProcessGoAwayFameAsync(long goawayPayloadLength) { long lastStreamId; int bytesRead; while (!VariableLengthIntegerHelper.TryRead(buffer.AvailableSpan, out lastStreamId, out bytesRead)) { buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength); bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead != 0) { buffer.Commit(bytesRead); } else { // Our buffer has partial frame data in it but not enough to complete the read: bail out. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } } buffer.Discard(bytesRead); if (bytesRead != goawayPayloadLength) { // Frame contains unknown extra data after the integer. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } OnServerGoAway(lastStreamId); } async ValueTask SkipUnknownPayloadAsync(Http3FrameType frameType, long payloadLength) { while (payloadLength != 0) { if (buffer.ActiveLength == 0) { int bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead != 0) { buffer.Commit(bytesRead); } else { // Our buffer has partial frame data in it but not enough to complete the read: bail out. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } } long readLength = Math.Min(payloadLength, buffer.ActiveLength); buffer.Discard((int)readLength); payloadLength -= readLength; } } }