Change how HTTP/2 frames are parsed and generated #2858

This commit is contained in:
Chris Ross (ASP.NET) 2018-09-01 02:03:32 -07:00
parent 2999aa54cd
commit b8423b8530
24 changed files with 1073 additions and 582 deletions

View File

@ -572,13 +572,13 @@ 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>
<data name="ArgumentOutOfRange" xml:space="preserve">
<value>A value between {min} and {max} is required.</value>
</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>
<data name="HPackErrorNotEnoughBuffer" xml:space="preserve">
<value>The given buffer was too small to encode any headers.</value>
</data>
</root>

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Buffers;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
@ -109,11 +110,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
_dynamicTable = dynamicTable;
}
public void Decode(Span<byte> data, bool endHeaders, IHttpHeadersHandler handler)
public void Decode(ReadOnlySequence<byte> data, bool endHeaders, IHttpHeadersHandler handler)
{
for (var i = 0; i < data.Length; i++)
foreach (var segment in data)
{
OnByte(data[i], handler);
var span = segment.Span;
for (var i = 0; i < span.Length; i++)
{
OnByte(span[i], handler);
}
}
if (endHeaders)

View File

@ -24,13 +24,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
_enumerator.MoveNext();
var statusCodeLength = EncodeStatusCode(statusCode, buffer);
var done = Encode(buffer.Slice(statusCodeLength), out var headersLength);
var done = Encode(buffer.Slice(statusCodeLength), throwIfNoneEncoded: false, out var headersLength);
length = statusCodeLength + headersLength;
return done;
}
public bool Encode(Span<byte> buffer, out int length)
{
return Encode(buffer, throwIfNoneEncoded: true, out length);
}
private bool Encode(Span<byte> buffer, bool throwIfNoneEncoded, out int length)
{
length = 0;
@ -38,6 +43,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
{
if (!EncodeHeader(_enumerator.Current.Key, _enumerator.Current.Value, buffer.Slice(length), out var headerLength))
{
if (length == 0 && throwIfNoneEncoded)
{
throw new HPackEncodingException(CoreStrings.HPackErrorNotEnoughBuffer);
}
return false;
}

View File

@ -0,0 +1,19 @@
// 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;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
{
public class HPackEncodingException : Exception
{
public HPackEncodingException(string message)
: base(message)
{
}
public HPackEncodingException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@ -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;
private readonly Http2Frame _incomingFrame = new Http2Frame();
private Http2Stream _currentHeadersStream;
private RequestHeaderParsingState _requestHeaderParsingState;
@ -92,7 +92,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_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);
_serverSettings.MaxHeaderListSize = (uint)context.ServiceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize;
}
@ -188,16 +187,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
var result = await Input.ReadAsync();
var readableBuffer = result.Buffer;
var consumed = readableBuffer.Start;
var examined = readableBuffer.End;
var examined = readableBuffer.Start;
try
{
if (!readableBuffer.IsEmpty)
{
if (Http2FrameReader.ReadFrame(readableBuffer, _incomingFrame, _serverSettings.MaxFrameSize, out consumed, out examined))
if (Http2FrameReader.ReadFrame(readableBuffer, _incomingFrame, _serverSettings.MaxFrameSize, out var framePayload))
{
Log.Http2FrameReceived(ConnectionId, _incomingFrame);
await ProcessFrameAsync(application);
consumed = examined = framePayload.End;
await ProcessFrameAsync(application, framePayload);
}
else
{
examined = readableBuffer.End;
}
}
@ -372,7 +376,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return true;
}
private Task ProcessFrameAsync<TContext>(IHttpApplication<TContext> application)
private Task ProcessFrameAsync<TContext>(IHttpApplication<TContext> application, ReadOnlySequence<byte> payload)
{
// http://httpwg.org/specs/rfc7540.html#rfc.section.5.1.1
// Streams initiated by a client MUST use odd-numbered stream identifiers; ...
@ -386,31 +390,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
switch (_incomingFrame.Type)
{
case Http2FrameType.DATA:
return ProcessDataFrameAsync();
return ProcessDataFrameAsync(payload);
case Http2FrameType.HEADERS:
return ProcessHeadersFrameAsync(application);
return ProcessHeadersFrameAsync(application, payload);
case Http2FrameType.PRIORITY:
return ProcessPriorityFrameAsync();
case Http2FrameType.RST_STREAM:
return ProcessRstStreamFrameAsync();
case Http2FrameType.SETTINGS:
return ProcessSettingsFrameAsync();
return ProcessSettingsFrameAsync(payload);
case Http2FrameType.PUSH_PROMISE:
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorPushPromiseReceived, Http2ErrorCode.PROTOCOL_ERROR);
case Http2FrameType.PING:
return ProcessPingFrameAsync();
return ProcessPingFrameAsync(payload);
case Http2FrameType.GOAWAY:
return ProcessGoAwayFrameAsync();
case Http2FrameType.WINDOW_UPDATE:
return ProcessWindowUpdateFrameAsync();
case Http2FrameType.CONTINUATION:
return ProcessContinuationFrameAsync(application);
return ProcessContinuationFrameAsync(application, payload);
default:
return ProcessUnknownFrameAsync();
}
}
private Task ProcessDataFrameAsync()
private Task ProcessDataFrameAsync(ReadOnlySequence<byte> payload)
{
if (_currentHeadersStream != null)
{
@ -422,12 +426,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
// 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);
@ -449,7 +447,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED);
}
return stream.OnDataAsync(_incomingFrame);
return stream.OnDataAsync(_incomingFrame, payload);
}
// If we couldn't find the stream, it was either alive previously but closed with
@ -466,7 +464,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED);
}
private async Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> application)
private Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> application, ReadOnlySequence<byte> payload)
{
if (_currentHeadersStream != null)
{
@ -478,12 +476,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
// 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);
@ -518,7 +510,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_currentHeadersStream = stream;
_requestHeaderParsingState = RequestHeaderParsingState.Trailers;
await DecodeTrailersAsync(_incomingFrame.HeadersEndHeaders, _incomingFrame.HeadersPayload);
var headersPayload = payload.Slice(0, _incomingFrame.HeadersPayloadLength); // Minus padding
return DecodeTrailersAsync(_incomingFrame.HeadersEndHeaders, headersPayload);
}
else if (_incomingFrame.StreamId <= _highestOpenedStreamId)
{
@ -554,7 +547,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_currentHeadersStream.Reset();
_headerFlags = _incomingFrame.HeadersFlags;
await DecodeHeadersAsync(application, _incomingFrame.HeadersEndHeaders, _incomingFrame.HeadersPayload);
var headersPayload = payload.Slice(0, _incomingFrame.HeadersPayloadLength); // Minus padding
return DecodeHeadersAsync(application, _incomingFrame.HeadersEndHeaders, headersPayload);
}
}
@ -606,7 +600,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return Task.CompletedTask;
}
private Task ProcessSettingsFrameAsync()
private Task ProcessSettingsFrameAsync(ReadOnlySequence<byte> payload)
{
if (_currentHeadersStream != null)
{
@ -639,14 +633,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
var previousInitialWindowSize = (int)_clientSettings.InitialWindowSize;
var previousMaxFrameSize = _clientSettings.MaxFrameSize;
_clientSettings.Update(_incomingFrame.GetSettings());
_clientSettings.Update(Http2FrameReader.ReadSettings(payload));
// Ack before we update the windows, they could send data immediately.
var ackTask = _frameWriter.WriteSettingsAckAsync();
var ackTask = _frameWriter.WriteSettingsAckAsync();
if (_clientSettings.MaxFrameSize != previousMaxFrameSize)
{
_frameWriter.UpdateMaxFrameSize(_clientSettings.MaxFrameSize);
// Don't let the client choose an arbitrarily large size, this will be used for response buffers.
_frameWriter.UpdateMaxFrameSize(Math.Min(_clientSettings.MaxFrameSize, _serverSettings.MaxFrameSize));
}
// This difference can be negative.
@ -676,7 +671,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
}
private Task ProcessPingFrameAsync()
private Task ProcessPingFrameAsync(ReadOnlySequence<byte> payload)
{
if (_currentHeadersStream != null)
{
@ -699,7 +694,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return Task.CompletedTask;
}
return _frameWriter.WritePingAsync(Http2PingFrameFlags.ACK, _incomingFrame.Payload);
return _frameWriter.WritePingAsync(Http2PingFrameFlags.ACK, payload);
}
private Task ProcessGoAwayFrameAsync()
@ -777,7 +772,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return Task.CompletedTask;
}
private Task ProcessContinuationFrameAsync<TContext>(IHttpApplication<TContext> application)
private Task ProcessContinuationFrameAsync<TContext>(IHttpApplication<TContext> application, ReadOnlySequence<byte> payload)
{
if (_currentHeadersStream == null)
{
@ -791,11 +786,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
{
return DecodeTrailersAsync(_incomingFrame.ContinuationEndHeaders, _incomingFrame.Payload);
return DecodeTrailersAsync(_incomingFrame.ContinuationEndHeaders, payload);
}
else
{
return DecodeHeadersAsync(application, _incomingFrame.ContinuationEndHeaders, _incomingFrame.Payload);
return DecodeHeadersAsync(application, _incomingFrame.ContinuationEndHeaders, payload);
}
}
@ -809,7 +804,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return Task.CompletedTask;
}
private Task DecodeHeadersAsync<TContext>(IHttpApplication<TContext> application, bool endHeaders, Span<byte> payload)
private Task DecodeHeadersAsync<TContext>(IHttpApplication<TContext> application, bool endHeaders, ReadOnlySequence<byte> payload)
{
try
{
@ -838,7 +833,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return Task.CompletedTask;
}
private Task DecodeTrailersAsync(bool endHeaders, Span<byte> payload)
private Task DecodeTrailersAsync(bool endHeaders, ReadOnlySequence<byte> payload)
{
_hpackDecoder.Decode(payload, endHeaders, handler: this);

View File

@ -1,7 +1,6 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
/* https://tools.ietf.org/html/rfc7540#section-6.10
@ -21,7 +20,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public void PrepareContinuation(Http2ContinuationFrameFlags flags, int streamId)
{
PayloadLength = (int)_maxFrameSize;
PayloadLength = 0;
Type = Http2FrameType.CONTINUATION;
ContinuationFlags = flags;
StreamId = streamId;

View File

@ -1,8 +1,6 @@
// 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;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
/*
@ -26,32 +24,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public bool DataHasPadding => (DataFlags & Http2DataFrameFlags.PADDED) == Http2DataFrameFlags.PADDED;
public byte DataPadLength
{
get => DataHasPadding ? Payload[0] : (byte)0;
set => Payload[0] = value;
}
public byte DataPadLength { get; set; }
public int DataPayloadOffset => DataHasPadding ? 1 : 0;
private int DataPayloadOffset => DataHasPadding ? 1 : 0;
private int DataPayloadLength => PayloadLength - DataPayloadOffset - DataPadLength;
public Span<byte> DataPayload => Payload.Slice(DataPayloadOffset, DataPayloadLength);
public int DataPayloadLength => PayloadLength - DataPayloadOffset - DataPadLength;
public void PrepareData(int streamId, byte? padLength = null)
{
var padded = padLength != null;
PayloadLength = (int)_maxFrameSize;
PayloadLength = 0;
Type = Http2FrameType.DATA;
DataFlags = padded ? Http2DataFrameFlags.PADDED : Http2DataFrameFlags.NONE;
DataFlags = padLength.HasValue ? Http2DataFrameFlags.PADDED : Http2DataFrameFlags.NONE;
StreamId = streamId;
if (padded)
{
DataPadLength = padLength.Value;
Payload.Slice(PayloadLength - padLength.Value).Fill(0);
}
DataPadLength = padLength ?? 0;
}
}
}

View File

@ -1,8 +1,6 @@
// 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
@ -16,19 +14,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
*/
public partial class Http2Frame
{
private const int ErrorCodeOffset = 4;
public int GoAwayLastStreamId { get; set; }
public int GoAwayLastStreamId
{
get => (int)Bitshifter.ReadUInt31BigEndian(Payload);
set => Bitshifter.WriteUInt31BigEndian(Payload, (uint)value);
}
public Http2ErrorCode GoAwayErrorCode
{
get => (Http2ErrorCode)BinaryPrimitives.ReadUInt32BigEndian(Payload.Slice(ErrorCodeOffset));
set => BinaryPrimitives.WriteUInt32BigEndian(Payload.Slice(ErrorCodeOffset), (uint)value);
}
public Http2ErrorCode GoAwayErrorCode { get; set; }
public void PrepareGoAway(int lastStreamId, Http2ErrorCode errorCode)
{

View File

@ -1,8 +1,6 @@
// 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;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
/* https://tools.ietf.org/html/rfc7540#section-6.2
@ -34,37 +32,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public bool HeadersHasPriority => (HeadersFlags & Http2HeadersFrameFlags.PRIORITY) == Http2HeadersFrameFlags.PRIORITY;
public byte HeadersPadLength
{
get => HeadersHasPadding ? Payload[0] : (byte)0;
set => Payload[0] = value;
}
public byte HeadersPadLength { get; set; }
private int HeadersStreamDependencyOffset => HeadersHasPadding ? 1 : 0;
public int HeadersStreamDependency { get; set; }
public int HeadersStreamDependency
{
get => (int)Bitshifter.ReadUInt31BigEndian(Payload.Slice(HeadersStreamDependencyOffset));
set => Bitshifter.WriteUInt31BigEndian(Payload.Slice(HeadersStreamDependencyOffset), (uint)value);
}
public byte HeadersPriorityWeight { get; set; }
private int HeadersPriorityWeightOffset => HeadersStreamDependencyOffset + 4;
private int HeadersPayloadOffset => (HeadersHasPadding ? 1 : 0) + (HeadersHasPriority ? 5 : 0);
public byte HeadersPriorityWeight
{
get => Payload[HeadersPriorityWeightOffset];
set => Payload[HeadersPriorityWeightOffset] = value;
}
public int HeadersPayloadOffset => (HeadersHasPadding ? 1 : 0) + (HeadersHasPriority ? 5 : 0);
private int HeadersPayloadLength => PayloadLength - HeadersPayloadOffset - HeadersPadLength;
public Span<byte> HeadersPayload => Payload.Slice(HeadersPayloadOffset, HeadersPayloadLength);
public int HeadersPayloadLength => PayloadLength - HeadersPayloadOffset - HeadersPadLength;
public void PrepareHeaders(Http2HeadersFrameFlags flags, int streamId)
{
PayloadLength = (int)_maxFrameSize;
PayloadLength = 0;
Type = Http2FrameType.HEADERS;
HeadersFlags = flags;
StreamId = streamId;

View File

@ -1,7 +1,6 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
/* https://tools.ietf.org/html/rfc7540#section-6.7

View File

@ -12,36 +12,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
*/
public partial class Http2Frame
{
private const int PriorityWeightOffset = 4;
public int PriorityStreamDependency { get; set; }
public int PriorityStreamDependency
{
get => (int)Bitshifter.ReadUInt31BigEndian(Payload);
set => Bitshifter.WriteUInt31BigEndian(Payload, (uint)value);
}
public bool PriorityIsExclusive
{
get => (Payload[0] & 0x80) != 0;
set
{
if (value)
{
Payload[0] |= 0x80;
}
else
{
Payload[0] &= 0x7f;
}
}
}
public byte PriorityWeight
{
get => Payload[PriorityWeightOffset];
set => Payload[PriorityWeightOffset] = value;
}
public bool PriorityIsExclusive { get; set; }
public byte PriorityWeight { get; set; }
public void PreparePriority(int streamId, int streamDependency, bool exclusive, byte weight)
{

View File

@ -1,8 +1,6 @@
// 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
@ -12,11 +10,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
*/
public partial class Http2Frame
{
public Http2ErrorCode RstStreamErrorCode
{
get => (Http2ErrorCode)BinaryPrimitives.ReadUInt32BigEndian(Payload);
set => BinaryPrimitives.WriteUInt32BigEndian(Payload, (uint)value);
}
public Http2ErrorCode RstStreamErrorCode { get; set; }
public void PrepareRstStream(int streamId, Http2ErrorCode errorCode)
{

View File

@ -1,9 +1,6 @@
// 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;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
/* https://tools.ietf.org/html/rfc7540#section-6.5.1
@ -16,8 +13,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
*/
public partial class Http2Frame
{
internal const int SettingSize = 6; // 2 bytes for the id, 4 bytes for the value.
public Http2SettingsFrameFlags SettingsFlags
{
get => (Http2SettingsFrameFlags)Flags;
@ -26,51 +21,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public bool SettingsAck => (SettingsFlags & Http2SettingsFrameFlags.ACK) == Http2SettingsFrameFlags.ACK;
public int SettingsCount
public void PrepareSettings(Http2SettingsFrameFlags flags)
{
get => PayloadLength / SettingSize;
set => PayloadLength = value * SettingSize;
}
public IList<Http2PeerSetting> GetSettings()
{
var settings = new Http2PeerSetting[SettingsCount];
for (int i = 0; i < settings.Length; i++)
{
settings[i] = GetSetting(i);
}
return settings;
}
private Http2PeerSetting GetSetting(int index)
{
var offset = index * SettingSize;
var payload = Payload.Slice(offset);
var id = (Http2SettingsParameter)BinaryPrimitives.ReadUInt16BigEndian(payload);
var value = BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(2));
return new Http2PeerSetting(id, value);
}
public void PrepareSettings(Http2SettingsFrameFlags flags, IList<Http2PeerSetting> settings = null)
{
var settingCount = settings?.Count ?? 0;
SettingsCount = settingCount;
PayloadLength = 0;
Type = Http2FrameType.SETTINGS;
SettingsFlags = flags;
StreamId = 0;
for (int i = 0; i < settingCount; i++)
{
SetSetting(i, settings[i]);
}
}
private void SetSetting(int index, Http2PeerSetting setting)
{
var offset = index * SettingSize;
var payload = Payload.Slice(offset);
BinaryPrimitives.WriteUInt16BigEndian(payload, (ushort)setting.Parameter);
BinaryPrimitives.WriteUInt32BigEndian(payload.Slice(2), setting.Value);
}
}
}

View File

@ -10,11 +10,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
*/
public partial class Http2Frame
{
public int WindowUpdateSizeIncrement
{
get => (int)Bitshifter.ReadUInt31BigEndian(Payload);
set => Bitshifter.WriteUInt31BigEndian(Payload, (uint)value);
}
public int WindowUpdateSizeIncrement { get; set; }
public void PrepareWindowUpdate(int streamId, int sizeIncrement)
{

View File

@ -1,8 +1,6 @@
// 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;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
/* https://tools.ietf.org/html/rfc7540#section-4.1
@ -18,51 +16,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
*/
public partial class Http2Frame
{
public const int HeaderLength = 9;
public int PayloadLength { get; set; }
private const int LengthOffset = 0;
private const int TypeOffset = 3;
private const int FlagsOffset = 4;
private const int StreamIdOffset = 5;
private const int PayloadOffset = 9;
public Http2FrameType Type { get; set; }
private uint _maxFrameSize;
public byte Flags { get; set; }
private readonly byte[] _data;
public Http2Frame(uint maxFrameSize)
{
_maxFrameSize = maxFrameSize;
_data = new byte[HeaderLength + _maxFrameSize];
}
public Span<byte> Raw => new Span<byte>(_data, 0, HeaderLength + PayloadLength);
public int PayloadLength
{
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;
}
public byte Flags
{
get => _data[FlagsOffset];
set => _data[FlagsOffset] = value;
}
public int StreamId
{
get => (int)Bitshifter.ReadUInt31BigEndian(_data.AsSpan(StreamIdOffset));
set => Bitshifter.WriteUInt31BigEndian(_data.AsSpan(StreamIdOffset), (uint)value);
}
public Span<byte> Payload => new Span<byte>(_data, PayloadOffset, PayloadLength);
public int StreamId { get; set; }
internal object ShowFlags()
{
@ -91,5 +51,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return $"0x{Flags:x}";
}
}
public override string ToString()
{
return $"{Type} Stream: {StreamId} Length: {PayloadLength} Flags: {ShowFlags()}";
}
}
}

View File

@ -3,40 +3,241 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
public static class Http2FrameReader
{
public static bool ReadFrame(ReadOnlySequence<byte> readableBuffer, Http2Frame frame, uint maxFrameSize, out SequencePosition consumed, out SequencePosition examined)
{
consumed = readableBuffer.Start;
examined = readableBuffer.End;
/* https://tools.ietf.org/html/rfc7540#section-4.1
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
*/
public const int HeaderLength = 9;
if (readableBuffer.Length < Http2Frame.HeaderLength)
private const int TypeOffset = 3;
private const int FlagsOffset = 4;
private const int StreamIdOffset = 5;
public const int SettingSize = 6; // 2 bytes for the id, 4 bytes for the value.
public static bool ReadFrame(ReadOnlySequence<byte> readableBuffer, Http2Frame frame, uint maxFrameSize, out ReadOnlySequence<byte> framePayload)
{
framePayload = ReadOnlySequence<byte>.Empty;
if (readableBuffer.Length < HeaderLength)
{
return false;
}
var headerSlice = readableBuffer.Slice(0, Http2Frame.HeaderLength);
headerSlice.CopyTo(frame.Raw);
var headerSlice = readableBuffer.Slice(0, HeaderLength);
var header = headerSlice.ToSpan();
var payloadLength = frame.PayloadLength;
var payloadLength = (int)Bitshifter.ReadUInt24BigEndian(header);
if (payloadLength > maxFrameSize)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorFrameOverLimit(payloadLength, maxFrameSize), Http2ErrorCode.FRAME_SIZE_ERROR);
}
var frameLength = Http2Frame.HeaderLength + payloadLength;
// Make sure the whole frame is buffered
var frameLength = HeaderLength + payloadLength;
if (readableBuffer.Length < frameLength)
{
return false;
}
readableBuffer.Slice(Http2Frame.HeaderLength, payloadLength).CopyTo(frame.Payload);
consumed = examined = readableBuffer.GetPosition(frameLength);
frame.PayloadLength = payloadLength;
frame.Type = (Http2FrameType)header[TypeOffset];
frame.Flags = header[FlagsOffset];
frame.StreamId = (int)Bitshifter.ReadUInt31BigEndian(header.Slice(StreamIdOffset));
var extendedHeaderLength = ReadExtendedFields(frame, readableBuffer);
// The remaining payload minus the extra fields
framePayload = readableBuffer.Slice(HeaderLength + extendedHeaderLength, payloadLength - extendedHeaderLength);
return true;
}
private static int ReadExtendedFields(Http2Frame frame, ReadOnlySequence<byte> readableBuffer)
{
// Copy in any extra fields for the given frame type
var extendedHeaderLength = GetPayloadFieldsLength(frame);
if (extendedHeaderLength > frame.PayloadLength)
{
throw new Http2ConnectionErrorException(
CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(frame.Type, expectedLength: extendedHeaderLength), Http2ErrorCode.FRAME_SIZE_ERROR);
}
var extendedHeaders = readableBuffer.Slice(HeaderLength, extendedHeaderLength).ToSpan();
// Parse frame type specific fields
switch (frame.Type)
{
/*
+---------------+
|Pad Length? (8)|
+---------------+-----------------------------------------------+
| Data (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
*/
case Http2FrameType.DATA: // Variable 0 or 1
frame.DataPadLength = frame.DataHasPadding ? extendedHeaders[0] : (byte)0;
break;
/* https://tools.ietf.org/html/rfc7540#section-6.2
+---------------+
|Pad Length? (8)|
+-+-------------+-----------------------------------------------+
|E| Stream Dependency? (31) |
+-+-------------+-----------------------------------------------+
| Weight? (8) |
+-+-------------+-----------------------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
*/
case Http2FrameType.HEADERS:
if (frame.HeadersHasPadding)
{
frame.HeadersPadLength = extendedHeaders[0];
extendedHeaders = extendedHeaders.Slice(1);
}
else
{
frame.HeadersPadLength = 0;
}
if (frame.HeadersHasPriority)
{
frame.HeadersStreamDependency = (int)Bitshifter.ReadUInt31BigEndian(extendedHeaders);
frame.HeadersPriorityWeight = extendedHeaders.Slice(4)[0];
}
else
{
frame.HeadersStreamDependency = 0;
frame.HeadersPriorityWeight = 0;
}
break;
/* https://tools.ietf.org/html/rfc7540#section-6.8
+-+-------------------------------------------------------------+
|R| Last-Stream-ID (31) |
+-+-------------------------------------------------------------+
| Error Code (32) |
+---------------------------------------------------------------+
| Additional Debug Data (*) |
+---------------------------------------------------------------+
*/
case Http2FrameType.GOAWAY:
frame.GoAwayLastStreamId = (int)Bitshifter.ReadUInt31BigEndian(extendedHeaders);
frame.GoAwayErrorCode = (Http2ErrorCode)BinaryPrimitives.ReadUInt32BigEndian(extendedHeaders.Slice(4));
break;
/* https://tools.ietf.org/html/rfc7540#section-6.3
+-+-------------------------------------------------------------+
|E| Stream Dependency (31) |
+-+-------------+-----------------------------------------------+
| Weight (8) |
+-+-------------+
*/
case Http2FrameType.PRIORITY:
frame.PriorityStreamDependency = (int)Bitshifter.ReadUInt31BigEndian(extendedHeaders);
frame.PriorityWeight = extendedHeaders.Slice(4)[0];
break;
/* https://tools.ietf.org/html/rfc7540#section-6.4
+---------------------------------------------------------------+
| Error Code (32) |
+---------------------------------------------------------------+
*/
case Http2FrameType.RST_STREAM:
frame.RstStreamErrorCode = (Http2ErrorCode)BinaryPrimitives.ReadUInt32BigEndian(extendedHeaders);
break;
/* https://tools.ietf.org/html/rfc7540#section-6.9
+-+-------------------------------------------------------------+
|R| Window Size Increment (31) |
+-+-------------------------------------------------------------+
*/
case Http2FrameType.WINDOW_UPDATE:
frame.WindowUpdateSizeIncrement = (int)Bitshifter.ReadUInt31BigEndian(extendedHeaders);
break;
case Http2FrameType.PING: // Opaque payload 8 bytes long
case Http2FrameType.SETTINGS: // Settings are general payload
case Http2FrameType.CONTINUATION: // None
case Http2FrameType.PUSH_PROMISE: // Not implemented frames are ignored at this phase
default:
return 0;
}
return extendedHeaderLength;
}
// The length in bytes of additional fields stored in the payload section.
// This may be variable based on flags, but should be no more than 8 bytes.
public static int GetPayloadFieldsLength(Http2Frame frame)
{
switch (frame.Type)
{
// TODO: Extract constants
case Http2FrameType.DATA: // Variable 0 or 1
return frame.DataHasPadding ? 1 : 0;
case Http2FrameType.HEADERS:
return (frame.HeadersHasPadding ? 1 : 0) + (frame.HeadersHasPriority ? 5 : 0); // Variable 0 to 6
case Http2FrameType.GOAWAY:
return 8; // Last stream id and error code.
case Http2FrameType.PRIORITY:
return 5; // Stream dependency and weight
case Http2FrameType.RST_STREAM:
return 4; // Error code
case Http2FrameType.WINDOW_UPDATE:
return 4; // Update size
case Http2FrameType.PING: // 8 bytes of opaque data
case Http2FrameType.SETTINGS: // Settings are general payload
case Http2FrameType.CONTINUATION: // None
case Http2FrameType.PUSH_PROMISE: // Not implemented frames are ignored at this phase
default:
return 0;
}
}
public static IList<Http2PeerSetting> ReadSettings(ReadOnlySequence<byte> payload)
{
var data = payload.ToSpan();
Debug.Assert(data.Length % SettingSize == 0, "Invalid settings payload length");
var settingsCount = data.Length / SettingSize;
var settings = new Http2PeerSetting[settingsCount];
for (int i = 0; i < settings.Length; i++)
{
settings[i] = ReadSetting(data);
data = data.Slice(SettingSize);
}
return settings;
}
private static Http2PeerSetting ReadSetting(ReadOnlySpan<byte> payload)
{
var id = (Http2SettingsParameter)BinaryPrimitives.ReadUInt16BigEndian(payload);
var value = BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(2));
return new Http2PeerSetting(id, value);
}
}
}

View File

@ -3,7 +3,9 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
@ -22,6 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private static readonly byte[] _continueBytes = new byte[] { 0x08, 0x03, (byte)'1', (byte)'0', (byte)'0' };
private uint _maxFrameSize = Http2PeerSettings.MinAllowedMaxFrameSize;
private byte[] _headerEncodingBuffer;
private Http2Frame _outgoingFrame;
private readonly object _writeLock = new object();
private readonly HPackEncoder _hpackEncoder = new HPackEncoder();
@ -49,15 +52,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_connectionId = connectionId;
_log = log;
_flusher = new StreamSafePipeFlusher(_outputWriter, timeoutControl);
_outgoingFrame = new Http2Frame(_maxFrameSize);
_outgoingFrame = new Http2Frame();
_headerEncodingBuffer = new byte[_maxFrameSize];
}
public void UpdateMaxFrameSize(uint maxFrameSize)
{
lock (_writeLock)
{
_maxFrameSize = maxFrameSize;
_outgoingFrame = new Http2Frame(maxFrameSize);
if (_maxFrameSize != maxFrameSize)
{
_maxFrameSize = maxFrameSize;
_headerEncodingBuffer = new byte[_maxFrameSize];
}
}
}
@ -109,14 +116,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
lock (_writeLock)
{
if (_completed)
{
return Task.CompletedTask;
}
_outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS, streamId);
_outgoingFrame.PayloadLength = _continueBytes.Length;
_continueBytes.CopyTo(_outgoingFrame.HeadersPayload);
return WriteFrameUnsynchronizedAsync();
WriteHeaderUnsynchronized();
_outputWriter.Write(_continueBytes);
return _flusher.FlushAsync();
}
}
// Optional header fields for padding and priority are not implemented.
/* https://tools.ietf.org/html/rfc7540#section-6.2
+---------------+
|Pad Length? (8)|
+-+-------------+-----------------------------------------------+
|E| Stream Dependency? (31) |
+-+-------------+-----------------------------------------------+
| Weight? (8) |
+-+-------------+-----------------------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
*/
public void WriteResponseHeaders(int streamId, int statusCode, IHeaderDictionary headers)
{
lock (_writeLock)
@ -126,33 +152,43 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return;
}
_outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
var done = _hpackEncoder.BeginEncode(statusCode, EnumerateHeaders(headers), _outgoingFrame.Payload, out var payloadLength);
_outgoingFrame.PayloadLength = payloadLength;
if (done)
try
{
_outgoingFrame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS;
}
_outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
var buffer = _headerEncodingBuffer.AsSpan();
var done = _hpackEncoder.BeginEncode(statusCode, EnumerateHeaders(headers), buffer, out var payloadLength);
_log.Http2FrameSending(_connectionId, _outgoingFrame);
_outputWriter.Write(_outgoingFrame.Raw);
while (!done)
{
_outgoingFrame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);
done = _hpackEncoder.Encode(_outgoingFrame.Payload, out var length);
_outgoingFrame.PayloadLength = length;
_outgoingFrame.PayloadLength = payloadLength;
if (done)
{
_outgoingFrame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS;
_outgoingFrame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS;
}
_log.Http2FrameSending(_connectionId, _outgoingFrame);
_outputWriter.Write(_outgoingFrame.Raw);
WriteHeaderUnsynchronized();
_outputWriter.Write(buffer.Slice(0, payloadLength));
while (!done)
{
_outgoingFrame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);
done = _hpackEncoder.Encode(buffer, out payloadLength);
_outgoingFrame.PayloadLength = payloadLength;
if (done)
{
_outgoingFrame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS;
}
WriteHeaderUnsynchronized();
_outputWriter.Write(buffer.Slice(0, payloadLength));
}
}
catch (HPackEncodingException hex)
{
// Header errors are fatal to the connection. We don't have a direct way to signal this to the Http2Connection.
_connectionContext.Abort(new ConnectionAbortedException("", hex));
throw new InvalidOperationException("", hex); // Report the error to the user if this was the first write.
}
}
}
@ -182,45 +218,54 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
}
/* Padding is not implemented
+---------------+
|Pad Length? (8)|
+---------------+-----------------------------------------------+
| Data (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
*/
private Task WriteDataUnsynchronizedAsync(int streamId, ReadOnlySequence<byte> data, bool endStream)
{
// Note padding is not implemented
_outgoingFrame.PrepareData(streamId);
var payload = _outgoingFrame.Payload;
var unwrittenPayloadLength = 0;
var dataPayloadLength = (int)_maxFrameSize; // Minus padding
foreach (var buffer in data)
while (data.Length > dataPayloadLength)
{
var current = buffer;
var currentData = data.Slice(0, dataPayloadLength);
_outgoingFrame.PayloadLength = dataPayloadLength; // Plus padding
while (current.Length > payload.Length)
WriteHeaderUnsynchronized();
foreach (var buffer in currentData)
{
current.Span.Slice(0, payload.Length).CopyTo(payload);
current = current.Slice(payload.Length);
_log.Http2FrameSending(_connectionId, _outgoingFrame);
_outputWriter.Write(_outgoingFrame.Raw);
payload = _outgoingFrame.Payload;
unwrittenPayloadLength = 0;
_outputWriter.Write(buffer.Span);
}
if (current.Length > 0)
{
current.Span.CopyTo(payload);
payload = payload.Slice(current.Length);
unwrittenPayloadLength += current.Length;
}
// Plus padding
data = data.Slice(dataPayloadLength);
}
if (endStream)
{
_outgoingFrame.DataFlags = Http2DataFrameFlags.END_STREAM;
_outgoingFrame.DataFlags |= Http2DataFrameFlags.END_STREAM;
}
_outgoingFrame.PayloadLength = unwrittenPayloadLength;
_outgoingFrame.PayloadLength = (int)data.Length; // Plus padding
_log.Http2FrameSending(_connectionId, _outgoingFrame);
_outputWriter.Write(_outgoingFrame.Raw);
WriteHeaderUnsynchronized();
foreach (var buffer in data)
{
_outputWriter.Write(buffer.Span);
}
// Plus padding
return _flusher.FlushAsync();
}
@ -271,71 +316,194 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
await ThreadPoolAwaitable.Instance;
}
/* https://tools.ietf.org/html/rfc7540#section-6.9
+-+-------------------------------------------------------------+
|R| Window Size Increment (31) |
+-+-------------------------------------------------------------+
*/
public Task WriteWindowUpdateAsync(int streamId, int sizeIncrement)
{
lock (_writeLock)
{
if (_completed)
{
return Task.CompletedTask;
}
_outgoingFrame.PrepareWindowUpdate(streamId, sizeIncrement);
return WriteFrameUnsynchronizedAsync();
WriteHeaderUnsynchronized();
var buffer = _outputWriter.GetSpan(4);
Bitshifter.WriteUInt31BigEndian(buffer, (uint)sizeIncrement);
_outputWriter.Advance(4);
return _flusher.FlushAsync();
}
}
/* https://tools.ietf.org/html/rfc7540#section-6.4
+---------------------------------------------------------------+
| Error Code (32) |
+---------------------------------------------------------------+
*/
public Task WriteRstStreamAsync(int streamId, Http2ErrorCode errorCode)
{
lock (_writeLock)
{
if (_completed)
{
return Task.CompletedTask;
}
_outgoingFrame.PrepareRstStream(streamId, errorCode);
return WriteFrameUnsynchronizedAsync();
WriteHeaderUnsynchronized();
var buffer = _outputWriter.GetSpan(4);
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)errorCode);
_outputWriter.Advance(4);
return _flusher.FlushAsync();
}
}
/* https://tools.ietf.org/html/rfc7540#section-6.5.1
List of:
+-------------------------------+
| Identifier (16) |
+-------------------------------+-------------------------------+
| Value (32) |
+---------------------------------------------------------------+
*/
public Task WriteSettingsAsync(IList<Http2PeerSetting> settings)
{
lock (_writeLock)
{
_outgoingFrame.PrepareSettings(Http2SettingsFrameFlags.NONE, settings);
return WriteFrameUnsynchronizedAsync();
if (_completed)
{
return Task.CompletedTask;
}
_outgoingFrame.PrepareSettings(Http2SettingsFrameFlags.NONE);
var settingsSize = settings.Count * Http2FrameReader.SettingSize;
_outgoingFrame.PayloadLength = settingsSize;
WriteHeaderUnsynchronized();
var buffer = _outputWriter.GetSpan(settingsSize).Slice(0, settingsSize); // GetSpan isn't precise
WriteSettings(settings, buffer);
_outputWriter.Advance(settingsSize);
return _flusher.FlushAsync();
}
}
internal static void WriteSettings(IList<Http2PeerSetting> settings, Span<byte> destination)
{
foreach (var setting in settings)
{
BinaryPrimitives.WriteUInt16BigEndian(destination, (ushort)setting.Parameter);
BinaryPrimitives.WriteUInt32BigEndian(destination.Slice(2), setting.Value);
destination = destination.Slice(Http2FrameReader.SettingSize);
}
}
// No payload
public Task WriteSettingsAckAsync()
{
lock (_writeLock)
{
if (_completed)
{
return Task.CompletedTask;
}
_outgoingFrame.PrepareSettings(Http2SettingsFrameFlags.ACK);
return WriteFrameUnsynchronizedAsync();
WriteHeaderUnsynchronized();
return _flusher.FlushAsync();
}
}
public Task WritePingAsync(Http2PingFrameFlags flags, ReadOnlySpan<byte> payload)
/* https://tools.ietf.org/html/rfc7540#section-6.7
+---------------------------------------------------------------+
| |
| Opaque Data (64) |
| |
+---------------------------------------------------------------+
*/
public Task WritePingAsync(Http2PingFrameFlags flags, ReadOnlySequence<byte> payload)
{
lock (_writeLock)
{
if (_completed)
{
return Task.CompletedTask;
}
_outgoingFrame.PreparePing(Http2PingFrameFlags.ACK);
payload.CopyTo(_outgoingFrame.Payload);
return WriteFrameUnsynchronizedAsync();
Debug.Assert(payload.Length == _outgoingFrame.PayloadLength); // 8
WriteHeaderUnsynchronized();
foreach (var segment in payload)
{
_outputWriter.Write(segment.Span);
}
return _flusher.FlushAsync();
}
}
/* https://tools.ietf.org/html/rfc7540#section-6.8
+-+-------------------------------------------------------------+
|R| Last-Stream-ID (31) |
+-+-------------------------------------------------------------+
| Error Code (32) |
+---------------------------------------------------------------+
| Additional Debug Data (*) | (not implemented)
+---------------------------------------------------------------+
*/
public Task WriteGoAwayAsync(int lastStreamId, Http2ErrorCode errorCode)
{
lock (_writeLock)
{
_outgoingFrame.PrepareGoAway(lastStreamId, errorCode);
return WriteFrameUnsynchronizedAsync();
WriteHeaderUnsynchronized();
var buffer = _outputWriter.GetSpan(8);
Bitshifter.WriteUInt31BigEndian(buffer, (uint)lastStreamId);
buffer = buffer.Slice(4);
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)errorCode);
_outputWriter.Advance(8);
return _flusher.FlushAsync();
}
}
private Task WriteFrameUnsynchronizedAsync()
private void WriteHeaderUnsynchronized()
{
if (_completed)
{
return Task.CompletedTask;
}
_log.Http2FrameSending(_connectionId, _outgoingFrame);
_outputWriter.Write(_outgoingFrame.Raw);
return _flusher.FlushAsync();
WriteHeader(_outgoingFrame, _outputWriter);
}
/* https://tools.ietf.org/html/rfc7540#section-4.1
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
*/
internal static void WriteHeader(Http2Frame frame, PipeWriter output)
{
var buffer = output.GetSpan(Http2FrameReader.HeaderLength);
Bitshifter.WriteUInt24BigEndian(buffer, (uint)frame.PayloadLength);
buffer = buffer.Slice(3);
buffer[0] = (byte)frame.Type;
buffer[1] = frame.Flags;
buffer = buffer.Slice(2);
Bitshifter.WriteUInt31BigEndian(buffer, (uint)frame.StreamId);
output.Advance(Http2FrameReader.HeaderLength);
}
public bool TryUpdateConnectionWindow(int bytes)

View File

@ -279,7 +279,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
}
public Task OnDataAsync(Http2Frame dataFrame)
public Task OnDataAsync(Http2Frame dataFrame, ReadOnlySequence<byte> payload)
{
// Since padding isn't buffered, immediately count padding bytes as read for flow control purposes.
if (dataFrame.DataHasPadding)
@ -288,10 +288,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
OnDataRead(dataFrame.DataPadLength + 1);
}
var payload = dataFrame.DataPayload;
var dataPayload = payload.Slice(0, dataFrame.DataPayloadLength); // minus padding
var endStream = dataFrame.DataEndStream;
if (payload.Length > 0)
if (dataPayload.Length > 0)
{
RequestBodyStarted = true;
@ -302,7 +302,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_inputFlowControl.StopWindowUpdates();
}
_inputFlowControl.Advance(payload.Length);
_inputFlowControl.Advance((int)dataPayload.Length);
if (IsAborted)
{
@ -315,15 +315,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
if (InputRemaining.HasValue)
{
// https://tools.ietf.org/html/rfc7540#section-8.1.2.6
if (payload.Length > InputRemaining.Value)
if (dataPayload.Length > InputRemaining.Value)
{
throw new Http2StreamErrorException(StreamId, CoreStrings.Http2StreamErrorMoreDataThanLength, Http2ErrorCode.PROTOCOL_ERROR);
}
InputRemaining -= payload.Length;
InputRemaining -= dataPayload.Length;
}
RequestBodyPipe.Writer.Write(payload);
foreach (var segment in dataPayload)
{
RequestBodyPipe.Writer.Write(segment.Span);
}
var flushTask = RequestBodyPipe.Writer.FlushAsync();
// It shouldn't be possible for the RequestBodyPipe to fill up an return an incomplete task if

View File

@ -2128,20 +2128,6 @@ 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");
/// <summary>
/// A value between {min} and {max} is required.
/// </summary>
@ -2170,6 +2156,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
internal static string FormatHPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock()
=> GetString("HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock");
/// <summary>
/// The given buffer was too small to encode any headers.
/// </summary>
internal static string HPackErrorNotEnoughBuffer
{
get => GetString("HPackErrorNotEnoughBuffer");
}
/// <summary>
/// The given buffer was too small to encode any headers.
/// </summary>
internal static string FormatHPackErrorNotEnoughBuffer()
=> GetString("HPackErrorNotEnoughBuffer");
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -104,7 +105,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
public void DecodesIndexedHeaderField_StaticTable()
{
_decoder.Decode(_indexedHeaderStatic, endHeaders: true, handler: this);
_decoder.Decode(new ReadOnlySequence<byte>(_indexedHeaderStatic), endHeaders: true, handler: this);
Assert.Equal("GET", _decodedHeaders[HeaderNames.Method]);
}
@ -115,14 +116,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_dynamicTable.Insert(_headerNameBytes, _headerValueBytes);
// Index it
_decoder.Decode(_indexedHeaderDynamic, endHeaders: true, handler: this);
_decoder.Decode(new ReadOnlySequence<byte>(_indexedHeaderDynamic), endHeaders: true, handler: this);
Assert.Equal(_headerValueString, _decodedHeaders[_headerNameString]);
}
[Fact]
public void DecodesIndexedHeaderField_OutOfRange_Error()
{
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(_indexedHeaderDynamic, endHeaders: true, handler: this));
var exception = Assert.Throws<HPackDecodingException>(() =>
_decoder.Decode(new ReadOnlySequence<byte>(_indexedHeaderDynamic), endHeaders: true, handler: this));
Assert.Equal(CoreStrings.FormatHPackErrorIndexOutOfRange(62), exception.Message);
Assert.Empty(_decodedHeaders);
}
@ -198,7 +200,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
// 11 1110 (Indexed Name - Index 62 encoded with 6-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation)
// Index 62 is the first entry in the dynamic table. If there's nothing there, the decoder should throw.
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new byte[] { 0x7e }, endHeaders: true, handler: this));
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new ReadOnlySequence<byte>(new byte[] { 0x7e }), endHeaders: true, handler: this));
Assert.Equal(CoreStrings.FormatHPackErrorIndexOutOfRange(62), exception.Message);
Assert.Empty(_decodedHeaders);
}
@ -274,7 +276,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
// 1111 0010 1111 (Indexed Name - Index 62 encoded with 4-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation)
// Index 62 is the first entry in the dynamic table. If there's nothing there, the decoder should throw.
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new byte[] { 0x0f, 0x2f }, endHeaders: true, handler: this));
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new ReadOnlySequence<byte>(new byte[] { 0x0f, 0x2f }), endHeaders: true, handler: this));
Assert.Equal(CoreStrings.FormatHPackErrorIndexOutOfRange(62), exception.Message);
Assert.Empty(_decodedHeaders);
}
@ -356,7 +358,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
// 1111 0010 1111 (Indexed Name - Index 62 encoded with 4-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation)
// Index 62 is the first entry in the dynamic table. If there's nothing there, the decoder should throw.
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new byte[] { 0x1f, 0x2f }, endHeaders: true, handler: this));
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new ReadOnlySequence<byte>(new byte[] { 0x1f, 0x2f }), endHeaders: true, handler: this));
Assert.Equal(CoreStrings.FormatHPackErrorIndexOutOfRange(62), exception.Message);
Assert.Empty(_decodedHeaders);
}
@ -369,7 +371,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize);
_decoder.Decode(new byte[] { 0x3e }, endHeaders: true, handler: this);
_decoder.Decode(new ReadOnlySequence<byte>(new byte[] { 0x3e }), endHeaders: true, handler: this);
Assert.Equal(30, _dynamicTable.MaxSize);
Assert.Empty(_decodedHeaders);
@ -383,7 +385,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize);
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(_indexedHeaderStatic.Concat(new byte[] { 0x3e }).ToArray(), endHeaders: true, handler: this));
var data = new ReadOnlySequence<byte>(_indexedHeaderStatic.Concat(new byte[] { 0x3e }).ToArray());
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(data, endHeaders: true, handler: this));
Assert.Equal(CoreStrings.HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock, exception.Message);
}
@ -392,12 +395,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize);
_decoder.Decode(_indexedHeaderStatic, endHeaders: false, handler: this);
_decoder.Decode(new ReadOnlySequence<byte>(_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));
var data = new ReadOnlySequence<byte>(new byte[] { 0x3e });
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(data, endHeaders: true, handler: this));
Assert.Equal(CoreStrings.HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock, exception.Message);
}
@ -406,12 +410,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize);
_decoder.Decode(_indexedHeaderStatic, endHeaders: true, handler: this);
_decoder.Decode(new ReadOnlySequence<byte>(_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);
_decoder.Decode(new ReadOnlySequence<byte>(new byte[] { 0x3e }), endHeaders: true, handler: this);
Assert.Equal(30, _dynamicTable.MaxSize);
}
@ -424,7 +428,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize);
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new byte[] { 0x3f, 0xe2, 0x1f }, endHeaders: true, handler: this));
var exception = Assert.Throws<HPackDecodingException>(() =>
_decoder.Decode(new ReadOnlySequence<byte>(new byte[] { 0x3f, 0xe2, 0x1f }), endHeaders: true, handler: this));
Assert.Equal(CoreStrings.FormatHPackErrorDynamicTableSizeUpdateTooLarge(4097, DynamicTableInitialMaxSize), exception.Message);
Assert.Empty(_decodedHeaders);
}
@ -436,7 +441,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
.Concat(new byte[] { 0xff, 0x82, 0x1f }) // 4097 encoded with 7-bit prefix
.ToArray();
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: this));
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new ReadOnlySequence<byte>(encoded), endHeaders: true, handler: this));
Assert.Equal(CoreStrings.FormatHPackStringLengthTooLarge(4097, HPackDecoder.MaxStringOctets), exception.Message);
Assert.Empty(_decodedHeaders);
}
@ -526,7 +531,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[MemberData(nameof(_incompleteHeaderBlockData))]
public void DecodesIncompleteHeaderBlock_Error(byte[] encoded)
{
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: this));
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new ReadOnlySequence<byte>(encoded), endHeaders: true, handler: this));
Assert.Equal(CoreStrings.HPackErrorIncompleteHeaderBlock, exception.Message);
Assert.Empty(_decodedHeaders);
}
@ -560,7 +565,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[MemberData(nameof(_huffmanDecodingErrorData))]
public void WrapsHuffmanDecodingExceptionInHPackDecodingException(byte[] encoded)
{
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: this));
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new ReadOnlySequence<byte>(encoded), endHeaders: true, handler: this));
Assert.Equal(CoreStrings.HPackHuffmanError, exception.Message);
Assert.IsType<HuffmanDecodingException>(exception.InnerException);
Assert.Empty(_decodedHeaders);
@ -581,7 +586,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Equal(0, _dynamicTable.Count);
Assert.Equal(0, _dynamicTable.Size);
_decoder.Decode(encoded, endHeaders: true, handler: this);
_decoder.Decode(new ReadOnlySequence<byte>(encoded), endHeaders: true, handler: this);
Assert.Equal(expectedHeaderValue, _decodedHeaders[expectedHeaderName]);

View File

@ -84,8 +84,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await InitializeConnectionAsync(_echoApplication);
await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
uint length = Http2PeerSettings.MinAllowedMaxFrameSize + 1;
await SendDataAsync(1, new byte[length].AsSpan(), endStream: true);
await SendDataAsync(1, new byte[length], endStream: true);
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: true,
@ -104,7 +105,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await InitializeConnectionAsync(_echoApplication, expectedSettingsCount: 3);
await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
await SendDataAsync(1, new byte[length].AsSpan(), endStream: true);
await SendDataAsync(1, new byte[length], endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 37,
@ -150,7 +151,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.DataPayload));
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
}
[Fact]
@ -177,7 +178,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame.DataPayload));
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
}
[Fact]
@ -249,10 +250,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
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.True(_maxData.AsSpan().SequenceEqual(dataFrame1.PayloadSequence.ToArray()));
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame2.PayloadSequence.ToArray()));
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame3.PayloadSequence.ToArray()));
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame4.PayloadSequence.ToArray()));
Assert.Equal(_maxData.Length * 2, streamWindowUpdateFrame1.WindowUpdateSizeIncrement);
Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame1.WindowUpdateSizeIncrement);
Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame2.WindowUpdateSizeIncrement);
@ -287,7 +288,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.DataPayload));
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
}
[Fact]
@ -350,10 +351,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
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));
Assert.True(_helloBytes.AsSpan().SequenceEqual(stream1DataFrame1.PayloadSequence.ToArray()));
Assert.True(_worldBytes.AsSpan().SequenceEqual(stream1DataFrame2.PayloadSequence.ToArray()));
Assert.True(_helloBytes.AsSpan().SequenceEqual(stream3DataFrame1.PayloadSequence.ToArray()));
Assert.True(_worldBytes.AsSpan().SequenceEqual(stream3DataFrame2.PayloadSequence.ToArray()));
}
[Fact]
@ -443,16 +444,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
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(dataFrame1.PayloadSequence.ToArray()));
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame2.PayloadSequence.ToArray()));
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame3.PayloadSequence.ToArray()));
Assert.Equal(_maxData.Length * 2, streamWindowUpdateFrame.WindowUpdateSizeIncrement);
Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame1.WindowUpdateSizeIncrement);
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame4.DataPayload));
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame4.PayloadSequence.ToArray()));
Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame2.WindowUpdateSizeIncrement);
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame5.DataPayload));
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame5.PayloadSequence.ToArray()));
}
[Fact]
@ -547,7 +548,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.DataPayload));
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
}
[Theory]
@ -564,7 +565,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await InitializeConnectionAsync(_echoApplication);
await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
await SendDataWithPaddingAsync(1, maxDataMinusPadding.Span, padLength, endStream: false);
await SendDataWithPaddingAsync(1, maxDataMinusPadding, padLength, endStream: false);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 37,
@ -595,8 +596,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.True(maxDataMinusPadding.Span.SequenceEqual(dataFrame1.DataPayload));
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame2.DataPayload));
Assert.True(maxDataMinusPadding.Span.SequenceEqual(dataFrame1.PayloadSequence.ToArray()));
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame2.PayloadSequence.ToArray()));
Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame.WindowUpdateSizeIncrement);
}
@ -703,8 +704,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: true,
expectedLastStreamId: 1,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.Http2FrameMissingFields);
expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.DATA, expectedLength: 1));
}
[Fact]
@ -1127,6 +1128,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
Assert.Equal(new byte[] { 0x08, 0x03, (byte)'1', (byte)'0', (byte)'0' }, frame.PayloadSequence.ToArray());
await SendDataAsync(1, _helloBytes, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
@ -1142,8 +1145,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
Assert.Equal(new byte[] { 0x08, 0x03, (byte)'1', (byte)'0', (byte)'0' }, frame.HeadersPayload.ToArray());
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
@ -1361,8 +1362,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: true,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.Http2FrameMissingFields);
expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.HEADERS, expectedLength: 1));
}
[Theory]
@ -1924,7 +1925,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: streamId);
Assert.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame.DataPayload));
Assert.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame.PayloadSequence.ToArray()));
Assert.False(writeTasks[streamId].IsCompleted);
}
@ -2064,13 +2065,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendSettingsAsync();
var frame = await ExpectAsync(Http2FrameType.SETTINGS,
withLength: Http2Frame.SettingSize * 2,
withLength: Http2FrameReader.SettingSize * 2,
withFlags: 0,
withStreamId: 0);
// Only non protocol defaults are sent
Assert.Equal(2, frame.SettingsCount);
var settings = frame.GetSettings();
var settings = Http2FrameReader.ReadSettings(frame.PayloadSequence);
Assert.Equal(2, settings.Count);
var setting = settings[0];
@ -2101,13 +2101,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendSettingsAsync();
var frame = await ExpectAsync(Http2FrameType.SETTINGS,
withLength: 6 * 2,
withLength: Http2FrameReader.SettingSize * 2,
withFlags: 0,
withStreamId: 0);
// Only non protocol defaults are sent
Assert.Equal(2, frame.SettingsCount);
var settings = frame.GetSettings();
var settings = Http2FrameReader.ReadSettings(frame.PayloadSequence);
Assert.Equal(2, settings.Count);
var setting = settings[0];
@ -2139,9 +2138,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
await InitializeConnectionAsync(_noopApplication);
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var frame = new Http2Frame();
frame.PrepareSettings(Http2SettingsFrameFlags.ACK);
await SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, _pair.Application.Output);
await FlushAsync(_pair.Application.Output);
await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
}
@ -2260,6 +2260,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
public async Task SETTINGS_Received_ChangesAllowedResponseMaxFrameSize()
{
_connection.ServerSettings.MaxFrameSize = Http2PeerSettings.MaxAllowedMaxFrameSize;
// This includes the default response headers such as :status, etc
var defaultResponseHeaderLength = 37;
var headerValueLength = Http2PeerSettings.MinAllowedMaxFrameSize;
@ -2276,7 +2277,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
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);
});
}, expectedSettingsCount: 3);
// Update client settings
_clientSettings.MaxFrameSize = (uint)payloadLength;
@ -2311,6 +2312,42 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task SETTINGS_Received_ClientMaxFrameSizeCannotExceedServerMaxFrameSize()
{
var serverMaxFrame = Http2PeerSettings.MinAllowedMaxFrameSize + 1024;
_connection.ServerSettings.MaxFrameSize = Http2PeerSettings.MinAllowedMaxFrameSize + 1024;
var clientMaxFrame = serverMaxFrame + 1024 * 5;
_clientSettings.MaxFrameSize = (uint)clientMaxFrame;
await InitializeConnectionAsync(context =>
{
return context.Response.Body.WriteAsync(new byte[clientMaxFrame], 0, clientMaxFrame);
}, expectedSettingsCount: 3);
// Start request
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: serverMaxFrame,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: clientMaxFrame - serverMaxFrame,
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 SETTINGS_Received_ChangesHeaderTableSize()
{
@ -2627,7 +2664,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: streamId);
Assert.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame.DataPayload));
Assert.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame.PayloadSequence.ToArray()));
Assert.False(writeTasks[streamId].IsCompleted);
}
@ -2931,8 +2968,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame1.DataPayload));
Assert.True(_helloWorldBytes.AsSpan(initialWindowSize, initialWindowSize).SequenceEqual(dataFrame2.DataPayload));
Assert.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame1.PayloadSequence.ToArray()));
Assert.True(_helloWorldBytes.AsSpan(initialWindowSize, initialWindowSize).SequenceEqual(dataFrame2.PayloadSequence.ToArray()));
}
[Fact]
@ -2986,9 +3023,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
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));
Assert.True(_helloWorldBytes.AsSpan(0, 6).SequenceEqual(dataFrame1.PayloadSequence.ToArray()));
Assert.True(_helloWorldBytes.AsSpan(6, 3).SequenceEqual(dataFrame2.PayloadSequence.ToArray()));
Assert.True(_helloWorldBytes.AsSpan(9, 3).SequenceEqual(dataFrame3.PayloadSequence.ToArray()));
}
[Fact]
@ -3190,9 +3227,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(continuationFrame1.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(continuationFrame2.HeadersPayload, endHeaders: true, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
_hpackDecoder.Decode(continuationFrame1.PayloadSequence, endHeaders: false, handler: this);
_hpackDecoder.Decode(continuationFrame2.PayloadSequence, endHeaders: true, handler: this);
Assert.Equal(11, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);

View File

@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(4, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(4, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -146,7 +146,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(5, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -178,7 +178,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(5, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -216,7 +216,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(6, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -262,7 +262,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -328,7 +328,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -361,7 +361,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -394,7 +394,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(4, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -429,7 +429,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(4, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -464,7 +464,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(4, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -499,7 +499,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(4, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -610,7 +610,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[12].AsSpan(), endStream: true);
await SendDataAsync(1, new byte[12], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 55,
@ -623,7 +623,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -653,7 +653,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
};
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[12].AsSpan(), endStream: true);
await SendDataAsync(1, new byte[12], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 55,
@ -666,7 +666,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -699,9 +699,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[1].AsSpan(), endStream: false);
await SendDataAsync(1, new byte[3].AsSpan(), endStream: false);
await SendDataAsync(1, new byte[8].AsSpan(), endStream: true);
await SendDataAsync(1, new byte[1], endStream: false);
await SendDataAsync(1, new byte[3], endStream: false);
await SendDataAsync(1, new byte[8], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 55,
@ -714,7 +714,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -786,7 +786,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[13].AsSpan(), endStream: true);
await SendDataAsync(1, new byte[13], endStream: true);
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorMoreDataThanLength);
@ -821,7 +821,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[11].AsSpan(), endStream: true);
await SendDataAsync(1, new byte[11], endStream: true);
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorLessDataThanLength);
@ -856,10 +856,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[1].AsSpan(), endStream: false);
await SendDataAsync(1, new byte[2].AsSpan(), endStream: false);
await SendDataAsync(1, new byte[10].AsSpan(), endStream: false);
await SendDataAsync(1, new byte[2].AsSpan(), endStream: true);
await SendDataAsync(1, new byte[1], endStream: false);
await SendDataAsync(1, new byte[2], endStream: false);
await SendDataAsync(1, new byte[10], endStream: false);
await SendDataAsync(1, new byte[2], endStream: true);
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorMoreDataThanLength);
@ -894,8 +894,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[1].AsSpan(), endStream: false);
await SendDataAsync(1, new byte[2].AsSpan(), endStream: true);
await SendDataAsync(1, new byte[1], endStream: false);
await SendDataAsync(1, new byte[2], endStream: true);
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorLessDataThanLength);
@ -938,7 +938,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -977,7 +977,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -1015,7 +1015,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -1053,7 +1053,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -1080,7 +1080,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[12].AsSpan(), endStream: true);
await SendDataAsync(1, new byte[12], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 55,
@ -1093,7 +1093,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -1136,7 +1136,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -1164,7 +1164,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[12].AsSpan(), endStream: true);
await SendDataAsync(1, new byte[12], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 55,
@ -1177,7 +1177,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -1207,9 +1207,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[6].AsSpan(), endStream: false);
await SendDataAsync(1, new byte[6].AsSpan(), endStream: false);
await SendDataAsync(1, new byte[6].AsSpan(), endStream: true);
await SendDataAsync(1, new byte[6], endStream: false);
await SendDataAsync(1, new byte[6], endStream: false);
await SendDataAsync(1, new byte[6], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 59,
@ -1222,7 +1222,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -1266,9 +1266,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[6].AsSpan(), endStream: false);
await SendDataAsync(1, new byte[6].AsSpan(), endStream: false);
await SendDataAsync(1, new byte[6].AsSpan(), endStream: true);
await SendDataAsync(1, new byte[6], endStream: false);
await SendDataAsync(1, new byte[6], endStream: false);
await SendDataAsync(1, new byte[6], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 59,
@ -1281,7 +1281,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -1321,7 +1321,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[12].AsSpan(), endStream: true);
await SendDataAsync(1, new byte[12], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 55,
@ -1334,7 +1334,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -1371,7 +1371,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -1409,7 +1409,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
@ -1735,5 +1735,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task ResponseWithHeadersTooLarge_AbortsConnection()
{
var appFinished = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
await InitializeConnectionAsync(async context =>
{
context.Response.Headers["too_long"] = new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize);
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.Response.WriteAsync("Hello World")).DefaultTimeout();
appFinished.TrySetResult(ex.InnerException.Message);
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var message = await appFinished.Task.DefaultTimeout();
Assert.Equal(CoreStrings.HPackErrorNotEnoughBuffer, message);
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
@ -11,6 +12,7 @@ using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
@ -20,7 +22,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.AspNetCore.Testing;
using Microsoft.AspNetCore.Connections;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;
@ -51,6 +52,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
protected readonly Http2PeerSettings _clientSettings = new Http2PeerSettings();
protected readonly HPackEncoder _hpackEncoder = new HPackEncoder();
protected readonly HPackDecoder _hpackDecoder;
private readonly byte[] _headerEncodingBuffer = new byte[Http2PeerSettings.MinAllowedMaxFrameSize];
protected readonly ConcurrentDictionary<int, TaskCompletionSource<object>> _runningStreams = new ConcurrentDictionary<int, TaskCompletionSource<object>>();
protected readonly Dictionary<string, string> _receivedHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@ -315,7 +317,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendSettingsAsync();
await ExpectAsync(Http2FrameType.SETTINGS,
withLength: expectedSettingsCount * Http2Frame.SettingSize,
withLength: expectedSettingsCount * Http2FrameReader.SettingSize,
withFlags: 0,
withStreamId: 0);
@ -325,14 +327,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
withStreamId: 0);
}
protected async Task StartStreamAsync(int streamId, IEnumerable<KeyValuePair<string, string>> headers, bool endStream)
protected Task StartStreamAsync(int streamId, IEnumerable<KeyValuePair<string, string>> headers, bool endStream)
{
var writableBuffer = _pair.Application.Output;
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
_runningStreams[streamId] = tcs;
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var frame = new Http2Frame();
frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
var done = _hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length);
var buffer = _headerEncodingBuffer.AsSpan();
var done = _hpackEncoder.BeginEncode(headers, buffer, out var length);
frame.PayloadLength = length;
if (done)
@ -345,12 +350,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM;
}
await SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, writableBuffer);
writableBuffer.Write(buffer.Slice(0, length));
while (!done)
{
frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);
done = _hpackEncoder.Encode(frame.HeadersPayload, out length);
done = _hpackEncoder.Encode(buffer, out length);
frame.PayloadLength = length;
if (done)
@ -358,77 +365,143 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
frame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS;
}
await SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, writableBuffer);
writableBuffer.Write(buffer.Slice(0, length));
}
return FlushAsync(writableBuffer);
}
protected async Task SendHeadersWithPaddingAsync(int streamId, IEnumerable<KeyValuePair<string, string>> headers, byte padLength, bool endStream)
/* https://tools.ietf.org/html/rfc7540#section-6.2
+---------------+
|Pad Length? (8)|
+-+-------------+-----------------------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
*/
protected Task SendHeadersWithPaddingAsync(int streamId, IEnumerable<KeyValuePair<string, string>> headers, byte padLength, bool endStream)
{
var writableBuffer = _pair.Application.Output;
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
_runningStreams[streamId] = tcs;
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var frame = new Http2Frame();
frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PADDED, streamId);
frame.HeadersPadLength = padLength;
_hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length);
var extendedHeaderLength = 1; // Padding length field
var buffer = _headerEncodingBuffer.AsSpan();
var extendedHeader = buffer.Slice(0, extendedHeaderLength);
extendedHeader[0] = padLength;
var payload = buffer.Slice(extendedHeaderLength, buffer.Length - padLength - extendedHeaderLength);
frame.PayloadLength = 1 + length + padLength;
frame.Payload.Slice(1 + length).Fill(0);
_hpackEncoder.BeginEncode(headers, payload, out var length);
var padding = buffer.Slice(extendedHeaderLength + length, padLength);
padding.Fill(0);
frame.PayloadLength = extendedHeaderLength + length + padLength;
if (endStream)
{
frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM;
}
await SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, writableBuffer);
writableBuffer.Write(buffer.Slice(0, frame.PayloadLength));
return FlushAsync(writableBuffer);
}
protected async Task SendHeadersWithPriorityAsync(int streamId, IEnumerable<KeyValuePair<string, string>> headers, byte priority, int streamDependency, bool endStream)
/* https://tools.ietf.org/html/rfc7540#section-6.2
+-+-------------+-----------------------------------------------+
|E| Stream Dependency? (31) |
+-+-------------+-----------------------------------------------+
| Weight? (8) |
+-+-------------+-----------------------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
*/
protected Task SendHeadersWithPriorityAsync(int streamId, IEnumerable<KeyValuePair<string, string>> headers, byte priority, int streamDependency, bool endStream)
{
var writableBuffer = _pair.Application.Output;
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
_runningStreams[streamId] = tcs;
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var frame = new Http2Frame();
frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PRIORITY, streamId);
frame.HeadersPriorityWeight = priority;
frame.HeadersStreamDependency = streamDependency;
_hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length);
var extendedHeaderLength = 5; // stream dependency + weight
var buffer = _headerEncodingBuffer.AsSpan();
var extendedHeader = buffer.Slice(0, extendedHeaderLength);
Bitshifter.WriteUInt31BigEndian(extendedHeader, (uint)streamDependency);
extendedHeader[4] = priority;
var payload = buffer.Slice(extendedHeaderLength);
frame.PayloadLength = 5 + length;
_hpackEncoder.BeginEncode(headers, payload, out var length);
frame.PayloadLength = extendedHeaderLength + length;
if (endStream)
{
frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM;
}
await SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, writableBuffer);
writableBuffer.Write(buffer.Slice(0, frame.PayloadLength));
return FlushAsync(writableBuffer);
}
protected async Task SendHeadersWithPaddingAndPriorityAsync(int streamId, IEnumerable<KeyValuePair<string, string>> headers, byte padLength, byte priority, int streamDependency, bool endStream)
/* https://tools.ietf.org/html/rfc7540#section-6.2
+---------------+
|Pad Length? (8)|
+-+-------------+-----------------------------------------------+
|E| Stream Dependency? (31) |
+-+-------------+-----------------------------------------------+
| Weight? (8) |
+-+-------------+-----------------------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
*/
protected Task SendHeadersWithPaddingAndPriorityAsync(int streamId, IEnumerable<KeyValuePair<string, string>> headers, byte padLength, byte priority, int streamDependency, bool endStream)
{
var writableBuffer = _pair.Application.Output;
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
_runningStreams[streamId] = tcs;
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var frame = new Http2Frame();
frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PADDED | Http2HeadersFrameFlags.PRIORITY, streamId);
frame.HeadersPadLength = padLength;
frame.HeadersPriorityWeight = priority;
frame.HeadersStreamDependency = streamDependency;
_hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length);
var extendedHeaderLength = 6; // pad length + stream dependency + weight
var buffer = _headerEncodingBuffer.AsSpan();
var extendedHeader = buffer.Slice(0, extendedHeaderLength);
extendedHeader[0] = padLength;
Bitshifter.WriteUInt31BigEndian(extendedHeader.Slice(1), (uint)streamDependency);
extendedHeader[5] = priority;
var payload = buffer.Slice(extendedHeaderLength, buffer.Length - padLength - extendedHeaderLength);
frame.PayloadLength = 6 + length + padLength;
frame.Payload.Slice(6 + length).Fill(0);
_hpackEncoder.BeginEncode(headers, payload, out var length);
var padding = buffer.Slice(extendedHeaderLength + length, padLength);
padding.Fill(0);
frame.PayloadLength = extendedHeaderLength + length + padLength;
if (endStream)
{
frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM;
}
await SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, writableBuffer);
writableBuffer.Write(buffer.Slice(0, frame.PayloadLength));
return FlushAsync(writableBuffer);
}
protected Task WaitForAllStreamsAsync()
@ -450,324 +523,428 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
protected Task SendPreambleAsync() => SendAsync(new ArraySegment<byte>(Http2Connection.ClientPreface));
protected Task SendSettingsAsync()
protected async Task SendSettingsAsync()
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults());
return SendAsync(frame.Raw);
var writableBuffer = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareSettings(Http2SettingsFrameFlags.NONE);
var settings = _clientSettings.GetNonProtocolDefaults();
var payload = new byte[settings.Count * Http2FrameReader.SettingSize];
frame.PayloadLength = payload.Length;
Http2FrameWriter.WriteSettings(settings, payload);
Http2FrameWriter.WriteHeader(frame, writableBuffer);
await SendAsync(payload);
}
protected Task SendSettingsAckWithInvalidLengthAsync(int length)
protected async Task SendSettingsAckWithInvalidLengthAsync(int length)
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var writableBuffer = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareSettings(Http2SettingsFrameFlags.ACK);
frame.PayloadLength = length;
return SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, writableBuffer);
await SendAsync(new byte[length]);
}
protected Task SendSettingsWithInvalidStreamIdAsync(int streamId)
protected async Task SendSettingsWithInvalidStreamIdAsync(int streamId)
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults());
var writableBuffer = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareSettings(Http2SettingsFrameFlags.NONE);
frame.StreamId = streamId;
return SendAsync(frame.Raw);
var settings = _clientSettings.GetNonProtocolDefaults();
var payload = new byte[settings.Count * Http2FrameReader.SettingSize];
frame.PayloadLength = payload.Length;
Http2FrameWriter.WriteSettings(settings, payload);
Http2FrameWriter.WriteHeader(frame, writableBuffer);
await SendAsync(payload);
}
protected Task SendSettingsWithInvalidLengthAsync(int length)
protected async Task SendSettingsWithInvalidLengthAsync(int length)
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults());
var writableBuffer = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareSettings(Http2SettingsFrameFlags.NONE);
frame.PayloadLength = length;
return SendAsync(frame.Raw);
var payload = new byte[length];
Http2FrameWriter.WriteHeader(frame, writableBuffer);
await SendAsync(payload);
}
protected Task SendSettingsWithInvalidParameterValueAsync(Http2SettingsParameter parameter, uint value)
protected async Task SendSettingsWithInvalidParameterValueAsync(Http2SettingsParameter parameter, uint value)
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var writableBuffer = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareSettings(Http2SettingsFrameFlags.NONE);
frame.PayloadLength = 6;
var payload = new byte[Http2FrameReader.SettingSize];
payload[0] = (byte)((ushort)parameter >> 8);
payload[1] = (byte)(ushort)parameter;
payload[2] = (byte)(value >> 24);
payload[3] = (byte)(value >> 16);
payload[4] = (byte)(value >> 8);
payload[5] = (byte)value;
frame.Payload[0] = (byte)((ushort)parameter >> 8);
frame.Payload[1] = (byte)(ushort)parameter;
frame.Payload[2] = (byte)(value >> 24);
frame.Payload[3] = (byte)(value >> 16);
frame.Payload[4] = (byte)(value >> 8);
frame.Payload[5] = (byte)value;
return SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, writableBuffer);
await SendAsync(payload);
}
protected Task SendPushPromiseFrameAsync()
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var writableBuffer = _pair.Application.Output;
var frame = new Http2Frame();
frame.PayloadLength = 0;
frame.Type = Http2FrameType.PUSH_PROMISE;
frame.StreamId = 1;
return SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, writableBuffer);
return FlushAsync(writableBuffer);
}
protected async Task<bool> SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, IEnumerable<KeyValuePair<string, string>> headers)
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareHeaders(flags, streamId);
var done = _hpackEncoder.BeginEncode(headers, frame.Payload, out var length);
var buffer = _headerEncodingBuffer.AsMemory();
var done = _hpackEncoder.BeginEncode(headers, buffer.Span, out var length);
frame.PayloadLength = length;
await SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, outputWriter);
await SendAsync(buffer.Span.Slice(0, length));
return done;
}
protected Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, byte[] headerBlock)
protected async Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, byte[] headerBlock)
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareHeaders(flags, streamId);
frame.PayloadLength = headerBlock.Length;
headerBlock.CopyTo(frame.HeadersPayload);
return SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, outputWriter);
await SendAsync(headerBlock);
}
protected Task SendInvalidHeadersFrameAsync(int streamId, int payloadLength, byte padLength)
protected async Task SendInvalidHeadersFrameAsync(int streamId, int payloadLength, byte padLength)
{
Assert.True(padLength >= payloadLength, $"{nameof(padLength)} must be greater than or equal to {nameof(payloadLength)} to create an invalid frame.");
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareHeaders(Http2HeadersFrameFlags.PADDED, streamId);
frame.Payload[0] = padLength;
// Set length last so .Payload can be written to
frame.PayloadLength = payloadLength;
var payload = new byte[payloadLength];
if (payloadLength > 0)
{
payload[0] = padLength;
}
return SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, outputWriter);
await SendAsync(payload);
}
protected Task SendIncompleteHeadersFrameAsync(int streamId)
protected async Task SendIncompleteHeadersFrameAsync(int streamId)
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS, streamId);
frame.PayloadLength = 3;
var payload = new byte[3];
// Set up an incomplete Literal Header Field w/ Incremental Indexing frame,
// with an incomplete new name
frame.Payload[0] = 0;
frame.Payload[1] = 2;
frame.Payload[2] = (byte)'a';
payload[0] = 0;
payload[1] = 2;
payload[2] = (byte)'a';
return SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, outputWriter);
await SendAsync(payload);
}
protected async Task<bool> SendContinuationAsync(int streamId, Http2ContinuationFrameFlags flags)
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareContinuation(flags, streamId);
var done = _hpackEncoder.Encode(frame.Payload, out var length);
var buffer = _headerEncodingBuffer.AsMemory();
var done = _hpackEncoder.Encode(buffer.Span, out var length);
frame.PayloadLength = length;
await SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, outputWriter);
await SendAsync(buffer.Span.Slice(0, length));
return done;
}
protected async Task SendContinuationAsync(int streamId, Http2ContinuationFrameFlags flags, byte[] payload)
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareContinuation(flags, streamId);
frame.PayloadLength = payload.Length;
payload.CopyTo(frame.Payload);
await SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, outputWriter);
await SendAsync(payload);
}
protected Task SendEmptyContinuationFrameAsync(int streamId, Http2ContinuationFrameFlags flags)
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareContinuation(flags, streamId);
frame.PayloadLength = 0;
return SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, outputWriter);
return FlushAsync(outputWriter);
}
protected Task SendIncompleteContinuationFrameAsync(int streamId)
protected async Task SendIncompleteContinuationFrameAsync(int streamId)
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareContinuation(Http2ContinuationFrameFlags.END_HEADERS, streamId);
frame.PayloadLength = 3;
var payload = new byte[3];
// Set up an incomplete Literal Header Field w/ Incremental Indexing frame,
// with an incomplete new name
frame.Payload[0] = 0;
frame.Payload[1] = 2;
frame.Payload[2] = (byte)'a';
payload[0] = 0;
payload[1] = 2;
payload[2] = (byte)'a';
return SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, outputWriter);
await SendAsync(payload);
}
protected Task SendDataAsync(int streamId, Span<byte> data, bool endStream)
protected Task SendDataAsync(int streamId, Memory<byte> data, bool endStream)
{
var frame = new Http2Frame((uint)data.Length);
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareData(streamId);
frame.PayloadLength = data.Length;
frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE;
data.CopyTo(frame.DataPayload);
return SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, outputWriter);
return SendAsync(data.Span);
}
protected Task SendDataWithPaddingAsync(int streamId, Span<byte> data, byte padLength, bool endStream)
protected async Task SendDataWithPaddingAsync(int streamId, Memory<byte> data, byte padLength, bool endStream)
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareData(streamId, padLength);
frame.PayloadLength = data.Length + 1 + padLength;
data.CopyTo(frame.DataPayload);
if (endStream)
{
frame.DataFlags |= Http2DataFrameFlags.END_STREAM;
}
return SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, outputWriter);
outputWriter.GetSpan(1)[0] = padLength;
outputWriter.Advance(1);
await SendAsync(data.Span);
await SendAsync(new byte[padLength]);
}
protected Task SendInvalidDataFrameAsync(int streamId, int frameLength, byte padLength)
{
Assert.True(padLength >= frameLength, $"{nameof(padLength)} must be greater than or equal to {nameof(frameLength)} to create an invalid frame.");
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareData(streamId);
frame.DataFlags = Http2DataFrameFlags.PADDED;
frame.Payload[0] = padLength;
// Set length last so .Payload can be written to
frame.PayloadLength = frameLength;
var payload = new byte[frameLength];
if (frameLength > 0)
{
payload[0] = padLength;
}
return SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, outputWriter);
return SendAsync(payload);
}
protected Task SendPingAsync(Http2PingFrameFlags flags)
{
var pingFrame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var pingFrame = new Http2Frame();
pingFrame.PreparePing(flags);
return SendAsync(pingFrame.Raw);
Http2FrameWriter.WriteHeader(pingFrame, outputWriter);
return SendAsync(new byte[8]); // Empty payload
}
protected Task SendPingWithInvalidLengthAsync(int length)
{
var pingFrame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var pingFrame = new Http2Frame();
pingFrame.PreparePing(Http2PingFrameFlags.NONE);
pingFrame.PayloadLength = length;
return SendAsync(pingFrame.Raw);
Http2FrameWriter.WriteHeader(pingFrame, outputWriter);
return SendAsync(new byte[length]);
}
protected Task SendPingWithInvalidStreamIdAsync(int streamId)
{
Assert.NotEqual(0, streamId);
var pingFrame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var pingFrame = new Http2Frame();
pingFrame.PreparePing(Http2PingFrameFlags.NONE);
pingFrame.StreamId = streamId;
return SendAsync(pingFrame.Raw);
Http2FrameWriter.WriteHeader(pingFrame, outputWriter);
return SendAsync(new byte[pingFrame.PayloadLength]);
}
/* https://tools.ietf.org/html/rfc7540#section-6.3
+-+-------------------------------------------------------------+
|E| Stream Dependency (31) |
+-+-------------+-----------------------------------------------+
| Weight (8) |
+-+-------------+
*/
protected Task SendPriorityAsync(int streamId, int streamDependency = 0)
{
var priorityFrame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var priorityFrame = new Http2Frame();
priorityFrame.PreparePriority(streamId, streamDependency: streamDependency, exclusive: false, weight: 0);
return SendAsync(priorityFrame.Raw);
var payload = new byte[priorityFrame.PayloadLength].AsSpan();
Bitshifter.WriteUInt31BigEndian(payload, (uint)streamDependency);
payload[4] = 0; // Weight
Http2FrameWriter.WriteHeader(priorityFrame, outputWriter);
return SendAsync(payload);
}
protected Task SendInvalidPriorityFrameAsync(int streamId, int length)
{
var priorityFrame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var priorityFrame = new Http2Frame();
priorityFrame.PreparePriority(streamId, streamDependency: 0, exclusive: false, weight: 0);
priorityFrame.PayloadLength = length;
return SendAsync(priorityFrame.Raw);
Http2FrameWriter.WriteHeader(priorityFrame, outputWriter);
return SendAsync(new byte[length]);
}
/* https://tools.ietf.org/html/rfc7540#section-6.4
+---------------------------------------------------------------+
| Error Code (32) |
+---------------------------------------------------------------+
*/
protected Task SendRstStreamAsync(int streamId)
{
var rstStreamFrame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var rstStreamFrame = new Http2Frame();
rstStreamFrame.PrepareRstStream(streamId, Http2ErrorCode.CANCEL);
return SendAsync(rstStreamFrame.Raw);
var payload = new byte[rstStreamFrame.PayloadLength];
BinaryPrimitives.WriteUInt32BigEndian(payload, (uint)Http2ErrorCode.CANCEL);
Http2FrameWriter.WriteHeader(rstStreamFrame, outputWriter);
return SendAsync(payload);
}
protected Task SendInvalidRstStreamFrameAsync(int streamId, int length)
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareRstStream(streamId, Http2ErrorCode.CANCEL);
frame.PayloadLength = length;
return SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, outputWriter);
return SendAsync(new byte[length]);
}
protected Task SendGoAwayAsync()
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareGoAway(0, Http2ErrorCode.NO_ERROR);
return SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, outputWriter);
return SendAsync(new byte[frame.PayloadLength]);
}
protected Task SendInvalidGoAwayFrameAsync()
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareGoAway(0, Http2ErrorCode.NO_ERROR);
frame.StreamId = 1;
return SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, outputWriter);
return SendAsync(new byte[frame.PayloadLength]);
}
protected Task SendWindowUpdateAsync(int streamId, int sizeIncrement)
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareWindowUpdate(streamId, sizeIncrement);
return SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, outputWriter);
var buffer = outputWriter.GetSpan(4);
Bitshifter.WriteUInt31BigEndian(buffer, (uint)sizeIncrement);
outputWriter.Advance(4);
return FlushAsync(outputWriter);
}
protected Task SendInvalidWindowUpdateAsync(int streamId, int sizeIncrement, int length)
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareWindowUpdate(streamId, sizeIncrement);
frame.PayloadLength = length;
return SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, outputWriter);
return SendAsync(new byte[length]);
}
protected Task SendUnknownFrameTypeAsync(int streamId, int frameType)
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.StreamId = streamId;
frame.Type = (Http2FrameType)frameType;
frame.PayloadLength = 0;
return SendAsync(frame.Raw);
Http2FrameWriter.WriteHeader(frame, outputWriter);
return FlushAsync(outputWriter);
}
protected async Task<Http2Frame> ReceiveFrameAsync(uint maxFrameSize = Http2PeerSettings.DefaultMaxFrameSize)
protected async Task<Http2FrameWithPayload> ReceiveFrameAsync(uint maxFrameSize = Http2PeerSettings.DefaultMaxFrameSize)
{
var frame = new Http2Frame(maxFrameSize);
var frame = new Http2FrameWithPayload();
while (true)
{
var result = await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout();
var buffer = result.Buffer;
var consumed = buffer.Start;
var examined = buffer.End;
var examined = buffer.Start;
try
{
Assert.True(buffer.Length > 0);
if (Http2FrameReader.ReadFrame(buffer, frame, maxFrameSize, out consumed, out examined))
if (Http2FrameReader.ReadFrame(buffer, frame, maxFrameSize, out var framePayload))
{
consumed = examined = framePayload.End;
frame.Payload = framePayload.ToArray();
return frame;
}
else
{
examined = buffer.End;
}
if (result.IsCompleted)
{
@ -781,7 +958,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
}
}
protected async Task<Http2Frame> ExpectAsync(Http2FrameType type, int withLength, byte withFlags, int withStreamId)
protected async Task<Http2FrameWithPayload> ExpectAsync(Http2FrameType type, int withLength, byte withFlags, int withStreamId)
{
var frame = await ReceiveFrameAsync((uint)withLength);
@ -864,5 +1041,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Equal(header.Value, value, ignoreCase: true);
}
}
public class Http2FrameWithPayload : Http2Frame
{
public Http2FrameWithPayload() : base()
{
}
// This does not contain extended headers
public Memory<byte> Payload { get; set; }
public ReadOnlySequence<byte> PayloadSequence => new ReadOnlySequence<byte>(Payload);
}
}
}

View File

@ -92,21 +92,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.Http2
private async Task<Http2Frame> ReceiveFrameAsync(PipeReader reader)
{
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
var frame = new Http2Frame();
while (true)
{
var result = await reader.ReadAsync();
var buffer = result.Buffer;
var consumed = buffer.Start;
var examined = buffer.End;
var examined = buffer.Start;
try
{
if (Http2FrameReader.ReadFrame(buffer, frame, 16_384, out consumed, out examined))
if (Http2FrameReader.ReadFrame(buffer, frame, 16_384, out var framePayload))
{
consumed = examined = framePayload.End;
return frame;
}
else
{
examined = buffer.End;
}
if (result.IsCompleted)
{