Enable hpack/4.2 - Maximum Table Size test

- Ensure dynamic tables size updates occur at the beginning of the header block
This commit is contained in:
John Luo 2018-09-11 21:42:24 -07:00
parent 025aca52df
commit 69ff195f66
5 changed files with 121 additions and 40 deletions

View File

@ -578,4 +578,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
<data name="ArgumentOutOfRange" xml:space="preserve"> <data name="ArgumentOutOfRange" xml:space="preserve">
<value>A value between {min} and {max} is required.</value> <value>A value between {min} and {max} is required.</value>
</data> </data>
<data name="HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock" xml:space="preserve">
<value>Dynamic tables size update did not occur at the beginning of the first header block.</value>
</data>
</root> </root>

View File

@ -94,6 +94,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
private int _headerValueLength; private int _headerValueLength;
private bool _index; private bool _index;
private bool _huffman; private bool _huffman;
private bool _headersObserved;
public HPackDecoder(int maxDynamicTableSize) public HPackDecoder(int maxDynamicTableSize)
: this(maxDynamicTableSize, new DynamicTable(maxDynamicTableSize)) : this(maxDynamicTableSize, new DynamicTable(maxDynamicTableSize))
@ -115,19 +116,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
OnByte(data[i], handler); OnByte(data[i], handler);
} }
if (endHeaders && _state != State.Ready) if (endHeaders)
{ {
throw new HPackDecodingException(CoreStrings.HPackErrorIncompleteHeaderBlock); if (_state != State.Ready)
{
throw new HPackDecodingException(CoreStrings.HPackErrorIncompleteHeaderBlock);
}
_headersObserved = false;
} }
} }
public void OnByte(byte b, IHttpHeadersHandler handler) private void OnByte(byte b, IHttpHeadersHandler handler)
{ {
switch (_state) switch (_state)
{ {
case State.Ready: case State.Ready:
if ((b & IndexedHeaderFieldMask) == IndexedHeaderFieldRepresentation) if ((b & IndexedHeaderFieldMask) == IndexedHeaderFieldRepresentation)
{ {
_headersObserved = true;
var val = b & ~IndexedHeaderFieldMask; var val = b & ~IndexedHeaderFieldMask;
if (_integerDecoder.BeginDecode((byte)val, IndexedHeaderFieldPrefix)) if (_integerDecoder.BeginDecode((byte)val, IndexedHeaderFieldPrefix))
@ -141,6 +148,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
} }
else if ((b & LiteralHeaderFieldWithIncrementalIndexingMask) == LiteralHeaderFieldWithIncrementalIndexingRepresentation) else if ((b & LiteralHeaderFieldWithIncrementalIndexingMask) == LiteralHeaderFieldWithIncrementalIndexingRepresentation)
{ {
_headersObserved = true;
_index = true; _index = true;
var val = b & ~LiteralHeaderFieldWithIncrementalIndexingMask; var val = b & ~LiteralHeaderFieldWithIncrementalIndexingMask;
@ -159,6 +167,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
} }
else if ((b & LiteralHeaderFieldWithoutIndexingMask) == LiteralHeaderFieldWithoutIndexingRepresentation) else if ((b & LiteralHeaderFieldWithoutIndexingMask) == LiteralHeaderFieldWithoutIndexingRepresentation)
{ {
_headersObserved = true;
_index = false; _index = false;
var val = b & ~LiteralHeaderFieldWithoutIndexingMask; var val = b & ~LiteralHeaderFieldWithoutIndexingMask;
@ -177,6 +186,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
} }
else if ((b & LiteralHeaderFieldNeverIndexedMask) == LiteralHeaderFieldNeverIndexedRepresentation) else if ((b & LiteralHeaderFieldNeverIndexedMask) == LiteralHeaderFieldNeverIndexedRepresentation)
{ {
_headersObserved = true;
_index = false; _index = false;
var val = b & ~LiteralHeaderFieldNeverIndexedMask; var val = b & ~LiteralHeaderFieldNeverIndexedMask;
@ -195,10 +205,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
} }
else if ((b & DynamicTableSizeUpdateMask) == DynamicTableSizeUpdateRepresentation) else if ((b & DynamicTableSizeUpdateMask) == DynamicTableSizeUpdateRepresentation)
{ {
// https://tools.ietf.org/html/rfc7541#section-4.2
// This dynamic table size
// update MUST occur at the beginning of the first header block
// following the change to the dynamic table size.
if (_headersObserved)
{
throw new HPackDecodingException(CoreStrings.HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock);
}
if (_integerDecoder.BeginDecode((byte)(b & ~DynamicTableSizeUpdateMask), DynamicTableSizeUpdatePrefix)) if (_integerDecoder.BeginDecode((byte)(b & ~DynamicTableSizeUpdateMask), DynamicTableSizeUpdatePrefix))
{ {
// TODO: validate that it's less than what's defined via SETTINGS SetDynamicHeaderTableSize(_integerDecoder.Value);
_dynamicTable.Resize(_integerDecoder.Value);
} }
else else
{ {
@ -295,13 +313,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
case State.DynamicTableSizeUpdate: case State.DynamicTableSizeUpdate:
if (_integerDecoder.Decode(b)) if (_integerDecoder.Decode(b))
{ {
if (_integerDecoder.Value > _maxDynamicTableSize) SetDynamicHeaderTableSize(_integerDecoder.Value);
{
throw new HPackDecodingException(
CoreStrings.FormatHPackErrorDynamicTableSizeUpdateTooLarge(_integerDecoder.Value, _maxDynamicTableSize));
}
_dynamicTable.Resize(_integerDecoder.Value);
_state = State.Ready; _state = State.Ready;
} }
@ -402,5 +414,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
throw new HPackDecodingException(CoreStrings.FormatHPackErrorIndexOutOfRange(index), ex); throw new HPackDecodingException(CoreStrings.FormatHPackErrorIndexOutOfRange(index), ex);
} }
} }
private void SetDynamicHeaderTableSize(int size)
{
if (size > _maxDynamicTableSize)
{
throw new HPackDecodingException(
CoreStrings.FormatHPackErrorDynamicTableSizeUpdateTooLarge(size, _maxDynamicTableSize));
}
_dynamicTable.Resize(size);
}
} }
} }

View File

@ -2156,6 +2156,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
internal static string FormatArgumentOutOfRange(object min, object max) internal static string FormatArgumentOutOfRange(object min, object max)
=> string.Format(CultureInfo.CurrentCulture, GetString("ArgumentOutOfRange", "min", "max"), min, max); => string.Format(CultureInfo.CurrentCulture, GetString("ArgumentOutOfRange", "min", "max"), min, max);
/// <summary>
/// Dynamic tables size update did not occur at the beginning of the first header block.
/// </summary>
internal static string HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock
{
get => GetString("HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock");
}
/// <summary>
/// Dynamic tables size update did not occur at the beginning of the first header block.
/// </summary>
internal static string FormatHPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock()
=> GetString("HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock");
private static string GetString(string name, params string[] formatterNames) private static string GetString(string name, params string[] formatterNames)
{ {
var value = _resourceManager.GetString(name); var value = _resourceManager.GetString(name);

View File

@ -375,6 +375,47 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Empty(_decodedHeaders); Assert.Empty(_decodedHeaders);
} }
[Fact]
public void DecodesDynamicTableSizeUpdate_AfterIndexedHeaderStatic_Error()
{
// 001 (Dynamic Table Size Update)
// 11110 (30 encoded with 5-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation)
Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize);
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(_indexedHeaderStatic.Concat(new byte[] { 0x3e }).ToArray(), endHeaders: true, handler: this));
Assert.Equal(CoreStrings.HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock, exception.Message);
}
[Fact]
public void DecodesDynamicTableSizeUpdate_AfterIndexedHeaderStatic_SubsequentDecodeCall_Error()
{
Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize);
_decoder.Decode(_indexedHeaderStatic, endHeaders: false, handler: this);
Assert.Equal("GET", _decodedHeaders[HeaderNames.Method]);
// 001 (Dynamic Table Size Update)
// 11110 (30 encoded with 5-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation)
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new byte[] { 0x3e }, endHeaders: true, handler: this));
Assert.Equal(CoreStrings.HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock, exception.Message);
}
[Fact]
public void DecodesDynamicTableSizeUpdate_AfterIndexedHeaderStatic_ResetAfterEndHeaders_Succeeds()
{
Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize);
_decoder.Decode(_indexedHeaderStatic, endHeaders: true, handler: this);
Assert.Equal("GET", _decodedHeaders[HeaderNames.Method]);
// 001 (Dynamic Table Size Update)
// 11110 (30 encoded with 5-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation)
_decoder.Decode(new byte[] { 0x3e }, endHeaders: true, handler: this);
Assert.Equal(30, _dynamicTable.MaxSize);
}
[Fact] [Fact]
public void DecodesDynamicTableSizeUpdate_GreaterThanLimit_Error() public void DecodesDynamicTableSizeUpdate_GreaterThanLimit_Error()
{ {

View File

@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
get get
{ {
var dataset = new TheoryData<H2SpecTestCase>(); var dataset = new TheoryData<H2SpecTestCase>();
var toSkip = new[] { "hpack/4.2/1", "http2/5.1/8" }; var toSkip = new[] { "http2/5.1/8" };
foreach (var testcase in H2SpecCommands.EnumerateTestCases()) foreach (var testcase in H2SpecCommands.EnumerateTestCases())
{ {