Extract bitshift helper code, Frame cleanup #2773

This commit is contained in:
Chris Ross (ASP.NET) 2018-08-15 14:04:57 -07:00
parent ff52525134
commit aa9dde2457
24 changed files with 422 additions and 268 deletions

View File

@ -0,0 +1,44 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Buffers.Binary;
using BenchmarkDotNet.Attributes;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
public class BinaryPrimitivesBenchmark
{
private const int Iterations = 100;
private byte[] _data;
[GlobalSetup]
public void Setup()
{
_data = new byte[4];
}
[Benchmark(Baseline = true, OperationsPerInvoke = Iterations)]
public uint GetUInt32AsBitwise()
{
var v = 0u;
for (int i = 0; i < 1_000_000; i++)
{
v = (uint)((_data[0] << 24) | (_data[1] << 16) | (_data[2] << 8) | _data[3]);
}
return v;
}
[Benchmark(OperationsPerInvoke = Iterations)]
public unsafe uint GetUInt32AsBinary()
{
var v = 0u;
for (int i = 0; i < 1_000_000; i++)
{
v = BinaryPrimitives.ReadUInt32BigEndian(_data.AsSpan());
}
return v;
}
}
}

View File

@ -1,5 +1,7 @@
Compile the solution in Release mode (so Kestrel is available in release)
```
build /t:compile /p:Configuration=Release
```
To run a specific benchmark add it as parameter
```
dotnet run -f netcoreapp2.0 -c Release RequestParsing

View File

@ -564,7 +564,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
<value>More data received than specified in the Content-Length header.</value>
</data>
<data name="Http2StreamErrorAfterHeaders" xml:space="preserve">
<value>An error occured after the response headers were sent, a reset is being sent.</value>
<value>An error occurred after the response headers were sent, a reset is being sent.</value>
</data>
<data name="Http2ErrorMaxStreams" xml:space="preserve">
<value>A new stream was refused because this connection has reached its stream limit.</value>
@ -572,4 +572,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
<data name="GreaterThanZeroRequired" xml:space="preserve">
<value>A value greater than zero is required.</value>
</data>
<data name="Http2FrameMissingFields" xml:space="preserve">
<value>The frame is too short to contain the fields indicated by the given flags.</value>
</data>
</root>

View File

@ -0,0 +1,46 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
// Mimics BinaryPrimities with oddly sized units
internal class Bitshifter
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ReadUInt24BigEndian(ReadOnlySpan<byte> source)
{
return (uint)((source[0] << 16) | (source[1] << 8) | source[2]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteUInt24BigEndian(Span<byte> destination, uint value)
{
Debug.Assert(value <= 0xFF_FF_FF, value.ToString());
destination[0] = (byte)((value & 0xFF_00_00) >> 16);
destination[1] = (byte)((value & 0x00_FF_00) >> 8);
destination[2] = (byte)(value & 0x00_00_FF);
}
// Drops the highest order bit
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ReadUInt31BigEndian(ReadOnlySpan<byte> source)
{
return BinaryPrimitives.ReadUInt32BigEndian(source) & 0x7F_FF_FF_FF;
}
// Does not overwrite the highest order bit
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteUInt31BigEndian(Span<byte> destination, uint value)
{
Debug.Assert(value <= 0x7F_FF_FF_FF, value.ToString());
// Keep the highest bit
var reserved = (destination[0] & 0x80u) << 24;
BinaryPrimitives.WriteUInt32BigEndian(destination, value | reserved);
}
}
}

View File

@ -417,7 +417,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.DataHasPadding && _incomingFrame.DataPadLength >= _incomingFrame.Length)
// The Padding field is missing
if (_incomingFrame.DataPayloadOffset > _incomingFrame.PayloadLength)
{
throw new Http2ConnectionErrorException(CoreStrings.Http2FrameMissingFields, Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.DataHasPadding && _incomingFrame.DataPadLength >= _incomingFrame.PayloadLength)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorPaddingTooLong(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
@ -467,7 +473,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.HeadersHasPadding && _incomingFrame.HeadersPadLength >= _incomingFrame.Length)
// Padding or priority fields are missing
if (_incomingFrame.HeadersPayloadOffset > _incomingFrame.PayloadLength)
{
throw new Http2ConnectionErrorException(CoreStrings.Http2FrameMissingFields, Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.HeadersHasPadding && _incomingFrame.HeadersPadLength >= _incomingFrame.PayloadLength - 1)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorPaddingTooLong(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
@ -492,7 +504,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
// This is the last chance for the client to send END_STREAM
if ((_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_STREAM) == 0)
if (!_incomingFrame.HeadersEndStream)
{
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorHeadersWithTrailersNoEndStream, Http2ErrorCode.PROTOCOL_ERROR);
}
@ -501,8 +513,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_currentHeadersStream = stream;
_requestHeaderParsingState = RequestHeaderParsingState.Trailers;
var endHeaders = (_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_HEADERS) == Http2HeadersFrameFlags.END_HEADERS;
await DecodeTrailersAsync(endHeaders, _incomingFrame.HeadersPayload);
await DecodeTrailersAsync(_incomingFrame.HeadersEndHeaders, _incomingFrame.HeadersPayload);
}
else if (_incomingFrame.StreamId <= _highestOpenedStreamId)
{
@ -538,8 +549,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_currentHeadersStream.Reset();
_headerFlags = _incomingFrame.HeadersFlags;
var endHeaders = (_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_HEADERS) == Http2HeadersFrameFlags.END_HEADERS;
await DecodeHeadersAsync(application, endHeaders, _incomingFrame.HeadersPayload);
await DecodeHeadersAsync(application, _incomingFrame.HeadersEndHeaders, _incomingFrame.HeadersPayload);
}
}
@ -560,7 +570,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamSelfDependency(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.Length != 5)
if (_incomingFrame.PayloadLength != 5)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 5), Http2ErrorCode.FRAME_SIZE_ERROR);
}
@ -580,7 +590,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.Length != 4)
if (_incomingFrame.PayloadLength != 4)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 4), Http2ErrorCode.FRAME_SIZE_ERROR);
}
@ -603,9 +613,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
if ((_incomingFrame.SettingsFlags & Http2SettingsFrameFlags.ACK) == Http2SettingsFrameFlags.ACK)
if (_incomingFrame.SettingsAck)
{
if (_incomingFrame.Length != 0)
if (_incomingFrame.PayloadLength != 0)
{
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsAckLengthNotZero, Http2ErrorCode.FRAME_SIZE_ERROR);
}
@ -613,7 +623,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return Task.CompletedTask;
}
if (_incomingFrame.Length % 6 != 0)
if (_incomingFrame.PayloadLength % 6 != 0)
{
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsLengthNotMultipleOfSix, Http2ErrorCode.FRAME_SIZE_ERROR);
}
@ -666,12 +676,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.Length != 8)
if (_incomingFrame.PayloadLength != 8)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 8), Http2ErrorCode.FRAME_SIZE_ERROR);
}
if ((_incomingFrame.PingFlags & Http2PingFrameFlags.ACK) == Http2PingFrameFlags.ACK)
if (_incomingFrame.PingAck)
{
// TODO: verify that payload is equal to the outgoing PING frame
return Task.CompletedTask;
@ -704,7 +714,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.Length != 4)
if (_incomingFrame.PayloadLength != 4)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 4), Http2ErrorCode.FRAME_SIZE_ERROR);
}
@ -767,15 +777,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
}
var endHeaders = (_incomingFrame.ContinuationFlags & Http2ContinuationFrameFlags.END_HEADERS) == Http2ContinuationFrameFlags.END_HEADERS;
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
{
return DecodeTrailersAsync(endHeaders, _incomingFrame.Payload);
return DecodeTrailersAsync(_incomingFrame.ContinuationEndHeaders, _incomingFrame.Payload);
}
else
{
return DecodeHeadersAsync(application, endHeaders, _incomingFrame.Payload);
return DecodeHeadersAsync(application, _incomingFrame.ContinuationEndHeaders, _incomingFrame.Payload);
}
}

View File

@ -4,6 +4,11 @@
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
/* https://tools.ietf.org/html/rfc7540#section-6.10
+---------------------------------------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
*/
public partial class Http2Frame
{
public Http2ContinuationFrameFlags ContinuationFlags
@ -12,9 +17,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
set => Flags = (byte)value;
}
public bool ContinuationEndHeaders => (ContinuationFlags & Http2ContinuationFrameFlags.END_HEADERS) == Http2ContinuationFrameFlags.END_HEADERS;
public void PrepareContinuation(Http2ContinuationFrameFlags flags, int streamId)
{
Length = MinAllowedMaxFrameSize - HeaderLength;
PayloadLength = MinAllowedMaxFrameSize - HeaderLength;
Type = Http2FrameType.CONTINUATION;
ContinuationFlags = flags;
StreamId = streamId;

View File

@ -2,10 +2,18 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
/*
+---------------+
|Pad Length? (8)|
+---------------+-----------------------------------------------+
| Data (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
*/
public partial class Http2Frame
{
public Http2DataFrameFlags DataFlags
@ -14,23 +22,27 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
set => Flags = (byte)value;
}
public bool DataEndStream => (DataFlags & Http2DataFrameFlags.END_STREAM) == Http2DataFrameFlags.END_STREAM;
public bool DataHasPadding => (DataFlags & Http2DataFrameFlags.PADDED) == Http2DataFrameFlags.PADDED;
public byte DataPadLength
{
get => DataHasPadding ? _data[PayloadOffset] : (byte)0;
set => _data[PayloadOffset] = value;
get => DataHasPadding ? Payload[0] : (byte)0;
set => Payload[0] = value;
}
public ArraySegment<byte> DataPayload => DataHasPadding
? new ArraySegment<byte>(_data, PayloadOffset + 1, Length - DataPadLength - 1)
: new ArraySegment<byte>(_data, PayloadOffset, Length);
public int DataPayloadOffset => DataHasPadding ? 1 : 0;
private int DataPayloadLength => PayloadLength - DataPayloadOffset - DataPadLength;
public Span<byte> DataPayload => Payload.Slice(DataPayloadOffset, DataPayloadLength);
public void PrepareData(int streamId, byte? padLength = null)
{
var padded = padLength != null;
Length = MinAllowedMaxFrameSize;
PayloadLength = MinAllowedMaxFrameSize;
Type = Http2FrameType.DATA;
DataFlags = padded ? Http2DataFrameFlags.PADDED : Http2DataFrameFlags.NONE;
StreamId = streamId;
@ -38,40 +50,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
if (padded)
{
DataPadLength = padLength.Value;
Payload.Slice(Length - padLength.Value).Fill(0);
}
}
private void DataTraceFrame(ILogger logger)
{
logger.LogTrace("'DATA' Frame. Flags = {DataFlags}, PadLength = {PadLength}, PayloadLength = {PayloadLength}", DataFlags, DataPadLength, DataPayload.Count);
}
internal object ShowFlags()
{
switch (Type)
{
case Http2FrameType.CONTINUATION:
return ContinuationFlags;
case Http2FrameType.DATA:
return DataFlags;
case Http2FrameType.HEADERS:
return HeadersFlags;
case Http2FrameType.SETTINGS:
return SettingsFlags;
case Http2FrameType.PING:
return PingFlags;
// Not Implemented
case Http2FrameType.PUSH_PROMISE:
// No flags defined
case Http2FrameType.PRIORITY:
case Http2FrameType.RST_STREAM:
case Http2FrameType.GOAWAY:
case Http2FrameType.WINDOW_UPDATE:
default:
return $"0x{Flags:x}";
Payload.Slice(PayloadLength - padLength.Value).Fill(0);
}
}
}

View File

@ -1,37 +1,38 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Buffers.Binary;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
/* https://tools.ietf.org/html/rfc7540#section-6.8
+-+-------------------------------------------------------------+
|R| Last-Stream-ID (31) |
+-+-------------------------------------------------------------+
| Error Code (32) |
+---------------------------------------------------------------+
| Additional Debug Data (*) |
+---------------------------------------------------------------+
*/
public partial class Http2Frame
{
private const int ErrorCodeOffset = 4;
public int GoAwayLastStreamId
{
get => (Payload[0] << 24) | (Payload[1] << 16) | (Payload[2] << 8) | Payload[3];
set
{
Payload[0] = (byte)((value >> 24) & 0xff);
Payload[1] = (byte)((value >> 16) & 0xff);
Payload[2] = (byte)((value >> 8) & 0xff);
Payload[3] = (byte)(value & 0xff);
}
get => (int)Bitshifter.ReadUInt31BigEndian(Payload);
set => Bitshifter.WriteUInt31BigEndian(Payload, (uint)value);
}
public Http2ErrorCode GoAwayErrorCode
{
get => (Http2ErrorCode)((Payload[4] << 24) | (Payload[5] << 16) | (Payload[6] << 8) | Payload[7]);
set
{
Payload[4] = (byte)(((uint)value >> 24) & 0xff);
Payload[5] = (byte)(((uint)value >> 16) & 0xff);
Payload[6] = (byte)(((uint)value >> 8) & 0xff);
Payload[7] = (byte)((uint)value & 0xff);
}
get => (Http2ErrorCode)BinaryPrimitives.ReadUInt32BigEndian(Payload.Slice(ErrorCodeOffset));
set => BinaryPrimitives.WriteUInt32BigEndian(Payload.Slice(ErrorCodeOffset), (uint)value);
}
public void PrepareGoAway(int lastStreamId, Http2ErrorCode errorCode)
{
Length = 8;
PayloadLength = 8;
Type = Http2FrameType.GOAWAY;
Flags = 0;
StreamId = 0;

View File

@ -5,6 +5,19 @@ using System;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
/* https://tools.ietf.org/html/rfc7540#section-6.2
+---------------+
|Pad Length? (8)|
+-+-------------+-----------------------------------------------+
|E| Stream Dependency? (31) |
+-+-------------+-----------------------------------------------+
| Weight? (8) |
+-+-------------+-----------------------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
*/
public partial class Http2Frame
{
public Http2HeadersFrameFlags HeadersFlags
@ -13,57 +26,45 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
set => Flags = (byte)value;
}
public bool HeadersHasPadding => (HeadersFlags & Http2HeadersFrameFlags.PADDED) == Http2HeadersFrameFlags.PADDED;
public bool HeadersEndHeaders => (HeadersFlags & Http2HeadersFrameFlags.END_HEADERS) == Http2HeadersFrameFlags.END_HEADERS;
public byte HeadersPadLength
{
get => HeadersHasPadding ? _data[HeaderLength] : (byte)0;
set => _data[HeaderLength] = value;
}
public bool HeadersEndStream => (HeadersFlags & Http2HeadersFrameFlags.END_STREAM) == Http2HeadersFrameFlags.END_STREAM;
public bool HeadersHasPadding => (HeadersFlags & Http2HeadersFrameFlags.PADDED) == Http2HeadersFrameFlags.PADDED;
public bool HeadersHasPriority => (HeadersFlags & Http2HeadersFrameFlags.PRIORITY) == Http2HeadersFrameFlags.PRIORITY;
public byte HeadersPriority
public byte HeadersPadLength
{
get => _data[HeadersPriorityOffset];
set => _data[HeadersPriorityOffset] = value;
get => HeadersHasPadding ? Payload[0] : (byte)0;
set => Payload[0] = value;
}
private int HeadersPriorityOffset => PayloadOffset + (HeadersHasPadding ? 1 : 0) + 4;
private int HeadersStreamDependencyOffset => HeadersHasPadding ? 1 : 0;
public int HeadersStreamDependency
{
get
{
var offset = HeadersStreamDependencyOffset;
return (int)((uint)((_data[offset] << 24)
| (_data[offset + 1] << 16)
| (_data[offset + 2] << 8)
| _data[offset + 3]) & 0x7fffffff);
}
set
{
var offset = HeadersStreamDependencyOffset;
_data[offset] = (byte)((value & 0xff000000) >> 24);
_data[offset + 1] = (byte)((value & 0x00ff0000) >> 16);
_data[offset + 2] = (byte)((value & 0x0000ff00) >> 8);
_data[offset + 3] = (byte)(value & 0x000000ff);
}
get => (int)Bitshifter.ReadUInt31BigEndian(Payload.Slice(HeadersStreamDependencyOffset));
set => Bitshifter.WriteUInt31BigEndian(Payload.Slice(HeadersStreamDependencyOffset), (uint)value);
}
private int HeadersStreamDependencyOffset => PayloadOffset + (HeadersHasPadding ? 1 : 0);
private int HeadersPriorityWeightOffset => HeadersStreamDependencyOffset + 4;
public Span<byte> HeadersPayload => new Span<byte>(_data, HeadersPayloadOffset, HeadersPayloadLength);
public byte HeadersPriorityWeight
{
get => Payload[HeadersPriorityWeightOffset];
set => Payload[HeadersPriorityWeightOffset] = value;
}
private int HeadersPayloadOffset => PayloadOffset + (HeadersHasPadding ? 1 : 0) + (HeadersHasPriority ? 5 : 0);
public int HeadersPayloadOffset => (HeadersHasPadding ? 1 : 0) + (HeadersHasPriority ? 5 : 0);
private int HeadersPayloadLength => Length - ((HeadersHasPadding ? 1 : 0) + (HeadersHasPriority ? 5 : 0)) - HeadersPadLength;
private int HeadersPayloadLength => PayloadLength - HeadersPayloadOffset - HeadersPadLength;
public Span<byte> HeadersPayload => Payload.Slice(HeadersPayloadOffset, HeadersPayloadLength);
public void PrepareHeaders(Http2HeadersFrameFlags flags, int streamId)
{
Length = MinAllowedMaxFrameSize - HeaderLength;
PayloadLength = MinAllowedMaxFrameSize - HeaderLength;
Type = Http2FrameType.HEADERS;
HeadersFlags = flags;
StreamId = streamId;

View File

@ -4,6 +4,13 @@
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
/* https://tools.ietf.org/html/rfc7540#section-6.7
+---------------------------------------------------------------+
| |
| Opaque Data (64) |
| |
+---------------------------------------------------------------+
*/
public partial class Http2Frame
{
public Http2PingFrameFlags PingFlags
@ -12,9 +19,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
set => Flags = (byte)value;
}
public bool PingAck => (PingFlags & Http2PingFrameFlags.ACK) == Http2PingFrameFlags.ACK;
public void PreparePing(Http2PingFrameFlags flags)
{
Length = 8;
PayloadLength = 8;
Type = Http2FrameType.PING;
PingFlags = flags;
StreamId = 0;

View File

@ -3,50 +3,49 @@
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
/* https://tools.ietf.org/html/rfc7540#section-6.3
+-+-------------------------------------------------------------+
|E| Stream Dependency (31) |
+-+-------------+-----------------------------------------------+
| Weight (8) |
+-+-------------+
*/
public partial class Http2Frame
{
private const int PriorityWeightOffset = 4;
public int PriorityStreamDependency
{
get => ((_data[PayloadOffset] << 24)
| (_data[PayloadOffset + 1] << 16)
| (_data[PayloadOffset + 2] << 8)
| _data[PayloadOffset + 3]) & 0x7fffffff;
set
{
_data[PayloadOffset] = (byte)((value & 0x7f000000) >> 24);
_data[PayloadOffset + 1] = (byte)((value & 0x00ff0000) >> 16);
_data[PayloadOffset + 2] = (byte)((value & 0x0000ff00) >> 8);
_data[PayloadOffset + 3] = (byte)(value & 0x000000ff);
}
get => (int)Bitshifter.ReadUInt31BigEndian(Payload);
set => Bitshifter.WriteUInt31BigEndian(Payload, (uint)value);
}
public bool PriorityIsExclusive
{
get => (_data[PayloadOffset] & 0x80000000) != 0;
get => (Payload[0] & 0x80) != 0;
set
{
if (value)
{
_data[PayloadOffset] |= 0x80;
Payload[0] |= 0x80;
}
else
{
_data[PayloadOffset] &= 0x7f;
Payload[0] &= 0x7f;
}
}
}
public byte PriorityWeight
{
get => _data[PayloadOffset + 4];
set => _data[PayloadOffset] = value;
get => Payload[PriorityWeightOffset];
set => Payload[PriorityWeightOffset] = value;
}
public void PreparePriority(int streamId, int streamDependency, bool exclusive, byte weight)
{
Length = 5;
PayloadLength = 5;
Type = Http2FrameType.PRIORITY;
StreamId = streamId;
PriorityStreamDependency = streamDependency;

View File

@ -1,25 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Buffers.Binary;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
/* https://tools.ietf.org/html/rfc7540#section-6.4
+---------------------------------------------------------------+
| Error Code (32) |
+---------------------------------------------------------------+
*/
public partial class Http2Frame
{
public Http2ErrorCode RstStreamErrorCode
{
get => (Http2ErrorCode)((Payload[0] << 24) | (Payload[1] << 16) | (Payload[2] << 8) | Payload[3]);
set
{
Payload[0] = (byte)(((uint)value >> 24) & 0xff);
Payload[1] = (byte)(((uint)value >> 16) & 0xff);
Payload[2] = (byte)(((uint)value >> 8) & 0xff);
Payload[3] = (byte)((uint)value & 0xff);
}
get => (Http2ErrorCode)BinaryPrimitives.ReadUInt32BigEndian(Payload);
set => BinaryPrimitives.WriteUInt32BigEndian(Payload, (uint)value);
}
public void PrepareRstStream(int streamId, Http2ErrorCode errorCode)
{
Length = 4;
PayloadLength = 4;
Type = Http2FrameType.RST_STREAM;
Flags = 0;
StreamId = streamId;

View File

@ -3,10 +3,17 @@
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
/* https://tools.ietf.org/html/rfc7540#section-6.5.1
List of:
+-------------------------------+
| Identifier (16) |
+-------------------------------+-------------------------------+
| Value (32) |
+---------------------------------------------------------------+
*/
public partial class Http2Frame
{
private const int SettingSize = 6; // 2 bytes for the id, 4 bytes for the value.
@ -17,10 +24,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
set => Flags = (byte)value;
}
public bool SettingsAck => (SettingsFlags & Http2SettingsFrameFlags.ACK) == Http2SettingsFrameFlags.ACK;
public int SettingsCount
{
get => Length / SettingSize;
set => Length = value * SettingSize;
get => PayloadLength / SettingSize;
set => PayloadLength = value * SettingSize;
}
public IList<Http2PeerSetting> GetSettings()

View File

@ -3,23 +3,22 @@
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
/* https://tools.ietf.org/html/rfc7540#section-6.9
+-+-------------------------------------------------------------+
|R| Window Size Increment (31) |
+-+-------------------------------------------------------------+
*/
public partial class Http2Frame
{
public int WindowUpdateSizeIncrement
{
get => ((Payload[0] << 24) | (Payload[1] << 16) | (Payload[2] << 8) | Payload[3]) & 0x7fffffff;
set
{
Payload[0] = (byte)(((uint)value >> 24) & 0x7f);
Payload[1] = (byte)(((uint)value >> 16) & 0xff);
Payload[2] = (byte)(((uint)value >> 8) & 0xff);
Payload[3] = (byte)((uint)value & 0xff);
}
get => (int)Bitshifter.ReadUInt31BigEndian(Payload);
set => Bitshifter.WriteUInt31BigEndian(Payload, (uint)value);
}
public void PrepareWindowUpdate(int streamId, int sizeIncrement)
{
Length = 4;
PayloadLength = 4;
Type = Http2FrameType.WINDOW_UPDATE;
Flags = 0;
StreamId = streamId;

View File

@ -5,6 +5,17 @@ using System;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
/* https://tools.ietf.org/html/rfc7540#section-4.1
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
*/
public partial class Http2Frame
{
public const int MinAllowedMaxFrameSize = 16 * 1024;
@ -19,52 +30,60 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private readonly byte[] _data = new byte[HeaderLength + MinAllowedMaxFrameSize];
public Span<byte> Raw => new Span<byte>(_data, 0, HeaderLength + Length);
public Span<byte> Raw => new Span<byte>(_data, 0, HeaderLength + PayloadLength);
public int Length
public int PayloadLength
{
get => (_data[LengthOffset] << 16) | (_data[LengthOffset + 1] << 8) | _data[LengthOffset + 2];
set
{
_data[LengthOffset] = (byte)((value & 0x00ff0000) >> 16);
_data[LengthOffset + 1] = (byte)((value & 0x0000ff00) >> 8);
_data[LengthOffset + 2] = (byte)(value & 0x000000ff);
}
get => (int)Bitshifter.ReadUInt24BigEndian(_data.AsSpan(LengthOffset));
set => Bitshifter.WriteUInt24BigEndian(_data.AsSpan(LengthOffset), (uint)value);
}
public Http2FrameType Type
{
get => (Http2FrameType)_data[TypeOffset];
set
{
_data[TypeOffset] = (byte)value;
}
set => _data[TypeOffset] = (byte)value;
}
public byte Flags
{
get => _data[FlagsOffset];
set
{
_data[FlagsOffset] = value;
}
set => _data[FlagsOffset] = value;
}
public int StreamId
{
get => (int)((uint)((_data[StreamIdOffset] << 24)
| (_data[StreamIdOffset + 1] << 16)
| (_data[StreamIdOffset + 2] << 8)
| _data[StreamIdOffset + 3]) & 0x7fffffff);
set
{
_data[StreamIdOffset] = (byte)((value & 0xff000000) >> 24);
_data[StreamIdOffset + 1] = (byte)((value & 0x00ff0000) >> 16);
_data[StreamIdOffset + 2] = (byte)((value & 0x0000ff00) >> 8);
_data[StreamIdOffset + 3] = (byte)(value & 0x000000ff);
}
get => (int)Bitshifter.ReadUInt31BigEndian(_data.AsSpan(StreamIdOffset));
set => Bitshifter.WriteUInt31BigEndian(_data.AsSpan(StreamIdOffset), (uint)value);
}
public Span<byte> Payload => new Span<byte>(_data, PayloadOffset, Length);
public Span<byte> Payload => new Span<byte>(_data, PayloadOffset, PayloadLength);
internal object ShowFlags()
{
switch (Type)
{
case Http2FrameType.CONTINUATION:
return ContinuationFlags;
case Http2FrameType.DATA:
return DataFlags;
case Http2FrameType.HEADERS:
return HeadersFlags;
case Http2FrameType.SETTINGS:
return SettingsFlags;
case Http2FrameType.PING:
return PingFlags;
// Not Implemented
case Http2FrameType.PUSH_PROMISE:
// No flags defined
case Http2FrameType.PRIORITY:
case Http2FrameType.RST_STREAM:
case Http2FrameType.GOAWAY:
case Http2FrameType.WINDOW_UPDATE:
default:
return $"0x{Flags:x}";
}
}
}
}

View File

@ -3,7 +3,6 @@
using System;
using System.Buffers;
using System.Collections;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
@ -22,18 +21,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
var headerSlice = readableBuffer.Slice(0, Http2Frame.HeaderLength);
headerSlice.CopyTo(frame.Raw);
if (frame.Length > maxFrameSize)
var payloadLength = frame.PayloadLength;
if (payloadLength > maxFrameSize)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorFrameOverLimit(frame.Length, maxFrameSize), Http2ErrorCode.FRAME_SIZE_ERROR);
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorFrameOverLimit(payloadLength, maxFrameSize), Http2ErrorCode.FRAME_SIZE_ERROR);
}
if (readableBuffer.Length < Http2Frame.HeaderLength + frame.Length)
var frameLength = Http2Frame.HeaderLength + payloadLength;
if (readableBuffer.Length < frameLength)
{
return false;
}
readableBuffer.Slice(Http2Frame.HeaderLength, frame.Length).CopyTo(frame.Payload);
consumed = examined = readableBuffer.GetPosition(Http2Frame.HeaderLength + frame.Length);
readableBuffer.Slice(Http2Frame.HeaderLength, payloadLength).CopyTo(frame.Payload);
consumed = examined = readableBuffer.GetPosition(frameLength);
return true;
}

View File

@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
lock (_writeLock)
{
_outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS, streamId);
_outgoingFrame.Length = _continueBytes.Length;
_outgoingFrame.PayloadLength = _continueBytes.Length;
_continueBytes.CopyTo(_outgoingFrame.HeadersPayload);
return WriteFrameUnsynchronizedAsync();
@ -119,7 +119,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
var done = _hpackEncoder.BeginEncode(statusCode, EnumerateHeaders(headers), _outgoingFrame.Payload, out var payloadLength);
_outgoingFrame.Length = payloadLength;
_outgoingFrame.PayloadLength = payloadLength;
if (done)
{
@ -134,7 +134,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_outgoingFrame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);
done = _hpackEncoder.Encode(_outgoingFrame.Payload, out var length);
_outgoingFrame.Length = length;
_outgoingFrame.PayloadLength = length;
if (done)
{
@ -207,7 +207,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_outgoingFrame.DataFlags = Http2DataFrameFlags.END_STREAM;
}
_outgoingFrame.Length = unwrittenPayloadLength;
_outgoingFrame.PayloadLength = unwrittenPayloadLength;
_log.Http2FrameSending(_connectionId, _outgoingFrame);
_outputWriter.Write(_outgoingFrame.Raw);

View File

@ -274,9 +274,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
var payload = dataFrame.DataPayload;
var endStream = (dataFrame.DataFlags & Http2DataFrameFlags.END_STREAM) == Http2DataFrameFlags.END_STREAM;
var endStream = dataFrame.DataEndStream;
if (payload.Count > 0)
if (payload.Length > 0)
{
RequestBodyStarted = true;
@ -287,7 +287,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_inputFlowControl.StopWindowUpdates();
}
_inputFlowControl.Advance(payload.Count);
_inputFlowControl.Advance(payload.Length);
if (IsAborted)
{
@ -300,12 +300,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
if (InputRemaining.HasValue)
{
// https://tools.ietf.org/html/rfc7540#section-8.1.2.6
if (payload.Count > InputRemaining.Value)
if (payload.Length > InputRemaining.Value)
{
throw new Http2StreamErrorException(StreamId, CoreStrings.Http2StreamErrorMoreDataThanLength, Http2ErrorCode.PROTOCOL_ERROR);
}
InputRemaining -= payload.Count;
InputRemaining -= payload.Length;
}
RequestBodyPipe.Writer.Write(payload);

View File

@ -256,12 +256,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public void Http2FrameReceived(string connectionId, Http2Frame frame)
{
_http2FrameReceived(_logger, connectionId, frame.Type, frame.StreamId, frame.Length, frame.ShowFlags(), null);
_http2FrameReceived(_logger, connectionId, frame.Type, frame.StreamId, frame.PayloadLength, frame.ShowFlags(), null);
}
public void Http2FrameSending(string connectionId, Http2Frame frame)
{
_http2FrameSending(_logger, connectionId, frame.Type, frame.StreamId, frame.Length, frame.ShowFlags(), null);
_http2FrameSending(_logger, connectionId, frame.Type, frame.StreamId, frame.PayloadLength, frame.ShowFlags(), null);
}
public virtual void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)

View File

@ -2087,7 +2087,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
=> GetString("Http2StreamErrorMoreDataThanLength");
/// <summary>
/// An error occured after the response headers were sent, a reset is being sent.
/// An error occurred after the response headers were sent, a reset is being sent.
/// </summary>
internal static string Http2StreamErrorAfterHeaders
{
@ -2095,7 +2095,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
}
/// <summary>
/// An error occured after the response headers were sent, a reset is being sent.
/// An error occurred after the response headers were sent, a reset is being sent.
/// </summary>
internal static string FormatHttp2StreamErrorAfterHeaders()
=> GetString("Http2StreamErrorAfterHeaders");
@ -2128,6 +2128,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
internal static string FormatGreaterThanZeroRequired()
=> GetString("GreaterThanZeroRequired");
/// <summary>
/// The frame is too short to contain the fields indicated by the given flags.
/// </summary>
internal static string Http2FrameMissingFields
{
get => GetString("Http2FrameMissingFields");
}
/// <summary>
/// The frame is too short to contain the fields indicated by the given flags.
/// </summary>
internal static string FormatHttp2FrameMissingFields()
=> GetString("Http2FrameMissingFields");
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -124,7 +124,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.Equal(_helloWorldBytes, dataFrame.DataPayload);
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.DataPayload));
}
[Fact]
@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.Equal(_maxData, dataFrame.DataPayload);
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame.DataPayload));
}
[Fact]
@ -223,10 +223,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.Equal(_maxData, dataFrame1.DataPayload);
Assert.Equal(_maxData, dataFrame2.DataPayload);
Assert.Equal(_maxData, dataFrame3.DataPayload);
Assert.Equal(_maxData, dataFrame4.DataPayload);
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame1.DataPayload));
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame2.DataPayload));
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame3.DataPayload));
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame4.DataPayload));
Assert.Equal(_maxData.Length * 2, streamWindowUpdateFrame1.WindowUpdateSizeIncrement);
Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame1.WindowUpdateSizeIncrement);
Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame2.WindowUpdateSizeIncrement);
@ -261,7 +261,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.Equal(_helloWorldBytes, dataFrame.DataPayload);
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.DataPayload));
}
[Fact]
@ -324,10 +324,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
Assert.Equal(stream1DataFrame1.DataPayload, _helloBytes);
Assert.Equal(stream1DataFrame2.DataPayload, _worldBytes);
Assert.Equal(stream3DataFrame1.DataPayload, _helloBytes);
Assert.Equal(stream3DataFrame2.DataPayload, _worldBytes);
Assert.True(_helloBytes.AsSpan().SequenceEqual(stream1DataFrame1.DataPayload));
Assert.True(_worldBytes.AsSpan().SequenceEqual(stream1DataFrame2.DataPayload));
Assert.True(_helloBytes.AsSpan().SequenceEqual(stream3DataFrame1.DataPayload));
Assert.True(_worldBytes.AsSpan().SequenceEqual(stream3DataFrame2.DataPayload));
}
[Fact]
@ -417,16 +417,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
Assert.Equal(_maxData, dataFrame1.DataPayload);
Assert.Equal(_maxData, dataFrame2.DataPayload);
Assert.Equal(_maxData, dataFrame3.DataPayload);
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame1.DataPayload));
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame2.DataPayload));
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame3.DataPayload));
Assert.Equal(_maxData.Length * 2, streamWindowUpdateFrame.WindowUpdateSizeIncrement);
Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame1.WindowUpdateSizeIncrement);
Assert.Equal(_maxData, dataFrame4.DataPayload);
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame4.DataPayload));
Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame2.WindowUpdateSizeIncrement);
Assert.Equal(_maxData, dataFrame5.DataPayload);
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame5.DataPayload));
}
[Fact]
@ -521,7 +521,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.Equal(_helloWorldBytes, dataFrame.DataPayload);
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.DataPayload));
}
[Theory]
@ -533,12 +533,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
// _maxData should be 1/4th of the default initial window size + 1.
Assert.Equal(Http2PeerSettings.DefaultInitialWindowSize + 1, (uint)_maxData.Length * 4);
var maxDataMinusPadding = new ArraySegment<byte>(_maxData, 0, _maxData.Length - padLength - 1);
var maxDataMinusPadding = _maxData.AsMemory(0, _maxData.Length - padLength - 1);
await InitializeConnectionAsync(_echoApplication);
await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
await SendDataWithPaddingAsync(1, maxDataMinusPadding, padLength, endStream: false);
await SendDataWithPaddingAsync(1, maxDataMinusPadding.Span, padLength, endStream: false);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 37,
@ -546,7 +546,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
withStreamId: 1);
var dataFrame1 = await ExpectAsync(Http2FrameType.DATA,
withLength: maxDataMinusPadding.Count,
withLength: maxDataMinusPadding.Length,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
@ -569,8 +569,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.Equal(maxDataMinusPadding, dataFrame1.DataPayload);
Assert.Equal(_maxData, dataFrame2.DataPayload);
Assert.True(maxDataMinusPadding.Span.SequenceEqual(dataFrame1.DataPayload));
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame2.DataPayload));
Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame.WindowUpdateSizeIncrement);
}
@ -678,7 +678,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
ignoreNonGoAwayFrames: true,
expectedLastStreamId: 1,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA));
expectedErrorMessage: CoreStrings.Http2FrameMissingFields);
}
[Fact]
@ -1303,14 +1303,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
}
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(255)]
public async Task HEADERS_Received_PaddingEqualToFramePayloadLength_ConnectionError(byte padLength)
{
await InitializeConnectionAsync(_noopApplication);
await SendInvalidHeadersFrameAsync(1, frameLength: padLength, padLength: padLength);
// The payload length includes the pad length field
await SendInvalidHeadersFrameAsync(1, payloadLength: padLength, padLength: padLength);
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: true,
@ -1319,8 +1319,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS));
}
[Fact]
public async Task HEADERS_Received_PaddingFieldMissing_ConnectionError()
{
await InitializeConnectionAsync(_noopApplication);
await SendInvalidHeadersFrameAsync(1, payloadLength: 0, padLength: 1);
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: true,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.Http2FrameMissingFields);
}
[Theory]
[InlineData(0, 1)]
[InlineData(1, 2)]
[InlineData(254, 255)]
public async Task HEADERS_Received_PaddingGreaterThanFramePayloadLength_ConnectionError(int frameLength, byte padLength)
@ -1825,7 +1838,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: streamId);
Assert.Equal(new ArraySegment<byte>(_helloWorldBytes, 0, initialWindowSize), dataFrame.DataPayload);
Assert.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame.DataPayload));
Assert.False(writeTasks[streamId].IsCompleted);
}
@ -2445,7 +2458,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: streamId);
Assert.Equal(new ArraySegment<byte>(_helloWorldBytes, 0, initialWindowSize), dataFrame.DataPayload);
Assert.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame.DataPayload));
Assert.False(writeTasks[streamId].IsCompleted);
}
@ -2749,8 +2762,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.Equal(new ArraySegment<byte>(_helloWorldBytes, 0, initialWindowSize), dataFrame1.DataPayload);
Assert.Equal(new ArraySegment<byte>(_helloWorldBytes, initialWindowSize, initialWindowSize), dataFrame2.DataPayload);
Assert.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame1.DataPayload));
Assert.True(_helloWorldBytes.AsSpan(initialWindowSize, initialWindowSize).SequenceEqual(dataFrame2.DataPayload));
}
[Fact]
@ -2804,9 +2817,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.Equal(new ArraySegment<byte>(_helloWorldBytes, 0, 6), dataFrame1.DataPayload);
Assert.Equal(new ArraySegment<byte>(_helloWorldBytes, 6, 3), dataFrame2.DataPayload);
Assert.Equal(new ArraySegment<byte>(_helloWorldBytes, 9, 3), dataFrame3.DataPayload);
Assert.True(_helloWorldBytes.AsSpan(0, 6).SequenceEqual(dataFrame1.DataPayload));
Assert.True(_helloWorldBytes.AsSpan(6, 3).SequenceEqual(dataFrame2.DataPayload));
Assert.True(_helloWorldBytes.AsSpan(9, 3).SequenceEqual(dataFrame3.DataPayload));
}
[Fact]

View File

@ -16,7 +16,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests

View File

@ -333,7 +333,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var frame = new Http2Frame();
frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
var done = _hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length);
frame.Length = length;
frame.PayloadLength = length;
if (done)
{
@ -351,7 +351,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);
done = _hpackEncoder.Encode(frame.HeadersPayload, out length);
frame.Length = length;
frame.PayloadLength = length;
if (done)
{
@ -374,7 +374,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length);
frame.Length = 1 + length + padLength;
frame.PayloadLength = 1 + length + padLength;
frame.Payload.Slice(1 + length).Fill(0);
if (endStream)
@ -392,12 +392,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var frame = new Http2Frame();
frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PRIORITY, streamId);
frame.HeadersPriority = priority;
frame.HeadersPriorityWeight = priority;
frame.HeadersStreamDependency = streamDependency;
_hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length);
frame.Length = 5 + length;
frame.PayloadLength = 5 + length;
if (endStream)
{
@ -415,12 +415,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var frame = new Http2Frame();
frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PADDED | Http2HeadersFrameFlags.PRIORITY, streamId);
frame.HeadersPadLength = padLength;
frame.HeadersPriority = priority;
frame.HeadersPriorityWeight = priority;
frame.HeadersStreamDependency = streamDependency;
_hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length);
frame.Length = 6 + length + padLength;
frame.PayloadLength = 6 + length + padLength;
frame.Payload.Slice(6 + length).Fill(0);
if (endStream)
@ -461,7 +461,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
var frame = new Http2Frame();
frame.PrepareSettings(Http2SettingsFrameFlags.ACK);
frame.Length = length;
frame.PayloadLength = length;
return SendAsync(frame.Raw);
}
@ -477,7 +477,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
var frame = new Http2Frame();
frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults());
frame.Length = length;
frame.PayloadLength = length;
return SendAsync(frame.Raw);
}
@ -485,7 +485,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
var frame = new Http2Frame();
frame.PrepareSettings(Http2SettingsFrameFlags.NONE);
frame.Length = 6;
frame.PayloadLength = 6;
frame.Payload[0] = (byte)((ushort)parameter >> 8);
frame.Payload[1] = (byte)(ushort)parameter;
@ -500,7 +500,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
protected Task SendPushPromiseFrameAsync()
{
var frame = new Http2Frame();
frame.Length = 0;
frame.PayloadLength = 0;
frame.Type = Http2FrameType.PUSH_PROMISE;
frame.StreamId = 1;
return SendAsync(frame.Raw);
@ -512,7 +512,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
frame.PrepareHeaders(flags, streamId);
var done = _hpackEncoder.BeginEncode(headers, frame.Payload, out var length);
frame.Length = length;
frame.PayloadLength = length;
await SendAsync(frame.Raw);
@ -524,15 +524,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var frame = new Http2Frame();
frame.PrepareHeaders(flags, streamId);
frame.Length = headerBlock.Length;
frame.PayloadLength = headerBlock.Length;
headerBlock.CopyTo(frame.HeadersPayload);
return SendAsync(frame.Raw);
}
protected Task SendInvalidHeadersFrameAsync(int streamId, int frameLength, byte padLength)
protected Task SendInvalidHeadersFrameAsync(int streamId, int payloadLength, byte padLength)
{
Assert.True(padLength >= frameLength, $"{nameof(padLength)} must be greater than or equal to {nameof(frameLength)} to create an invalid frame.");
Assert.True(padLength >= payloadLength, $"{nameof(padLength)} must be greater than or equal to {nameof(payloadLength)} to create an invalid frame.");
var frame = new Http2Frame();
@ -540,7 +540,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
frame.Payload[0] = padLength;
// Set length last so .Payload can be written to
frame.Length = frameLength;
frame.PayloadLength = payloadLength;
return SendAsync(frame.Raw);
}
@ -550,7 +550,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var frame = new Http2Frame();
frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS, streamId);
frame.Length = 3;
frame.PayloadLength = 3;
// Set up an incomplete Literal Header Field w/ Incremental Indexing frame,
// with an incomplete new name
@ -567,7 +567,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
frame.PrepareContinuation(flags, streamId);
var done = _hpackEncoder.Encode(frame.Payload, out var length);
frame.Length = length;
frame.PayloadLength = length;
await SendAsync(frame.Raw);
@ -579,7 +579,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var frame = new Http2Frame();
frame.PrepareContinuation(flags, streamId);
frame.Length = payload.Length;
frame.PayloadLength = payload.Length;
payload.CopyTo(frame.Payload);
await SendAsync(frame.Raw);
@ -590,7 +590,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var frame = new Http2Frame();
frame.PrepareContinuation(flags, streamId);
frame.Length = 0;
frame.PayloadLength = 0;
return SendAsync(frame.Raw);
}
@ -600,7 +600,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var frame = new Http2Frame();
frame.PrepareContinuation(Http2ContinuationFrameFlags.END_HEADERS, streamId);
frame.Length = 3;
frame.PayloadLength = 3;
// Set up an incomplete Literal Header Field w/ Incremental Indexing frame,
// with an incomplete new name
@ -616,7 +616,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var frame = new Http2Frame();
frame.PrepareData(streamId);
frame.Length = data.Length;
frame.PayloadLength = data.Length;
frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE;
data.CopyTo(frame.DataPayload);
@ -628,7 +628,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var frame = new Http2Frame();
frame.PrepareData(streamId, padLength);
frame.Length = data.Length + 1 + padLength;
frame.PayloadLength = data.Length + 1 + padLength;
data.CopyTo(frame.DataPayload);
if (endStream)
@ -650,7 +650,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
frame.Payload[0] = padLength;
// Set length last so .Payload can be written to
frame.Length = frameLength;
frame.PayloadLength = frameLength;
return SendAsync(frame.Raw);
}
@ -666,7 +666,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
var pingFrame = new Http2Frame();
pingFrame.PreparePing(Http2PingFrameFlags.NONE);
pingFrame.Length = length;
pingFrame.PayloadLength = length;
return SendAsync(pingFrame.Raw);
}
@ -691,7 +691,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
var priorityFrame = new Http2Frame();
priorityFrame.PreparePriority(streamId, streamDependency: 0, exclusive: false, weight: 0);
priorityFrame.Length = length;
priorityFrame.PayloadLength = length;
return SendAsync(priorityFrame.Raw);
}
@ -706,7 +706,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
var frame = new Http2Frame();
frame.PrepareRstStream(streamId, Http2ErrorCode.CANCEL);
frame.Length = length;
frame.PayloadLength = length;
return SendAsync(frame.Raw);
}
@ -736,7 +736,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
var frame = new Http2Frame();
frame.PrepareWindowUpdate(streamId, sizeIncrement);
frame.Length = length;
frame.PayloadLength = length;
return SendAsync(frame.Raw);
}
@ -745,7 +745,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var frame = new Http2Frame();
frame.StreamId = streamId;
frame.Type = (Http2FrameType)frameType;
frame.Length = 0;
frame.PayloadLength = 0;
return SendAsync(frame.Raw);
}
@ -786,7 +786,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var frame = await ReceiveFrameAsync();
Assert.Equal(type, frame.Type);
Assert.Equal(withLength, frame.Length);
Assert.Equal(withLength, frame.PayloadLength);
Assert.Equal(withFlags, frame.Flags);
Assert.Equal(withStreamId, frame.StreamId);
@ -808,7 +808,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
protected void VerifyGoAway(Http2Frame frame, int expectedLastStreamId, Http2ErrorCode expectedErrorCode)
{
Assert.Equal(Http2FrameType.GOAWAY, frame.Type);
Assert.Equal(8, frame.Length);
Assert.Equal(8, frame.PayloadLength);
Assert.Equal(0, frame.Flags);
Assert.Equal(0, frame.StreamId);
Assert.Equal(expectedLastStreamId, frame.GoAwayLastStreamId);
@ -845,7 +845,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var frame = await ReceiveFrameAsync();
Assert.Equal(Http2FrameType.RST_STREAM, frame.Type);
Assert.Equal(4, frame.Length);
Assert.Equal(4, frame.PayloadLength);
Assert.Equal(0, frame.Flags);
Assert.Equal(expectedStreamId, frame.StreamId);
Assert.Equal(expectedErrorCode, frame.RstStreamErrorCode);

View File

@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.Http2
}
Assert.Equal(Http2FrameType.GOAWAY, frame.Type);
Assert.Equal(8, frame.Length);
Assert.Equal(8, frame.PayloadLength);
Assert.Equal(0, frame.Flags);
Assert.Equal(0, frame.StreamId);
Assert.Equal(expectedLastStreamId, frame.GoAwayLastStreamId);