diff --git a/src/Kestrel.Core/CoreStrings.resx b/src/Kestrel.Core/CoreStrings.resx
index 6d72c97ca4..c40ba8d917 100644
--- a/src/Kestrel.Core/CoreStrings.resx
+++ b/src/Kestrel.Core/CoreStrings.resx
@@ -575,4 +575,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
The frame is too short to contain the fields indicated by the given flags.
+
+ A value between {min} and {max} is required.
+
\ No newline at end of file
diff --git a/src/Kestrel.Core/Http2Limits.cs b/src/Kestrel.Core/Http2Limits.cs
index 34a4ef36f1..f4b05c4abb 100644
--- a/src/Kestrel.Core/Http2Limits.cs
+++ b/src/Kestrel.Core/Http2Limits.cs
@@ -11,6 +11,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
public class Http2Limits
{
private int _maxStreamsPerConnection = 100;
+ private int _headerTableSize = MaxAllowedHeaderTableSize;
+ private int _maxFrameSize = MinAllowedMaxFrameSize;
+
+ // These are limits defined by the RFC https://tools.ietf.org/html/rfc7540#section-4.2
+ public const int MaxAllowedHeaderTableSize = 4096;
+ public const int MinAllowedMaxFrameSize = 16 * 1024;
+ public const int MaxAllowedMaxFrameSize = 16 * 1024 * 1024 - 1;
///
/// Limits the number of concurrent request streams per HTTP/2 connection. Excess streams will be refused.
@@ -27,8 +34,49 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
{
throw new ArgumentOutOfRangeException(nameof(value), value, CoreStrings.GreaterThanZeroRequired);
}
+
_maxStreamsPerConnection = value;
}
}
+
+ ///
+ /// Limits the size of the header compression table, in octets, the HPACK decoder on the server can use.
+ ///
+ /// Defaults to 4096
+ ///
+ ///
+ public int HeaderTableSize
+ {
+ get => _headerTableSize;
+ set
+ {
+ if (value <= 0 || value > MaxAllowedHeaderTableSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), value, CoreStrings.FormatArgumentOutOfRange(0, MaxAllowedHeaderTableSize));
+ }
+
+ _headerTableSize = value;
+ }
+ }
+
+ ///
+ /// Indicates the size of the largest frame payload that is allowed to be received, in octets. The size must be between 2^14 and 2^24-1.
+ ///
+ /// Defaults to 2^14 (16,384)
+ ///
+ ///
+ public int MaxFrameSize
+ {
+ get => _maxFrameSize;
+ set
+ {
+ if (value < MinAllowedMaxFrameSize || value > MaxAllowedMaxFrameSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), value, CoreStrings.FormatArgumentOutOfRange(MinAllowedMaxFrameSize, MaxAllowedMaxFrameSize));
+ }
+
+ _maxFrameSize = value;
+ }
+ }
}
}
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs
index 3bd7aa7c29..c33674d645 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs
@@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private readonly Http2PeerSettings _serverSettings = new Http2PeerSettings();
private readonly Http2PeerSettings _clientSettings = new Http2PeerSettings();
- private readonly Http2Frame _incomingFrame = new Http2Frame();
+ private readonly Http2Frame _incomingFrame;
private Http2Stream _currentHeadersStream;
private RequestHeaderParsingState _requestHeaderParsingState;
@@ -87,8 +87,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
_context = context;
_frameWriter = new Http2FrameWriter(context.Transport.Output, context.ConnectionContext, _outputFlowControl, this, context.ConnectionId, context.ServiceContext.Log);
- _hpackDecoder = new HPackDecoder((int)_serverSettings.HeaderTableSize);
_serverSettings.MaxConcurrentStreams = (uint)context.ServiceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection;
+ _serverSettings.MaxFrameSize = (uint)context.ServiceContext.ServerOptions.Limits.Http2.MaxFrameSize;
+ _serverSettings.HeaderTableSize = (uint)context.ServiceContext.ServerOptions.Limits.Http2.HeaderTableSize;
+ _hpackDecoder = new HPackDecoder((int)_serverSettings.HeaderTableSize);
+ _incomingFrame = new Http2Frame(_serverSettings.MaxFrameSize);
}
public string ConnectionId => _context.ConnectionId;
@@ -601,7 +604,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return Task.CompletedTask;
}
- private Task ProcessSettingsFrameAsync()
+ private async Task ProcessSettingsFrameAsync()
{
if (_currentHeadersStream != null)
{
@@ -620,7 +623,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsAckLengthNotZero, Http2ErrorCode.FRAME_SIZE_ERROR);
}
- return Task.CompletedTask;
+ return;
}
if (_incomingFrame.PayloadLength % 6 != 0)
@@ -632,10 +635,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
// int.MaxValue is the largest allowed windows size.
var previousInitialWindowSize = (int)_clientSettings.InitialWindowSize;
+ var previousMaxFrameSize = _clientSettings.MaxFrameSize;
_clientSettings.Update(_incomingFrame.GetSettings());
- var ackTask = _frameWriter.WriteSettingsAckAsync(); // Ack before we update the windows, they could send data immediately.
+ // Ack before we update the windows, they could send data immediately.
+ await _frameWriter.WriteSettingsAckAsync();
+
+ if (_clientSettings.MaxFrameSize != previousMaxFrameSize)
+ {
+ _frameWriter.UpdateMaxFrameSize(_clientSettings.MaxFrameSize);
+ }
// This difference can be negative.
var windowSizeDifference = (int)_clientSettings.InitialWindowSize - previousInitialWindowSize;
@@ -653,8 +663,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
}
}
-
- return ackTask;
}
catch (Http2SettingsParameterOutOfRangeException ex)
{
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.Continuation.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.Continuation.cs
index 26f3afe92b..56712f5289 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Frame.Continuation.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.Continuation.cs
@@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public void PrepareContinuation(Http2ContinuationFrameFlags flags, int streamId)
{
- PayloadLength = MinAllowedMaxFrameSize - HeaderLength;
+ PayloadLength = (int)_maxFrameSize;
Type = Http2FrameType.CONTINUATION;
ContinuationFlags = flags;
StreamId = streamId;
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.Data.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.Data.cs
index b2df5c4bfd..3bc53971e5 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Frame.Data.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.Data.cs
@@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
var padded = padLength != null;
- PayloadLength = MinAllowedMaxFrameSize;
+ PayloadLength = (int)_maxFrameSize;
Type = Http2FrameType.DATA;
DataFlags = padded ? Http2DataFrameFlags.PADDED : Http2DataFrameFlags.NONE;
StreamId = streamId;
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.Headers.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.Headers.cs
index e89a2e1675..3028e27200 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Frame.Headers.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.Headers.cs
@@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public void PrepareHeaders(Http2HeadersFrameFlags flags, int streamId)
{
- PayloadLength = MinAllowedMaxFrameSize - HeaderLength;
+ PayloadLength = (int)_maxFrameSize;
Type = Http2FrameType.HEADERS;
HeadersFlags = flags;
StreamId = streamId;
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.cs
index f5f447ae7f..f9d0723bdc 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Frame.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.cs
@@ -18,8 +18,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
*/
public partial class Http2Frame
{
- public const int MinAllowedMaxFrameSize = 16 * 1024;
- public const int MaxAllowedMaxFrameSize = 16 * 1024 * 1024 - 1;
public const int HeaderLength = 9;
private const int LengthOffset = 0;
@@ -28,7 +26,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private const int StreamIdOffset = 5;
private const int PayloadOffset = 9;
- private readonly byte[] _data = new byte[HeaderLength + MinAllowedMaxFrameSize];
+ private uint _maxFrameSize;
+
+ private readonly byte[] _data;
+
+ public Http2Frame(uint maxFrameSize)
+ {
+ _maxFrameSize = maxFrameSize;
+ _data = new byte[HeaderLength + _maxFrameSize];
+ }
public Span Raw => new Span(_data, 0, HeaderLength + PayloadLength);
diff --git a/src/Kestrel.Core/Internal/Http2/Http2FrameWriter.cs b/src/Kestrel.Core/Internal/Http2/Http2FrameWriter.cs
index 1d101d1591..35def3fc16 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2FrameWriter.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2FrameWriter.cs
@@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
-using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
@@ -22,7 +21,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
// Literal Header Field without Indexing - Indexed Name (Index 8 - :status)
private static readonly byte[] _continueBytes = new byte[] { 0x08, 0x03, (byte)'1', (byte)'0', (byte)'0' };
- private readonly Http2Frame _outgoingFrame = new Http2Frame();
+ private uint _maxFrameSize = Http2Limits.MinAllowedMaxFrameSize;
+ private Http2Frame _outgoingFrame;
private readonly object _writeLock = new object();
private readonly HPackEncoder _hpackEncoder = new HPackEncoder();
private readonly PipeWriter _outputWriter;
@@ -49,6 +49,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_connectionId = connectionId;
_log = log;
_flusher = new StreamSafePipeFlusher(_outputWriter, timeoutControl);
+ _outgoingFrame = new Http2Frame(_maxFrameSize);
+ }
+
+ public void UpdateMaxFrameSize(uint maxFrameSize)
+ {
+ lock (_writeLock)
+ {
+ _maxFrameSize = maxFrameSize;
+ _outgoingFrame = new Http2Frame(maxFrameSize);
+ }
}
public void Complete()
diff --git a/src/Kestrel.Core/Internal/Http2/Http2PeerSettings.cs b/src/Kestrel.Core/Internal/Http2/Http2PeerSettings.cs
index eeb13bb808..e821daac13 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2PeerSettings.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2PeerSettings.cs
@@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public const bool DefaultEnablePush = true;
public const uint DefaultMaxConcurrentStreams = uint.MaxValue;
public const uint DefaultInitialWindowSize = 65535;
- public const uint DefaultMaxFrameSize = 16384;
+ public const uint DefaultMaxFrameSize = Http2Limits.MinAllowedMaxFrameSize;
public const uint DefaultMaxHeaderListSize = uint.MaxValue;
public const uint MaxWindowSize = int.MaxValue;
@@ -38,6 +38,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
switch (setting.Parameter)
{
case Http2SettingsParameter.SETTINGS_HEADER_TABLE_SIZE:
+ if (value > Http2Limits.MaxAllowedHeaderTableSize)
+ {
+ throw new Http2SettingsParameterOutOfRangeException(Http2SettingsParameter.SETTINGS_HEADER_TABLE_SIZE,
+ lowerBound: 0,
+ upperBound: Http2Limits.MaxAllowedHeaderTableSize);
+ }
HeaderTableSize = value;
break;
case Http2SettingsParameter.SETTINGS_ENABLE_PUSH:
@@ -64,11 +70,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
InitialWindowSize = value;
break;
case Http2SettingsParameter.SETTINGS_MAX_FRAME_SIZE:
- if (value < Http2Frame.MinAllowedMaxFrameSize || value > Http2Frame.MaxAllowedMaxFrameSize)
+ if (value < Http2Limits.MinAllowedMaxFrameSize || value > Http2Limits.MaxAllowedMaxFrameSize)
{
throw new Http2SettingsParameterOutOfRangeException(Http2SettingsParameter.SETTINGS_MAX_FRAME_SIZE,
- lowerBound: Http2Frame.MinAllowedMaxFrameSize,
- upperBound: Http2Frame.MaxAllowedMaxFrameSize);
+ lowerBound: Http2Limits.MinAllowedMaxFrameSize,
+ upperBound: Http2Limits.MaxAllowedMaxFrameSize);
}
MaxFrameSize = value;
diff --git a/src/Kestrel.Core/Properties/CoreStrings.Designer.cs b/src/Kestrel.Core/Properties/CoreStrings.Designer.cs
index 77e6f5c678..06945a5160 100644
--- a/src/Kestrel.Core/Properties/CoreStrings.Designer.cs
+++ b/src/Kestrel.Core/Properties/CoreStrings.Designer.cs
@@ -2142,6 +2142,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
internal static string FormatHttp2FrameMissingFields()
=> GetString("Http2FrameMissingFields");
+ ///
+ /// A value between {min} and {max} is required.
+ ///
+ internal static string ArgumentOutOfRange
+ {
+ get => GetString("ArgumentOutOfRange");
+ }
+
+ ///
+ /// A value between {min} and {max} is required.
+ ///
+ internal static string FormatArgumentOutOfRange(object min, object max)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ArgumentOutOfRange", "min", "max"), min, max);
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/test/Kestrel.Core.Tests/KestrelServerLimitsTests.cs b/test/Kestrel.Core.Tests/KestrelServerLimitsTests.cs
index d71642f9dd..cf46717f85 100644
--- a/test/Kestrel.Core.Tests/KestrelServerLimitsTests.cs
+++ b/test/Kestrel.Core.Tests/KestrelServerLimitsTests.cs
@@ -308,6 +308,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Equal(TimeSpan.FromSeconds(5), new KestrelServerLimits().MinResponseDataRate.GracePeriod);
}
+ [Fact]
+ public void Http2MaxFrameSizeDefault()
+ {
+ Assert.Equal(1 << 14, new KestrelServerLimits().Http2.MaxFrameSize);
+ }
+
+ [Theory]
+ [InlineData(1 << 14 - 1)]
+ [InlineData(1 << 24)]
+ [InlineData(-1)]
+ public void Http2MaxFrameSizeInvalid(int value)
+ {
+ var ex = Assert.Throws(() => new KestrelServerLimits().Http2.MaxFrameSize = value);
+ Assert.Contains("A value between", ex.Message);
+ }
+
+ [Fact]
+ public void Http2HeaderTableSizeDefault()
+ {
+ Assert.Equal(4096, new KestrelServerLimits().Http2.HeaderTableSize);
+ }
+
+ [Theory]
+ [InlineData(4097)]
+ [InlineData(-1)]
+ public void Http2HeaderTableSizeInvalid(int value)
+ {
+ var ex = Assert.Throws(() => new KestrelServerLimits().Http2.MaxFrameSize = value);
+ Assert.Contains("A value between", ex.Message);
+ }
+
public static TheoryData TimeoutValidData => new TheoryData
{
TimeSpan.FromTicks(1),
diff --git a/test/Kestrel.InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/test/Kestrel.InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs
index c4b6c3ab44..f876a1f01b 100644
--- a/test/Kestrel.InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs
+++ b/test/Kestrel.InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs
@@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
private static readonly byte[] _worldBytes = Encoding.ASCII.GetBytes("world");
private static readonly byte[] _helloWorldBytes = Encoding.ASCII.GetBytes("hello, world");
private static readonly byte[] _noData = new byte[0];
- private static readonly byte[] _maxData = Encoding.ASCII.GetBytes(new string('a', Http2Frame.MinAllowedMaxFrameSize));
+ private static readonly byte[] _maxData = Encoding.ASCII.GetBytes(new string('a', Http2Limits.MinAllowedMaxFrameSize));
[Fact]
public async Task Frame_Received_OverMaxSize_FrameError()
@@ -85,20 +85,47 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await InitializeConnectionAsync(_echoApplication);
await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
- // Manually craft a frame where the size is too large. Our own frame class won't allow this.
- // See Http2Frame.Length
- var length = Http2Frame.MinAllowedMaxFrameSize + 1; // Too big
- var frame = new byte[9 + length];
- frame[0] = (byte)((length & 0x00ff0000) >> 16);
- frame[1] = (byte)((length & 0x0000ff00) >> 8);
- frame[2] = (byte)(length & 0x000000ff);
- await SendAsync(frame);
+ uint length = Http2Limits.MinAllowedMaxFrameSize + 1;
+ await SendDataAsync(1, new byte[length].AsSpan(), endStream: true);
await WaitForConnectionErrorAsync(
ignoreNonGoAwayFrames: true,
expectedLastStreamId: 1,
expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
- expectedErrorMessage: CoreStrings.FormatHttp2ErrorFrameOverLimit(length, Http2Frame.MinAllowedMaxFrameSize));
+ expectedErrorMessage: CoreStrings.FormatHttp2ErrorFrameOverLimit(length, Http2Limits.MinAllowedMaxFrameSize));
+ }
+
+ [Fact]
+ public async Task ServerSettings_ChangesRequestMaxFrameSize()
+ {
+ var length = Http2Limits.MinAllowedMaxFrameSize + 10;
+ _connectionContext.ServiceContext.ServerOptions.Limits.Http2.MaxFrameSize = length;
+ _connection = new Http2Connection(_connectionContext);
+
+ await InitializeConnectionAsync(_echoApplication, expectedSettingsLegnth: 12);
+
+ await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
+ await SendDataAsync(1, new byte[length].AsSpan(), endStream: true);
+
+ await ExpectAsync(Http2FrameType.HEADERS,
+ withLength: 37,
+ withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
+ withStreamId: 1);
+ // The client's settings is still defaulted to Http2PeerSettings.MinAllowedMaxFrameSize so the echo response will come back in two separate frames
+ await ExpectAsync(Http2FrameType.DATA,
+ withLength: Http2Limits.MinAllowedMaxFrameSize,
+ withFlags: (byte)Http2DataFrameFlags.NONE,
+ withStreamId: 1);
+ await ExpectAsync(Http2FrameType.DATA,
+ withLength: length - Http2Limits.MinAllowedMaxFrameSize,
+ 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]
@@ -2042,7 +2069,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
await InitializeConnectionAsync(_noopApplication);
- var frame = new Http2Frame();
+ var frame = new Http2Frame(Http2Limits.MinAllowedMaxFrameSize);
frame.PrepareSettings(Http2SettingsFrameFlags.ACK);
await SendAsync(frame.Raw);
@@ -2073,6 +2100,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[InlineData(Http2SettingsParameter.SETTINGS_MAX_FRAME_SIZE, 16 * 1024 - 1, Http2ErrorCode.PROTOCOL_ERROR)]
[InlineData(Http2SettingsParameter.SETTINGS_MAX_FRAME_SIZE, 16 * 1024 * 1024, Http2ErrorCode.PROTOCOL_ERROR)]
[InlineData(Http2SettingsParameter.SETTINGS_MAX_FRAME_SIZE, uint.MaxValue, Http2ErrorCode.PROTOCOL_ERROR)]
+ [InlineData(Http2SettingsParameter.SETTINGS_HEADER_TABLE_SIZE, 4097, Http2ErrorCode.PROTOCOL_ERROR)]
public async Task SETTINGS_Received_InvalidParameterValue_ConnectionError(Http2SettingsParameter parameter, uint value, Http2ErrorCode expectedErrorCode)
{
await InitializeConnectionAsync(_noopApplication);
@@ -2160,6 +2188,60 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
expectedErrorMessage: CoreStrings.Http2ErrorInitialWindowSizeInvalid);
}
+ [Fact]
+ public async Task SETTINGS_Received_ChangesAllowedResponseMaxFrameSize()
+ {
+ // This includes the default response headers such as :status, etc
+ var defaultResponseHeaderLength = 37;
+ var headerValueLength = Http2Limits.MinAllowedMaxFrameSize;
+ // First byte is always 0
+ // Second byte is the length of header name which is 1
+ // Third byte is the header name which is A/B
+ // Next three bytes are the 7-bit integer encoding representation of the header length which is 16*1024
+ var encodedHeaderLength = 1 + 1 + 1 + 3 + headerValueLength;
+ // Adding 10 additional bytes for encoding overhead
+ var payloadLength = defaultResponseHeaderLength + encodedHeaderLength;
+
+ await InitializeConnectionAsync(context =>
+ {
+ context.Response.Headers["A"] = new string('a', headerValueLength);
+ context.Response.Headers["B"] = new string('b', headerValueLength);
+ return context.Response.Body.WriteAsync(new byte[payloadLength], 0, payloadLength);
+ });
+
+ // Update client settings
+ _clientSettings.MaxFrameSize = (uint)payloadLength;
+ await SendSettingsAsync();
+
+ // ACK
+ await ExpectAsync(Http2FrameType.SETTINGS,
+ withLength: 0,
+ withFlags: (byte)Http2SettingsFrameFlags.ACK,
+ withStreamId: 0);
+
+ // Start request
+ await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
+
+ await ExpectAsync(Http2FrameType.HEADERS,
+ withLength: defaultResponseHeaderLength + encodedHeaderLength,
+ withFlags: (byte)Http2HeadersFrameFlags.NONE,
+ withStreamId: 1);
+ await ExpectAsync(Http2FrameType.CONTINUATION,
+ withLength: encodedHeaderLength,
+ withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
+ withStreamId: 1);
+ await ExpectAsync(Http2FrameType.DATA,
+ withLength: payloadLength,
+ 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 PUSH_PROMISE_Received_ConnectionError()
{
diff --git a/test/Kestrel.InMemory.FunctionalTests/Http2/Http2TestBase.cs b/test/Kestrel.InMemory.FunctionalTests/Http2/Http2TestBase.cs
index 4fa61a11df..7449a22f2f 100644
--- a/test/Kestrel.InMemory.FunctionalTests/Http2/Http2TestBase.cs
+++ b/test/Kestrel.InMemory.FunctionalTests/Http2/Http2TestBase.cs
@@ -141,7 +141,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_echoApplication = async context =>
{
- var buffer = new byte[Http2Frame.MinAllowedMaxFrameSize];
+ var buffer = new byte[Http2Limits.MinAllowedMaxFrameSize];
var received = 0;
while ((received = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length)) > 0)
@@ -152,7 +152,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_echoWaitForAbortApplication = async context =>
{
- var buffer = new byte[Http2Frame.MinAllowedMaxFrameSize];
+ var buffer = new byte[Http2Limits.MinAllowedMaxFrameSize];
var received = 0;
while ((received = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length)) > 0)
@@ -307,7 +307,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_decodedHeaders[name.GetAsciiStringNonNullCharacters()] = value.GetAsciiStringNonNullCharacters();
}
- protected async Task InitializeConnectionAsync(RequestDelegate application)
+ protected async Task InitializeConnectionAsync(RequestDelegate application, int expectedSettingsLegnth = 6)
{
_connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(application));
@@ -315,7 +315,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendSettingsAsync();
await ExpectAsync(Http2FrameType.SETTINGS,
- withLength: 6,
+ withLength: expectedSettingsLegnth,
withFlags: 0,
withStreamId: 0);
@@ -330,7 +330,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var tcs = new TaskCompletionSource