Skip to content

Commit 10555e3

Browse files
author
ladeak
committed
Frame ordering
1 parent fb9f1e6 commit 10555e3

2 files changed

Lines changed: 73 additions & 39 deletions

File tree

src/CHttpServer/CHttpServer/Http3/Http3Stream.cs

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ internal sealed partial class Http3Stream
1818
private enum StreamReadingStatus
1919
{
2020
ReadingFrameHeader,
21-
ReadingPayload,
21+
ReadingPayloadHeader,
22+
ReadingPayloadData,
23+
ReadingPayloadReserved,
2224
}
2325

2426
private QuicStream? _quicStream;
@@ -96,7 +98,6 @@ public async void ProcessStream<TContext>(IHttpApplication<TContext> application
9698
public async Task ProcessStreamAsync<TContext>(IHttpApplication<TContext> application, CancellationToken token)
9799
where TContext : notnull
98100
{
99-
ulong frameType = 0;
100101
long payloadRemainingLength = 0;
101102
Task? applicationProcessing = null;
102103
var streamReadingState = StreamReadingStatus.ReadingFrameHeader;
@@ -112,7 +113,7 @@ public async Task ProcessStreamAsync<TContext>(IHttpApplication<TContext> applic
112113

113114
while (!buffer.IsEmpty)
114115
{
115-
long bufferConsumed = ProcessStreamAsync(application, ref frameType, ref payloadRemainingLength, ref applicationProcessing, ref streamReadingState, buffer, token);
116+
long bufferConsumed = ProcessStreamAsync(application, ref payloadRemainingLength, ref applicationProcessing, ref streamReadingState, buffer, token);
116117

117118
// Could not further process. Break the inner loop to read more data
118119
if (bufferConsumed == 0)
@@ -130,6 +131,11 @@ public async Task ProcessStreamAsync<TContext>(IHttpApplication<TContext> applic
130131
{
131132
// Ignore
132133
}
134+
catch (Http3ConnectionException connectionError)
135+
{
136+
_connection?.StreamError(connectionError.ErrorCode);
137+
throw;
138+
}
133139
catch (Exception ex)
134140
{
135141
Debug.WriteLine(ex);
@@ -141,7 +147,6 @@ public async Task ProcessStreamAsync<TContext>(IHttpApplication<TContext> applic
141147
}
142148

143149
private long ProcessStreamAsync<TContext>(IHttpApplication<TContext> application,
144-
ref ulong frameType,
145150
ref long payloadRemainingLength,
146151
ref Task? applicationProcessing,
147152
ref StreamReadingStatus streamReadingState,
@@ -153,7 +158,7 @@ private long ProcessStreamAsync<TContext>(IHttpApplication<TContext> application
153158
if (streamReadingState == StreamReadingStatus.ReadingFrameHeader)
154159
{
155160
// FrameType is a single byte.
156-
if (!VariableLenghtIntegerDecoder.TryRead(buffer.FirstSpan, out frameType, out int bytesReadFrameType))
161+
if (!VariableLenghtIntegerDecoder.TryRead(buffer.FirstSpan, out var frameType, out int bytesReadFrameType))
157162
{
158163
// Not enough data to read payload length.
159164
return 0;
@@ -165,49 +170,59 @@ private long ProcessStreamAsync<TContext>(IHttpApplication<TContext> application
165170
return 0;
166171
}
167172
payloadRemainingLength = checked((long)payloadLength);
168-
streamReadingState = StreamReadingStatus.ReadingPayload;
173+
streamReadingState = NextRequestReadingState(applicationProcessing, frameType);
169174
buffer = buffer.Slice(bytesReadPayloadLength);
170175
bufferConsumed += bytesReadFrameType + bytesReadPayloadLength;
171176
return bufferConsumed;
172177
}
173178

174-
if (streamReadingState == StreamReadingStatus.ReadingPayload)
179+
if (payloadRemainingLength < buffer.Length)
180+
buffer = buffer.Slice(0, payloadRemainingLength);
181+
182+
if (streamReadingState == StreamReadingStatus.ReadingPayloadHeader)
175183
{
176-
switch (frameType)
177-
{
178-
case 0x0: // DATA
179-
if (payloadRemainingLength < buffer.Length)
180-
buffer = buffer.Slice(0, payloadRemainingLength);
181-
bufferConsumed = ProcessDataFrame(buffer);
182-
payloadRemainingLength -= bufferConsumed;
183-
break;
184-
case 0x1: // HEADERS
185-
if (payloadRemainingLength < buffer.Length)
186-
buffer = buffer.Slice(0, payloadRemainingLength);
187-
bufferConsumed = ProcessHeaderFrame(buffer);
188-
payloadRemainingLength -= bufferConsumed;
189-
if (payloadRemainingLength == 0)
190-
applicationProcessing = Task.Run(() => StartApplicationProcessing(application, token), token);
191-
break;
192-
default:
193-
if ((frameType - 0x21) % 0x1f != 0)
194-
{
195-
_connection?.StreamError(ErrorCodes.H3FrameUnexpected);
196-
throw new Http3ConnectionException(ErrorCodes.H3FrameUnexpected);
197-
}
198-
199-
// Read the complete reserved frame.
200-
bufferConsumed = payloadRemainingLength > buffer.Length ? buffer.Length : payloadRemainingLength;
201-
payloadRemainingLength -= bufferConsumed;
202-
break;
203-
}
204-
if (payloadRemainingLength == 0)
205-
streamReadingState = StreamReadingStatus.ReadingFrameHeader;
184+
bufferConsumed = ProcessHeaderFrame(buffer);
185+
if (payloadRemainingLength == bufferConsumed)
186+
applicationProcessing = Task.Run(() => StartApplicationProcessing(application, token), token);
206187
}
207188

189+
if (streamReadingState == StreamReadingStatus.ReadingPayloadData)
190+
bufferConsumed = ProcessDataFrame(buffer);
191+
192+
if (streamReadingState == StreamReadingStatus.ReadingPayloadReserved)
193+
bufferConsumed = buffer.Length; // Read the complete reserved frame.
194+
195+
payloadRemainingLength -= bufferConsumed;
196+
if (payloadRemainingLength == 0)
197+
streamReadingState = StreamReadingStatus.ReadingFrameHeader;
208198
return bufferConsumed;
209199
}
210200

201+
private StreamReadingStatus NextRequestReadingState(Task? applicationProcessing, ulong frameType)
202+
{
203+
StreamReadingStatus streamReadingState;
204+
switch (frameType)
205+
{
206+
case 0x0: // DATA
207+
if (applicationProcessing == null)
208+
throw new Http3ConnectionException(ErrorCodes.H3FrameUnexpected);
209+
streamReadingState = StreamReadingStatus.ReadingPayloadData;
210+
break;
211+
case 0x1: // HEADERS
212+
if (applicationProcessing != null)
213+
throw new Http3ConnectionException(ErrorCodes.H3FrameUnexpected);
214+
streamReadingState = StreamReadingStatus.ReadingPayloadHeader;
215+
break;
216+
default:
217+
if ((frameType - 0x21) % 0x1f != 0)
218+
throw new Http3ConnectionException(ErrorCodes.H3FrameUnexpected);
219+
streamReadingState = StreamReadingStatus.ReadingPayloadReserved;
220+
break;
221+
}
222+
223+
return streamReadingState;
224+
}
225+
211226
private async Task CloseStreamAsync()
212227
{
213228
await _dataReader.CompleteAsync();
@@ -221,7 +236,7 @@ private async Task CloseStreamAsync()
221236
private long ProcessDataFrame(ReadOnlySequence<byte> buffer)
222237
{
223238
// 'Copy' to Body
224-
return 0;
239+
return buffer.Length;
225240

226241
}
227242

@@ -337,4 +352,4 @@ internal partial class Http3Stream : IHttpResponseTrailersFeature
337352
{
338353
private readonly Http3ResponseHeaderCollection _responseTrailers;
339354
public IHeaderDictionary Trailers { get => _responseTrailers; set => throw new PlatformNotSupportedException(); }
340-
}
355+
}

tests/CHttpServer.Tests/Http3/Http3StreamTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,25 @@ public async Task SingleWrite_HeaderFrame_ReservedFrame_DataFrame()
178178
await quicConnection.DisposeAsync();
179179
}
180180

181+
[Fact]
182+
public async Task SingleWrite_InOrderFrames_Throw()
183+
{
184+
var quicConnection = await QuicConnectionFixture.SetupConnectionAsync(Port, TestContext.Current.CancellationToken);
185+
var serverStreamTask = Task.Run(async () => await quicConnection.ServerConnection.AcceptInboundStreamAsync());
186+
var sut = new Http3Stream([]);
187+
188+
var clientStream = await quicConnection.ClientConnection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional, TestContext.Current.CancellationToken);
189+
byte[] data = [.. GetData(30), .. await GetHeadersFrame()]; //DATA before HEADERS is in-order
190+
await clientStream.WriteAsync(data, TestContext.Current.CancellationToken);
191+
await clientStream.FlushAsync(TestContext.Current.CancellationToken);
192+
193+
sut.Initialize(null, await serverStreamTask);
194+
var testApp = new TestBase.TestApplication(ctx => Task.CompletedTask);
195+
var processing = await Assert.ThrowsAsync<Http3ConnectionException>(() => sut.ProcessStreamAsync(testApp, TestContext.Current.CancellationToken));
196+
clientStream.Close();
197+
await quicConnection.DisposeAsync();
198+
}
199+
181200
private static async Task<byte[]> GetHeadersFrame()
182201
{
183202
var encoder = new QPackDecoder();

0 commit comments

Comments
 (0)