diff --git a/src/Kestrel.Core/CoreStrings.resx b/src/Kestrel.Core/CoreStrings.resx
index 76953b051c..5a04c01d0c 100644
--- a/src/Kestrel.Core/CoreStrings.resx
+++ b/src/Kestrel.Core/CoreStrings.resx
@@ -572,13 +572,13 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
A value greater than zero is required.
-
- The frame is too short to contain the fields indicated by the given flags.
-
A value between {min} and {max} is required.
Dynamic tables size update did not occur at the beginning of the first header block.
+
+ The given buffer was too small to encode any headers.
+
\ No newline at end of file
diff --git a/src/Kestrel.Core/Internal/Http2/HPack/HPackDecoder.cs b/src/Kestrel.Core/Internal/Http2/HPack/HPackDecoder.cs
index 51d6068ae1..3b6cc0f00b 100644
--- a/src/Kestrel.Core/Internal/Http2/HPack/HPackDecoder.cs
+++ b/src/Kestrel.Core/Internal/Http2/HPack/HPackDecoder.cs
@@ -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 data, bool endHeaders, IHttpHeadersHandler handler)
+ public void Decode(ReadOnlySequence 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)
diff --git a/src/Kestrel.Core/Internal/Http2/HPack/HPackEncoder.cs b/src/Kestrel.Core/Internal/Http2/HPack/HPackEncoder.cs
index b364ec7e49..a474f975e1 100644
--- a/src/Kestrel.Core/Internal/Http2/HPack/HPackEncoder.cs
+++ b/src/Kestrel.Core/Internal/Http2/HPack/HPackEncoder.cs
@@ -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 buffer, out int length)
+ {
+ return Encode(buffer, throwIfNoneEncoded: true, out length);
+ }
+
+ private bool Encode(Span 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;
}
diff --git a/src/Kestrel.Core/Internal/Http2/HPack/HPackEncodingException.cs b/src/Kestrel.Core/Internal/Http2/HPack/HPackEncodingException.cs
new file mode 100644
index 0000000000..ded6cb50df
--- /dev/null
+++ b/src/Kestrel.Core/Internal/Http2/HPack/HPackEncodingException.cs
@@ -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)
+ {
+ }
+ }
+}
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs
index d9dc7910b4..25500bb28a 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs
@@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private readonly Http2PeerSettings _serverSettings = new Http2PeerSettings();
private readonly Http2PeerSettings _clientSettings = new Http2PeerSettings();
- private readonly Http2Frame _incomingFrame;
+ 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(IHttpApplication application)
+ private Task ProcessFrameAsync(IHttpApplication application, ReadOnlySequence 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 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(IHttpApplication application)
+ private Task ProcessHeadersFrameAsync(IHttpApplication application, ReadOnlySequence 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 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 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(IHttpApplication application)
+ private Task ProcessContinuationFrameAsync(IHttpApplication application, ReadOnlySequence 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(IHttpApplication application, bool endHeaders, Span payload)
+ private Task DecodeHeadersAsync(IHttpApplication application, bool endHeaders, ReadOnlySequence payload)
{
try
{
@@ -838,7 +833,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return Task.CompletedTask;
}
- private Task DecodeTrailersAsync(bool endHeaders, Span payload)
+ private Task DecodeTrailersAsync(bool endHeaders, ReadOnlySequence payload)
{
_hpackDecoder.Decode(payload, endHeaders, handler: this);
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.Continuation.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.Continuation.cs
index 56712f5289..d971385b95 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Frame.Continuation.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.Continuation.cs
@@ -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;
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.Data.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.Data.cs
index 3bc53971e5..93765602e0 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Frame.Data.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.Data.cs
@@ -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 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;
}
}
}
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.GoAway.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.GoAway.cs
index 602c5f1fac..cb0aa22501 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Frame.GoAway.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.GoAway.cs
@@ -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)
{
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.Headers.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.Headers.cs
index 3028e27200..16f369570e 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Frame.Headers.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.Headers.cs
@@ -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 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;
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.Ping.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.Ping.cs
index 239c006c1b..2c69de1b21 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Frame.Ping.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.Ping.cs
@@ -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
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.Priority.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.Priority.cs
index 53f1d9e368..4c293e9de6 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Frame.Priority.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.Priority.cs
@@ -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)
{
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.RstStream.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.RstStream.cs
index c9c2716f3b..98a8fa22ca 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Frame.RstStream.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.RstStream.cs
@@ -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)
{
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.Settings.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.Settings.cs
index ceddf8089d..b8f3ffe198 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Frame.Settings.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.Settings.cs
@@ -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 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 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);
}
}
}
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.WindowUpdate.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.WindowUpdate.cs
index 098064b3e3..404de2c446 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Frame.WindowUpdate.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.WindowUpdate.cs
@@ -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)
{
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.cs
index f9d0723bdc..9880572ed9 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Frame.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.cs
@@ -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 Raw => new Span(_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 Payload => new Span(_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()}";
+ }
}
}
diff --git a/src/Kestrel.Core/Internal/Http2/Http2FrameReader.cs b/src/Kestrel.Core/Internal/Http2/Http2FrameReader.cs
index 15f3d430a6..0bc2617ac3 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2FrameReader.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2FrameReader.cs
@@ -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 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 readableBuffer, Http2Frame frame, uint maxFrameSize, out ReadOnlySequence framePayload)
+ {
+ framePayload = ReadOnlySequence.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 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 ReadSettings(ReadOnlySequence 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 payload)
+ {
+ var id = (Http2SettingsParameter)BinaryPrimitives.ReadUInt16BigEndian(payload);
+ var value = BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(2));
+
+ return new Http2PeerSetting(id, value);
+ }
}
}
diff --git a/src/Kestrel.Core/Internal/Http2/Http2FrameWriter.cs b/src/Kestrel.Core/Internal/Http2/Http2FrameWriter.cs
index 426c330314..c71ee387f0 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2FrameWriter.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2FrameWriter.cs
@@ -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 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 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 settings, Span 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 payload)
+ /* https://tools.ietf.org/html/rfc7540#section-6.7
+ +---------------------------------------------------------------+
+ | |
+ | Opaque Data (64) |
+ | |
+ +---------------------------------------------------------------+
+ */
+ public Task WritePingAsync(Http2PingFrameFlags flags, ReadOnlySequence 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)
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Stream.cs b/src/Kestrel.Core/Internal/Http2/Http2Stream.cs
index 64e32d560e..8bcdd5fcdc 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Stream.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Stream.cs
@@ -279,7 +279,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
}
- public Task OnDataAsync(Http2Frame dataFrame)
+ public Task OnDataAsync(Http2Frame dataFrame, ReadOnlySequence 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
diff --git a/src/Kestrel.Core/Properties/CoreStrings.Designer.cs b/src/Kestrel.Core/Properties/CoreStrings.Designer.cs
index c9710cd26e..4717a70da3 100644
--- a/src/Kestrel.Core/Properties/CoreStrings.Designer.cs
+++ b/src/Kestrel.Core/Properties/CoreStrings.Designer.cs
@@ -2128,20 +2128,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
internal static string FormatGreaterThanZeroRequired()
=> GetString("GreaterThanZeroRequired");
- ///
- /// The frame is too short to contain the fields indicated by the given flags.
- ///
- internal static string Http2FrameMissingFields
- {
- get => GetString("Http2FrameMissingFields");
- }
-
- ///
- /// The frame is too short to contain the fields indicated by the given flags.
- ///
- internal static string FormatHttp2FrameMissingFields()
- => GetString("Http2FrameMissingFields");
-
///
/// A value between {min} and {max} is required.
///
@@ -2170,6 +2156,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
internal static string FormatHPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock()
=> GetString("HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock");
+ ///
+ /// The given buffer was too small to encode any headers.
+ ///
+ internal static string HPackErrorNotEnoughBuffer
+ {
+ get => GetString("HPackErrorNotEnoughBuffer");
+ }
+
+ ///
+ /// The given buffer was too small to encode any headers.
+ ///
+ internal static string FormatHPackErrorNotEnoughBuffer()
+ => GetString("HPackErrorNotEnoughBuffer");
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/test/Kestrel.Core.Tests/HPackDecoderTests.cs b/test/Kestrel.Core.Tests/HPackDecoderTests.cs
index 70d1f3e085..c45614348a 100644
--- a/test/Kestrel.Core.Tests/HPackDecoderTests.cs
+++ b/test/Kestrel.Core.Tests/HPackDecoderTests.cs
@@ -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(_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(_indexedHeaderDynamic), endHeaders: true, handler: this);
Assert.Equal(_headerValueString, _decodedHeaders[_headerNameString]);
}
[Fact]
public void DecodesIndexedHeaderField_OutOfRange_Error()
{
- var exception = Assert.Throws(() => _decoder.Decode(_indexedHeaderDynamic, endHeaders: true, handler: this));
+ var exception = Assert.Throws(() =>
+ _decoder.Decode(new ReadOnlySequence(_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(() => _decoder.Decode(new byte[] { 0x7e }, endHeaders: true, handler: this));
+ var exception = Assert.Throws(() => _decoder.Decode(new ReadOnlySequence(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(() => _decoder.Decode(new byte[] { 0x0f, 0x2f }, endHeaders: true, handler: this));
+ var exception = Assert.Throws(() => _decoder.Decode(new ReadOnlySequence(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(() => _decoder.Decode(new byte[] { 0x1f, 0x2f }, endHeaders: true, handler: this));
+ var exception = Assert.Throws(() => _decoder.Decode(new ReadOnlySequence(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(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(() => _decoder.Decode(_indexedHeaderStatic.Concat(new byte[] { 0x3e }).ToArray(), endHeaders: true, handler: this));
+ var data = new ReadOnlySequence(_indexedHeaderStatic.Concat(new byte[] { 0x3e }).ToArray());
+ var exception = Assert.Throws(() => _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(_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(() => _decoder.Decode(new byte[] { 0x3e }, endHeaders: true, handler: this));
+ var data = new ReadOnlySequence(new byte[] { 0x3e });
+ var exception = Assert.Throws(() => _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(_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(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(() => _decoder.Decode(new byte[] { 0x3f, 0xe2, 0x1f }, endHeaders: true, handler: this));
+ var exception = Assert.Throws(() =>
+ _decoder.Decode(new ReadOnlySequence(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(() => _decoder.Decode(encoded, endHeaders: true, handler: this));
+ var exception = Assert.Throws(() => _decoder.Decode(new ReadOnlySequence(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(() => _decoder.Decode(encoded, endHeaders: true, handler: this));
+ var exception = Assert.Throws(() => _decoder.Decode(new ReadOnlySequence(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(() => _decoder.Decode(encoded, endHeaders: true, handler: this));
+ var exception = Assert.Throws(() => _decoder.Decode(new ReadOnlySequence(encoded), endHeaders: true, handler: this));
Assert.Equal(CoreStrings.HPackHuffmanError, exception.Message);
Assert.IsType(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(encoded), endHeaders: true, handler: this);
Assert.Equal(expectedHeaderValue, _decodedHeaders[expectedHeaderName]);
diff --git a/test/Kestrel.InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/test/Kestrel.InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs
index 3a97c60a79..a098609f91 100644
--- a/test/Kestrel.InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs
+++ b/test/Kestrel.InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs
@@ -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(
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(
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(
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);
diff --git a/test/Kestrel.InMemory.FunctionalTests/Http2/Http2StreamTests.cs b/test/Kestrel.InMemory.FunctionalTests/Http2/Http2StreamTests.cs
index 8c623167fb..c8623ced46 100644
--- a/test/Kestrel.InMemory.FunctionalTests/Http2/Http2StreamTests.cs
+++ b/test/Kestrel.InMemory.FunctionalTests/Http2/Http2StreamTests.cs
@@ -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(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(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ await InitializeConnectionAsync(async context =>
+ {
+ context.Response.Headers["too_long"] = new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize);
+ var ex = await Assert.ThrowsAsync(() => 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);
+ }
}
}
\ No newline at end of file
diff --git a/test/Kestrel.InMemory.FunctionalTests/Http2/Http2TestBase.cs b/test/Kestrel.InMemory.FunctionalTests/Http2/Http2TestBase.cs
index 126cbf8ef1..ed1fecc8d9 100644
--- a/test/Kestrel.InMemory.FunctionalTests/Http2/Http2TestBase.cs
+++ b/test/Kestrel.InMemory.FunctionalTests/Http2/Http2TestBase.cs
@@ -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> _runningStreams = new ConcurrentDictionary>();
protected readonly Dictionary _receivedHeaders = new Dictionary(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> headers, bool endStream)
+ protected Task StartStreamAsync(int streamId, IEnumerable> headers, bool endStream)
{
+ var writableBuffer = _pair.Application.Output;
var tcs = new TaskCompletionSource