Skip to content

Commit

Permalink
if actual bytes to be written is negative, set to zero (#58575)
Browse files Browse the repository at this point in the history
* wait for window updates, if stream window becomes negative

* update comment

* fix typo

* update comment per suggestion

Co-authored-by: Andrew Casey <[email protected]>

---------

Co-authored-by: Andrew Casey <[email protected]>
  • Loading branch information
bhaskarqlik and amcasey authored Oct 25, 2024
1 parent f6dedca commit 397ef0a
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 0 deletions.
11 changes: 11 additions & 0 deletions src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,17 @@ private async Task WriteToOutputPipe()
// Now check the connection window
actual = CheckConnectionWindow(actual);

// actual is negative means window size has become negative
// this can usually happen if the receiver decreases window size before receiving the previous data frame
// in this case, reset to 0 and continue, no data will be sent but will wait for window update
// RFC 9113 section 6.9.2 specifically calls out that the window size can go negative. As required,
// we continue to track the negative value but use 0 for the remainder of this write to avoid
// out-of-range errors.
if (actual < 0)
{
actual = 0;
}

// Write what we can
if (actual < buffer.Length)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4751,6 +4751,52 @@ await ExpectAsync(Http2FrameType.DATA,
Assert.True(_helloWorldBytes.AsSpan(9, 3).SequenceEqual(dataFrame3.PayloadSequence.ToArray()));
}

[Fact]
public async Task WINDOW_UPDATE_Received_OnStream_Resumed_WhenInitialWindowSizeNegativeMidStream()
{
const int windowSize = 3;
_clientSettings.InitialWindowSize = windowSize;
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
await InitializeConnectionAsync(async context =>
{
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
bodyControlFeature.AllowSynchronousIO = true;
await context.Response.Body.WriteAsync(new byte[windowSize - 1], 0, windowSize - 1);
await tcs.Task;
await context.Response.Body.WriteAsync(new byte[windowSize], 0, windowSize);
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);

// Decrease window size after server has already sent the current window - 1 size of data
_clientSettings.InitialWindowSize = windowSize - 2;
await SendSettingsAsync();
await ExpectAsync(Http2FrameType.DATA,
withLength: windowSize - 1,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.SETTINGS,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 0);
tcs.SetResult();

// send window update to receive the next frame data
await SendWindowUpdateAsync(1, windowSize + 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: windowSize,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}

[Fact]
public async Task CONTINUATION_Received_Decoded()
{
Expand Down

0 comments on commit 397ef0a

Please sign in to comment.